diff --git a/README b/README index edf3c134..8fd75b81 100644 --- a/README +++ b/README @@ -134,8 +134,10 @@ ckpool supports the following options: -n NAME | --name NAME -P | --passthrough -p | --proxy +-R | --redirector -S CKDB-SOCKDIR | --ckdb-sockdir CKDB-SOCKDIR -s SOCKDIR | --sockdir SOCKDIR +-u | --userproxy -A Standalone mode tells ckpool not to try to communicate with ckdb or log any @@ -144,8 +146,9 @@ are automatically accepted without any attempt to authorise users in any way. This option is explicitly enabled when built without ckdb support. -c tells ckpool to override its default configuration filename and -load the specified one. If -c is not specified, ckpool looks for ckpool.conf -whereas in proxy or passthrough modes it will look for ckproxy.conf +load the specified one. If -c is not specified, ckpool looks for ckpool.conf, +in proxy mode it looks for ckproxy.conf, in passthrough mode for +ckpassthrough.conf and in redirector mode for ckredirector.conf -d tells ckpool what the name of the ckdb process is that it should speak to, otherwise it will look for ckdb. @@ -169,8 +172,14 @@ and then workbase. -l will change the ckpool process name to that specified, allowing -multiple different named instances to be running. +multiple different named instances to be running. By default the variant +names are used: ckpool, ckproxy, ckpassthrough, ckredirector, cknode. -P will start ckpool in passthrough proxy mode where it collates all incoming connections and streams all information on a single connection to an upstream @@ -182,6 +191,13 @@ clients as separate entities while presenting shares as a single user to the upstream pool specified. Note that the upstream pool needs to be a ckpool for it to scale to large hashrates. Standalone mode is Optional. +-R will start ckpool in a variant of passthrough mode. It is designed to be a +front end to filter out users that never contribute any shares. Once an +accepted share from the upstream pool is detected, it will issue a redirect to +one of the redirecturl entries in the configuration file. It will cycle over +entries if multiple exist, but try to keep all clients from the same IP +redirecting to the same pool. + -S tells ckpool which directory to look for the ckdb socket to talk to. This option does not exist when built without ckdb support. @@ -189,6 +205,12 @@ This option does not exist when built without ckdb support. -s tells ckpool which directory to place its own communication sockets (/tmp by default) +-u Userproxy mode will start ckpool in proxy mode as per the -p option above, +but in addition it will accept username/passwords from the stratum connects +and try to open additional connections with those credentials to the upstream +pool specified in the configuration file and then reconnect miners to mine with +their chosen username/password to the upstream pool. + ckdb takes the following options: @@ -259,6 +281,9 @@ and 3334 in proxy mode. Multiple entries can be specified as an array by either IP or resolvable domain name but the executable must be able to bind to all of them and ports up to 1024 usually require privileged access. +"redirecturl" : This is an array of URLs that ckpool will redirect active +miners to in redirector mode. They must be valid resolvable URLs+ports. + "mindiff" : Minimum diff that vardiff will allow miners to drop to. Default 1 "startdiff" : Starting diff that new clients are given. Default 42 diff --git a/cknode.conf b/cknode.conf new file mode 100644 index 00000000..3a025572 --- /dev/null +++ b/cknode.conf @@ -0,0 +1,34 @@ +{ +"btcd" : [ + { + "url" : "localhost:8332", + "auth" : "user", + "pass" : "pass", + "notify" : true + }, + { + "url" : "backup:8332", + "auth" : "user", + "pass" : "pass", + "notify" : false + } +], +"proxy" : [ + { + "url" : "ckpool.org:3333", + "auth" : "user", + "pass" : "pass" + }, + { + "url" : "backup.ckpool.org:3333", + "auth" : "user", + "pass" : "pass" + } +], +"serverurl" : [ + "192.168.1.100:3334", + "127.0.0.1:3334" + ], +"logdir" : "logs" +} +Comments from here on are ignored. diff --git a/ckpassthrough.conf b/ckpassthrough.conf new file mode 100644 index 00000000..c5b85bcd --- /dev/null +++ b/ckpassthrough.conf @@ -0,0 +1,15 @@ +{ +"proxy" : [ + { + "url" : "ckpool.org:3333", + "auth" : "user", + "pass" : "pass" + } +], +"serverurl" : [ + "192.168.1.100:3334", + "127.0.0.1:3334" + ], +"logdir" : "logs" +} +Comments from here on are ignored. diff --git a/ckproxy.conf b/ckproxy.conf index 5daddb5b..fec5783a 100644 --- a/ckproxy.conf +++ b/ckproxy.conf @@ -19,7 +19,6 @@ "mindiff" : 1, "startdiff" : 42, "maxdiff" : 0, -"clientsvspeed" : false, "logdir" : "logs" } Comments from here on are ignored. diff --git a/ckredirector.conf b/ckredirector.conf new file mode 100644 index 00000000..2c499a57 --- /dev/null +++ b/ckredirector.conf @@ -0,0 +1,23 @@ +{ +"proxy" : [ + { + "url" : "ckpool.org:3333", + "auth" : "user", + "pass" : "pass" + } +], +"update_interval" : 30, +"serverurl" : [ + "192.168.1.100:3334", + "127.0.0.1:3334" + ], +"redirecturl" : [ + "node1.ckpool.org:3333", + "node2.ckpool.org:3333" + ], +"mindiff" : 1, +"startdiff" : 42, +"maxdiff" : 0, +"logdir" : "logs" +} +Comments from here on are ignored. diff --git a/configure.ac b/configure.ac index 468326e1..12c688e7 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT(ckpool, 0.8.8, kernel@kolivas.org) +AC_INIT(ckpool, 0.9.0, kernel@kolivas.org) AC_CANONICAL_SYSTEM AC_CONFIG_MACRO_DIR([m4]) @@ -40,17 +40,11 @@ AC_CHECK_HEADERS(stdint.h netinet/in.h netinet/tcp.h sys/ioctl.h getopt.h) AC_CHECK_HEADERS(sys/epoll.h libpq-fe.h postgresql/libpq-fe.h grp.h) AC_CHECK_HEADERS(gsl/gsl_math.h gsl/gsl_cdf.h) AC_CHECK_HEADERS(openssl/x509.h openssl/hmac.h) - -PTHREAD_LIBS="-lpthread" -MATH_LIBS="-lm" -RT_LIBS="-lrt" +AC_CHECK_HEADERS(zlib.h) AC_CONFIG_SUBDIRS([src/jansson-2.6]) JANSSON_LIBS="jansson-2.6/src/.libs/libjansson.a" -AC_SUBST(PTHREAD_LIBS) -AC_SUBST(MATH_LIBS) -AC_SUBST(RT_LIBS) AC_SUBST(JANSSON_LIBS) AC_ARG_WITH([ckdb], @@ -58,6 +52,10 @@ AC_ARG_WITH([ckdb], [ckdb=$withval] ) +#AC_SEARCH_LIBS(whatgoeshere?, rt, , echo "Error: Required library realtime not found." && exit 1) +AC_SEARCH_LIBS(exp, m, , echo "Error: Required library math not found." && exit 1) +AC_SEARCH_LIBS(pthread_mutex_trylock, pthread, , "Error: Required library pthreads not found." && exit 1) + if test "x$ckdb" != "xno"; then AC_CHECK_LIB([pq], [main],[PQ=-lpq],echo "Error: Required library libpq-dev not found. Install it or disable postgresql support with --without-ckdb" && exit 1) @@ -84,8 +82,10 @@ echo "Compilation............: make (or gmake)" echo " CPPFLAGS.............: $CPPFLAGS" echo " CFLAGS...............: $CFLAGS" echo " LDFLAGS..............: $LDFLAGS" -echo " LDADD................: $PTHREAD_LIBS $MATH_LIBS $RT_LIBS $JANSSON_LIBS" -echo " db LDADD.............: $DB_LIBS" +echo " LDADD................: $LIBS $JANSSON_LIBS" +if test "x$ckdb" != "xno"; then + echo " db LDADD.............: $LIBS $DB_LIBS $JANSSON_LIBS" +fi echo echo "Installation...........: make install (as root if needed, with 'su' or 'sudo')" echo " prefix...............: $prefix" diff --git a/src/Makefile.am b/src/Makefile.am index f63ec2e7..c4832dab 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -5,7 +5,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/jansson-2.6/src lib_LTLIBRARIES = libckpool.la libckpool_la_SOURCES = libckpool.c libckpool.h sha2.c sha2.h -libckpool_la_LIBADD = @PTHREAD_LIBS@ @MATH_LIBS@ @RT_LIBS@ +libckpool_la_LIBADD = @LIBS@ bin_PROGRAMS = ckpool ckpmsg notifier ckpool_SOURCES = ckpool.c ckpool.h generator.c generator.h bitcoin.c bitcoin.h \ @@ -23,5 +23,5 @@ if WANT_CKDB bin_PROGRAMS += ckdb ckdb_SOURCES = ckdb.c ckdb_cmd.c ckdb_data.c ckdb_dbio.c ckdb_btc.c \ ckdb_crypt.c ckdb.h klist.c ktree.c klist.h ktree.h -ckdb_LDADD = libckpool.la @JANSSON_LIBS@ @DB_LIBS@ @MATH_LIBS@ +ckdb_LDADD = libckpool.la @JANSSON_LIBS@ @DB_LIBS@ @LIBS@ endif diff --git a/src/ckpool.c b/src/ckpool.c index 75ba27dc..6178b225 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -375,6 +376,17 @@ out: return ret; } +static void api_message(ckpool_t *ckp, char **buf, int *sockd) +{ + apimsg_t *apimsg = ckalloc(sizeof(apimsg_t)); + + apimsg->buf = *buf; + *buf = NULL; + apimsg->sockd = *sockd; + *sockd = -1; + ckmsgq_add(ckp->ckpapi, apimsg); +} + /* Listen for incoming global requests. Always returns a response if possible */ static void *listener(void *arg) { @@ -397,6 +409,9 @@ retry: if (!buf) { LOGWARNING("Failed to get message in listener"); send_unix_msg(sockd, "failed"); + } else if (buf[0] == '{') { + /* Any JSON messages received are for the RPC API to handle */ + api_message(ckp, &buf, &sockd); } else if (cmdmatch(buf, "shutdown")) { LOGWARNING("Listener received shutdown message, terminating ckpool"); send_unix_msg(sockd, "exiting"); @@ -492,9 +507,49 @@ bool ping_main(ckpool_t *ckp) void empty_buffer(connsock_t *cs) { + if (cs->buf) + cs->buf[0] = '\0'; cs->buflen = cs->bufofs = 0; } +static void clear_bufline(connsock_t *cs) +{ + if (unlikely(!cs->buf)) + cs->buf = ckzalloc(PAGESIZE); + else if (cs->buflen) { + memmove(cs->buf, cs->buf + cs->bufofs, cs->buflen); + memset(cs->buf + cs->buflen, 0, cs->bufofs); + cs->bufofs = cs->buflen; + cs->buflen = 0; + cs->buf[cs->bufofs] = '\0'; + } else + cs->bufofs = 0; +} + +static void add_bufline(connsock_t *cs, const char *readbuf, const int len) +{ + int backoff = 1; + size_t buflen; + char *newbuf; + + buflen = round_up_page(cs->bufofs + len + 1); + while (42) { + newbuf = realloc(cs->buf, buflen); + if (likely(newbuf)) + break; + if (backoff == 1) + fprintf(stderr, "Failed to realloc %d in read_socket_line, retrying\n", (int)buflen); + cksleep_ms(backoff); + backoff <<= 1; + } + cs->buf = newbuf; + if (unlikely(!cs->buf)) + quit(1, "Failed to alloc buf of %d bytes in read_socket_line", (int)buflen); + memcpy(cs->buf + cs->bufofs, readbuf, len); + cs->bufofs += len; + cs->buf[cs->bufofs] = '\0'; +} + /* Read from a socket into cs->buf till we get an '\n', converting it to '\0' * and storing how much extra data we've received, to be moved to the beginning * of the buffer for use on the next receive. */ @@ -502,86 +557,54 @@ int read_socket_line(connsock_t *cs, float *timeout) { char *eom = NULL; tv_t start, now; - size_t buflen; int ret = -1; - bool polled; float diff; if (unlikely(cs->fd < 0)) goto out; - if (unlikely(!cs->buf)) - cs->buf = ckzalloc(PAGESIZE); - else if (cs->buflen) { - memmove(cs->buf, cs->buf + cs->bufofs, cs->buflen); - memset(cs->buf + cs->buflen, 0, cs->bufofs); - cs->bufofs = cs->buflen; - cs->buflen = 0; - cs->buf[cs->bufofs] = '\0'; - eom = strchr(cs->buf, '\n'); - } + clear_bufline(cs); + eom = strchr(cs->buf, '\n'); tv_time(&start); -rewait: - if (*timeout < 0) { - LOGDEBUG("Timed out in read_socket_line"); - ret = 0; - goto out; - } - ret = wait_read_select(cs->fd, eom ? 0 : *timeout); - polled = true; - if (ret < 1) { - if (!ret) { - if (eom) - goto parse; - LOGDEBUG("Select timed out in read_socket_line"); - } else - LOGERR("Select failed in read_socket_line"); - goto out; - } - tv_time(&now); - diff = tvdiff(&now, &start); - copy_tv(&start, &now); - *timeout -= diff; - while (42) { - char readbuf[PAGESIZE] = {}; - int backoff = 1; - char *newbuf; + while (!eom) { + char readbuf[PAGESIZE]; + + if (*timeout < 0) { + if (cs->ckp->proxy) + LOGINFO("Timed out in read_socket_line"); + else + LOGERR("Timed out in read_socket_line"); + ret = 0; + goto out; + } + ret = wait_read_select(cs->fd, *timeout); + if (ret < 1) { + if (cs->ckp->proxy) + LOGINFO("Select %s in read_socket_line", !ret ? "timed out" : "failed"); + else + LOGERR("Select %s in read_socket_line", !ret ? "timed out" : "failed"); + goto out; + } ret = recv(cs->fd, readbuf, PAGESIZE - 4, MSG_DONTWAIT); if (ret < 1) { - /* No more to read or closed socket after valid message */ - if (eom) - break; - /* Have we used up all the timeout yet? If polled is - * set that means poll has said there should be + /* If we have done wait_read_select there should be * something to read and if we get nothing it means the * socket is closed. */ - if (!polled && *timeout >= 0 && (errno == EAGAIN || errno == EWOULDBLOCK || !ret)) - goto rewait; - LOGERR("Failed to recv in read_socket_line"); + if (cs->ckp->proxy) + LOGINFO("Failed to recv in read_socket_line"); + else + LOGERR("Failed to recv in read_socket_line"); goto out; } - polled = false; - buflen = cs->bufofs + ret + 1; - while (42) { - newbuf = realloc(cs->buf, buflen); - if (likely(newbuf)) - break; - if (backoff == 1) - fprintf(stderr, "Failed to realloc %d in read_socket_line, retrying\n", (int)buflen); - cksleep_ms(backoff); - backoff <<= 1; - } - cs->buf = newbuf; - if (unlikely(!cs->buf)) - quit(1, "Failed to alloc buf of %d bytes in read_socket_line", (int)buflen); - memcpy(cs->buf + cs->bufofs, readbuf, ret); - cs->bufofs += ret; - cs->buf[cs->bufofs] = '\0'; + add_bufline(cs, readbuf, ret); eom = strchr(cs->buf, '\n'); + tv_time(&now); + diff = tvdiff(&now, &start); + copy_tv(&start, &now); + *timeout -= diff; } -parse: ret = eom - cs->buf; cs->buflen = cs->buf + cs->bufofs - eom - 1; @@ -1126,7 +1149,7 @@ bool json_get_int64(int64_t *store, const json_t *val, const char *res) goto out; } if (!json_is_integer(entry)) { - LOGWARNING("Json entry %s is not an integer", res); + LOGINFO("Json entry %s is not an integer", res); goto out; } *store = json_integer_value(entry); @@ -1176,7 +1199,7 @@ out: return ret; } -static bool json_get_bool(bool *store, const json_t *val, const char *res) +bool json_get_bool(bool *store, const json_t *val, const char *res) { json_t *entry = json_object_get(val, res); bool ret = false; @@ -1186,7 +1209,7 @@ static bool json_get_bool(bool *store, const json_t *val, const char *res) goto out; } if (!json_is_boolean(entry)) { - LOGWARNING("Json entry %s is not a boolean", res); + LOGINFO("Json entry %s is not a boolean", res); goto out; } *store = json_is_true(entry); @@ -1196,6 +1219,26 @@ out: return ret; } +bool json_getdel_int(int *store, json_t *val, const char *res) +{ + bool ret; + + ret = json_get_int(store, val, res); + if (ret) + json_object_del(val, res); + return ret; +} + +bool json_getdel_int64(int64_t *store, json_t *val, const char *res) +{ + bool ret; + + ret = json_get_int64(store, val, res); + if (ret) + json_object_del(val, res); + return ret; +} + static void parse_btcds(ckpool_t *ckp, const json_t *arr_val, const int arr_size) { json_t *val; @@ -1261,6 +1304,43 @@ out: return ret; } +static bool parse_redirecturls(ckpool_t *ckp, const json_t *arr_val) +{ + bool ret = false; + int arr_size, i; + char *redirecturl, url[INET6_ADDRSTRLEN], port[8]; + redirecturl = alloca(INET6_ADDRSTRLEN); + + if (!arr_val) + goto out; + if (!json_is_array(arr_val)) { + LOGNOTICE("Unable to parse redirecturl entries as an array"); + goto out; + } + arr_size = json_array_size(arr_val); + if (!arr_size) { + LOGWARNING("redirecturl array empty"); + goto out; + } + ckp->redirecturls = arr_size; + ckp->redirecturl = ckalloc(sizeof(char *) * arr_size); + ckp->redirectport = ckalloc(sizeof(char *) * arr_size); + for (i = 0; i < arr_size; i++) { + json_t *val = json_array_get(arr_val, i); + + strncpy(redirecturl, json_string_value(val), INET6_ADDRSTRLEN - 1); + /* See that the url properly resolves */ + if (!url_from_serverurl(redirecturl, url, port)) + quit(1, "Invalid redirecturl entry %d %s", i, redirecturl); + ckp->redirecturl[i] = strdup(strsep(&redirecturl, ":")); + ckp->redirectport[i] = strdup(port); + } + ret = true; +out: + return ret; +} + + static void parse_config(ckpool_t *ckp) { json_t *json_conf, *arr_val; @@ -1310,7 +1390,10 @@ static void parse_config(ckpool_t *ckp) if (arr_size) parse_proxies(ckp, arr_val, arr_size); } - json_get_bool(&ckp->clientsvspeed, json_conf, "clientsvspeed"); + arr_val = json_object_get(json_conf, "redirecturl"); + if (arr_val) + parse_redirecturls(ckp, arr_val); + json_decref(json_conf); } @@ -1436,10 +1519,13 @@ static struct option long_options[] = { {"log-shares", no_argument, 0, 'L'}, {"loglevel", required_argument, 0, 'l'}, {"name", required_argument, 0, 'n'}, + {"node", no_argument, 0, 'N'}, {"passthrough", no_argument, 0, 'P'}, {"proxy", no_argument, 0, 'p'}, + {"redirector", no_argument, 0, 'R'}, {"ckdb-sockdir",required_argument, 0, 'S'}, {"sockdir", required_argument, 0, 's'}, + {"userproxy", no_argument, 0, 'u'}, {0, 0, 0, 0} }; #else @@ -1453,9 +1539,12 @@ static struct option long_options[] = { {"log-shares", no_argument, 0, 'L'}, {"loglevel", required_argument, 0, 'l'}, {"name", required_argument, 0, 'n'}, + {"node", no_argument, 0, 'N'}, {"passthrough", no_argument, 0, 'P'}, {"proxy", no_argument, 0, 'p'}, + {"redirector", no_argument, 0, 'R'}, {"sockdir", required_argument, 0, 's'}, + {"userproxy", no_argument, 0, 'u'}, {0, 0, 0, 0} }; #endif @@ -1499,7 +1588,7 @@ int main(int argc, char **argv) ckp.initial_args[ckp.args] = strdup(argv[ckp.args]); ckp.initial_args[ckp.args] = NULL; - while ((c = getopt_long(argc, argv, "Ac:Dd:g:HhkLl:n:PpS:s:", long_options, &i)) != -1) { + while ((c = getopt_long(argc, argv, "Ac:Dd:g:HhkLl:Nn:PpRS:s:u", long_options, &i)) != -1) { switch (c) { case 'A': ckp.standalone = true; @@ -1550,30 +1639,49 @@ int main(int argc, char **argv) LOG_EMERG, LOG_DEBUG, ckp.loglevel); } break; + case 'N': + if (ckp.proxy || ckp.redirector || ckp.userproxy || ckp.passthrough) + quit(1, "Cannot set another proxy type or redirector and node mode"); + ckp.standalone = ckp.proxy = ckp.passthrough = ckp.node = true; + break; case 'n': ckp.name = optarg; break; case 'P': - if (ckp.proxy) - quit(1, "Cannot set both proxy and passthrough mode"); + if (ckp.proxy || ckp.redirector || ckp.userproxy || ckp.node) + quit(1, "Cannot set another proxy type or redirector and passthrough mode"); ckp.standalone = ckp.proxy = ckp.passthrough = true; break; case 'p': - if (ckp.passthrough) - quit(1, "Cannot set both passthrough and proxy mode"); + if (ckp.passthrough || ckp.redirector || ckp.userproxy || ckp.node) + quit(1, "Cannot set another proxy type or redirector and proxy mode"); ckp.proxy = true; break; + case 'R': + if (ckp.proxy || ckp.passthrough || ckp.userproxy || ckp.node) + quit(1, "Cannot set a proxy type or passthrough and redirector modes"); + ckp.standalone = ckp.proxy = ckp.passthrough = ckp.redirector = true; + break; case 'S': ckp.ckdb_sockdir = strdup(optarg); break; case 's': ckp.socket_dir = strdup(optarg); break; + case 'u': + if (ckp.proxy || ckp.redirector || ckp.passthrough || ckp.node) + quit(1, "Cannot set both userproxy and another proxy type or redirector"); + ckp.userproxy = ckp.proxy = true; + break; } } if (!ckp.name) { - if (ckp.passthrough) + if (ckp.node) + ckp.name = "cknode"; + else if (ckp.redirector) + ckp.name = "ckredirector"; + else if (ckp.passthrough) ckp.name = "ckpassthrough"; else if (ckp.proxy) ckp.name = "ckproxy"; @@ -1629,7 +1737,7 @@ int main(int argc, char **argv) parse_config(&ckp); /* Set defaults if not found in config file */ - if (!ckp.btcds && !ckp.proxy) { + if (!ckp.btcds) { ckp.btcds = 1; ckp.btcdurl = ckzalloc(sizeof(char *)); ckp.btcdauth = ckzalloc(sizeof(char *)); @@ -1672,6 +1780,8 @@ int main(int argc, char **argv) ckp.serverurl = ckzalloc(sizeof(char *)); if (ckp.proxy && !ckp.proxies) quit(0, "No proxy entries found in config file %s", ckp.config); + if (ckp.redirector && !ckp.redirecturls) + quit(0, "No redirect entries found in config file %s", ckp.config); /* Create the log directory */ trail_slash(&ckp.logdir); @@ -1763,6 +1873,7 @@ int main(int argc, char **argv) launch_logger(&ckp.main); ckp.logfd = fileno(ckp.logfp); + ckp.ckpapi = create_ckmsgq(&ckp, "api", &ckpool_api); create_pthread(&ckp.pth_listener, listener, &ckp.main); /* Launch separate processes from here */ diff --git a/src/ckpool.h b/src/ckpool.h index 61108868..f2afd133 100644 --- a/src/ckpool.h +++ b/src/ckpool.h @@ -79,6 +79,7 @@ struct connsock { char *buf; int bufofs; int buflen; + ckpool_t *ckp; /* Semaphore used to serialise request/responses */ sem_t sem; }; @@ -160,6 +161,9 @@ struct ckpool_instance { /* How many clients maximum to accept before rejecting further */ int maxclients; + /* API message queue */ + ckmsgq_t *ckpapi; + /* Logger message queue NOTE: Unique per process */ ckmsgq_t *logger; /* Process instance data of parent/child processes */ @@ -176,18 +180,24 @@ struct ckpool_instance { pthread_t pth_listener; pthread_t pth_watchdog; + /* Are we running in node proxy mode */ + bool node; + /* Are we running in passthrough mode */ bool passthrough; + /* Are we a redirecting passthrough */ + bool redirector; + /* Are we running as a proxy */ bool proxy; - /* Do we prefer more proxy clients over support for >5TH clients */ - bool clientsvspeed; - /* Are we running without ckdb */ bool standalone; + /* Are we running in userproxy mode */ + bool userproxy; + /* Should we daemonise the ckpool process */ bool daemon; @@ -224,10 +234,53 @@ struct ckpool_instance { char **proxyauth; char **proxypass; + /* Passthrough redirect options */ + int redirecturls; + char **redirecturl; + char **redirectport; + /* Private data for each process */ void *data; }; +enum stratum_msgtype { + SM_RECONNECT = 0, + SM_DIFF, + SM_MSG, + SM_UPDATE, + SM_ERROR, + SM_SUBSCRIBE, + SM_SUBSCRIBERESULT, + SM_SHARE, + SM_SHARERESULT, + SM_AUTH, + SM_AUTHRESULT, + SM_TXNS, + SM_TXNSRESULT, + SM_PING, + SM_WORKINFO, + SM_NONE +}; + +static const char __maybe_unused *stratum_msgs[] = { + "reconnect", + "diff", + "message", + "update", + "error", + "subscribe", + "subscribe.result", + "share", + "share.result", + "auth", + "auth.result", + "txns", + "txns.result", + "ping", + "workinfo", + "" +}; + #ifdef USE_CKDB #define CKP_STANDALONE(CKP) ((CKP)->standalone == true) #else @@ -267,5 +320,22 @@ bool json_get_string(char **store, const json_t *val, const char *res); bool json_get_int64(int64_t *store, const json_t *val, const char *res); bool json_get_int(int *store, const json_t *val, const char *res); bool json_get_double(double *store, const json_t *val, const char *res); +bool json_get_bool(bool *store, const json_t *val, const char *res); +bool json_getdel_int(int *store, json_t *val, const char *res); +bool json_getdel_int64(int64_t *store, json_t *val, const char *res); + + +/* API Placeholders for future API implementation */ +typedef struct apimsg apimsg_t; + +struct apimsg { + char *buf; + int sockd; +}; + +static inline void ckpool_api(ckpool_t __maybe_unused *ckp, apimsg_t __maybe_unused *apimsg) {}; +static inline json_t *json_encode_errormsg(json_error_t __maybe_unused *err_val) { return NULL; }; +static inline json_t *json_errormsg(const char __maybe_unused *fmt, ...) { return NULL; }; +static inline void send_api_response(json_t __maybe_unused *val, const int __maybe_unused sockd) {}; #endif /* CKPOOL_H */ diff --git a/src/connector.c b/src/connector.c index 6ddd3f53..8dc31059 100644 --- a/src/connector.c +++ b/src/connector.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "ckpool.h" #include "libckpool.h" @@ -25,6 +26,8 @@ typedef struct client_instance client_instance_t; typedef struct sender_send sender_send_t; +typedef struct share share_t; +typedef struct redirect redirect_t; struct client_instance { /* For clients hashtable */ @@ -53,12 +56,20 @@ struct client_instance { int server; char buf[PAGESIZE]; - int bufofs; + unsigned long bufofs; /* Are we currently sending a blocked message from this client */ sender_send_t *sending; + + /* Is this the parent passthrough client */ bool passthrough; + /* Linked list of shares in redirector mode.*/ + share_t *shares; + + /* Has this client already been told to redirect */ + bool redirected; + /* Time this client started blocking, 0 when not blocked */ time_t blocked_time; }; @@ -73,6 +84,21 @@ struct sender_send { int ofs; }; +struct share { + share_t *next; + share_t *prev; + + time_t submitted; + int64_t id; +}; + +struct redirect { + UT_hash_handle hh; + char address_name[INET6_ADDRSTRLEN]; + int id; + int redirect_no; +}; + /* Private data for the connector */ struct connector_data { ckpool_t *ckp; @@ -115,6 +141,11 @@ struct connector_data { /* For protecting the pending sends list */ mutex_t sender_lock; pthread_cond_t sender_cond; + + /* Hash list of all redirected IP address in redirector mode */ + redirect_t *redirects; + /* What redirect we're currently up to */ + int redirect; }; typedef struct connector_data cdata_t; @@ -125,6 +156,13 @@ static void __inc_instance_ref(client_instance_t *client) client->ref++; } +static void inc_instance_ref(cdata_t *cdata, client_instance_t *client) +{ + ck_wlock(&cdata->lock); + __inc_instance_ref(client); + ck_wunlock(&cdata->lock); +} + /* Increase the reference count of instance */ static void __dec_instance_ref(client_instance_t *client) { @@ -261,25 +299,34 @@ static int accept_client(cdata_t *cdata, const int epfd, const uint64_t server) return 1; } +static int __drop_client(cdata_t *cdata, client_instance_t *client) +{ + int ret = -1; + + if (client->invalid) + goto out; + client->invalid = true; + ret = client->fd; + Close(client->fd); + epoll_ctl(cdata->epfd, EPOLL_CTL_DEL, ret, NULL); + HASH_DEL(cdata->clients, client); + DL_APPEND(cdata->dead_clients, client); + /* This is the reference to this client's presence in the + * epoll list. */ + __dec_instance_ref(client); + cdata->dead_generated++; +out: + return ret; +} + /* Client must hold a reference count */ static int drop_client(cdata_t *cdata, client_instance_t *client) { - int64_t client_id = 0; + int64_t client_id = client->id; int fd = -1; ck_wlock(&cdata->lock); - if (!client->invalid) { - client->invalid = true; - client_id = client->id; - fd = client->fd; - epoll_ctl(cdata->epfd, EPOLL_CTL_DEL, fd, NULL); - HASH_DEL(cdata->clients, client); - DL_APPEND(cdata->dead_clients, client); - /* This is the reference to this client's presence in the - * epoll list. */ - __dec_instance_ref(client); - cdata->dead_generated++; - } + fd = __drop_client(cdata, client); ck_wunlock(&cdata->lock); if (fd > -1) @@ -297,7 +344,7 @@ static void generator_drop_client(ckpool_t *ckp, const client_instance_t *client JSON_CPACK(val, "{si,sI:ss:si:ss:s[]}", "id", 42, "client_id", client->id, "address", client->address_name, "server", client->server, "method", "mining.term", "params"); - s = json_dumps(val, 0); + s = json_dumps(val, JSON_COMPACT); json_decref(val); send_proc(ckp->generator, s); free(s); @@ -351,8 +398,60 @@ static int invalidate_client(ckpool_t *ckp, cdata_t *cdata, client_instance_t *c return ret; } +static void drop_all_clients(cdata_t *cdata) +{ + client_instance_t *client, *tmp; + + ck_wlock(&cdata->lock); + HASH_ITER(hh, cdata->clients, client, tmp) { + __drop_client(cdata, client); + } + ck_wunlock(&cdata->lock); +} + static void send_client(cdata_t *cdata, int64_t id, char *buf); +/* Look for shares being submitted via a redirector and add them to a linked + * list for looking up the responses. */ +static void parse_redirector_share(client_instance_t *client, const char *msg, const json_t *val) +{ + share_t *share, *tmp; + time_t now; + int64_t id; + + if (!json_get_int64(&id, val, "id")) { + LOGNOTICE("Failed to find redirector share id"); + return; + } + /* If this is not a share, delete any matching ID messages so we + * don't falsely assume the client has had an accepted share based on + * a true result to a different message. */ + if (!strstr(msg, "mining.submit")) { + LOGDEBUG("Redirector client %"PRId64" non share message: %s", client->id, msg); + DL_FOREACH_SAFE(client->shares, share, tmp) { + if (share->id == id) { + DL_DELETE(client->shares, share); + dealloc(share); + } + } + return; + } + share = ckzalloc(sizeof(share_t)); + now = time(NULL); + share->submitted = now; + share->id = id; + DL_APPEND(client->shares, share); + LOGINFO("Redirector adding client %"PRId64" share id: %"PRId64, client->id, id); + + /* Age old shares. */ + DL_FOREACH_SAFE(client->shares, share, tmp) { + if (now > share->submitted + 120) { + DL_DELETE(client->shares, share); + dealloc(share); + } + } +} + /* Client is holding a reference count from being on the epoll list */ static void parse_client_msg(cdata_t *cdata, client_instance_t *client) { @@ -374,7 +473,7 @@ retry: if (ret < 1) { if (likely(errno == EAGAIN || errno == EWOULDBLOCK || !ret)) return; - LOGINFO("Client id %"PRId64" fd %d disconnected - recv fail with bufofs %d ret %d errno %d %s", + LOGINFO("Client id %"PRId64" fd %d disconnected - recv fail with bufofs %lu ret %d errno %d %s", client->id, client->fd, client->bufofs, ret, errno, ret && errno ? strerror(errno) : ""); invalidate_client(ckp, cdata, client); return; @@ -392,6 +491,7 @@ reparse: invalidate_client(ckp, cdata, client); return; } + memcpy(msg, client->buf, buflen); msg[buflen] = '\0'; client->bufofs -= buflen; @@ -405,29 +505,31 @@ reparse: invalidate_client(ckp, cdata, client); return; } else { - int64_t passthrough_id; char *s; if (client->passthrough) { - passthrough_id = json_integer_value(json_object_get(val, "client_id")); - json_object_del(val, "client_id"); + int64_t passthrough_id; + + json_getdel_int64(&passthrough_id, val, "client_id"); passthrough_id = (client->id << 32) | passthrough_id; json_object_set_new_nocheck(val, "client_id", json_integer(passthrough_id)); } else { + if (ckp->redirector && !client->redirected && strstr(msg, "mining.submit")) + parse_redirector_share(client, msg, val); json_object_set_new_nocheck(val, "client_id", json_integer(client->id)); json_object_set_new_nocheck(val, "address", json_string(client->address_name)); } json_object_set_new_nocheck(val, "server", json_integer(client->server)); - s = json_dumps(val, 0); + s = json_dumps(val, JSON_COMPACT); /* Do not send messages of clients we've already dropped. We * do this unlocked as the occasional false negative can be * filtered by the stratifier. */ if (likely(!client->invalid)) { + if (!ckp->passthrough || ckp->node) + send_proc(ckp->stratifier, s); if (ckp->passthrough) send_proc(ckp->generator, s); - else - send_proc(ckp->stratifier, s); } free(s); @@ -477,7 +579,7 @@ void *receiver(void *arg) for (i = 0; i < serverfds; i++) { /* The small values will be less than the first client ids */ event.data.u64 = i; - event.events = EPOLLIN; + event.events = EPOLLIN | EPOLLRDHUP; ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cdata->serverfd[i], &event); if (ret < 0) { LOGEMERG("FATAL: Failed to add epfd %d to epoll_ctl", epfd); @@ -660,6 +762,115 @@ static void *sender(void *arg) return NULL; } +static int add_redirect(ckpool_t *ckp, cdata_t *cdata, client_instance_t *client) +{ + redirect_t *redirect; + bool found; + + ck_wlock(&cdata->lock); + HASH_FIND_STR(cdata->redirects, client->address_name, redirect); + if (!redirect) { + redirect = ckzalloc(sizeof(redirect_t)); + strcpy(redirect->address_name, client->address_name); + redirect->redirect_no = cdata->redirect++; + if (cdata->redirect >= ckp->redirecturls) + cdata->redirect = 0; + HASH_ADD_STR(cdata->redirects, address_name, redirect); + found = false; + } else + found = true; + ck_wunlock(&cdata->lock); + + LOGNOTICE("Redirecting client %"PRId64" from %s IP %s to redirecturl %d", + client->id, found ? "matching" : "new", client->address_name, redirect->redirect_no); + return redirect->redirect_no; +} + +static void redirect_client(ckpool_t *ckp, client_instance_t *client) +{ + sender_send_t *sender_send; + cdata_t *cdata = ckp->data; + json_t *val; + char *buf; + int num; + + /* Set the redirected boool to only try redirecting them once */ + client->redirected = true; + + num = add_redirect(ckp, cdata, client); + JSON_CPACK(val, "{sosss[ssi]}", "id", json_null(), "method", "client.reconnect", + "params", ckp->redirecturl[num], ckp->redirectport[num], 0); + buf = json_dumps(val, JSON_EOL | JSON_COMPACT); + json_decref(val); + + sender_send = ckzalloc(sizeof(sender_send_t)); + sender_send->client = client; + sender_send->buf = buf; + sender_send->len = strlen(buf); + inc_instance_ref(cdata, client); + + mutex_lock(&cdata->sender_lock); + cdata->sends_generated++; + DL_APPEND(cdata->sender_sends, sender_send); + pthread_cond_signal(&cdata->sender_cond); + mutex_unlock(&cdata->sender_lock); +} + +/* Look for accepted shares in redirector mode to know we can redirect this + * client to a protected server. */ +static void test_redirector_shares(ckpool_t *ckp, client_instance_t *client, const char *buf) +{ + json_t *val = json_loads(buf, 0, NULL); + share_t *share, *found = NULL; + int64_t id; + + if (!val) { + LOGNOTICE("Invalid json response to client %"PRId64, client->id); + return; + } + if (!json_get_int64(&id, val, "id")) { + LOGINFO("Failed to find response id"); + goto out; + } + DL_FOREACH(client->shares, share) { + if (share->id == id) { + LOGDEBUG("Found matching share %"PRId64" in trs for client %"PRId64, + id, client->id); + DL_DELETE(client->shares, share); + found = share; + break; + } + } + if (found) { + bool result = false; + + dealloc(found); + if (!json_get_bool(&result, val, "result")) { + LOGINFO("Failed to find result in trs share"); + goto out; + } + if (!json_is_null(json_object_get(val, "error"))) { + LOGINFO("Got error for trs share"); + goto out; + } + if (!result) { + LOGDEBUG("Rejected trs share"); + goto out; + } + LOGNOTICE("Found accepted share for client %"PRId64" - redirecting", + client->id); + redirect_client(ckp, client); + + /* Clear the list now since we don't need it any more */ + DL_FOREACH_SAFE(client->shares, share, found) { + DL_DELETE(client->shares, share); + dealloc(share); + } + } +out: + json_decref(val); +} + /* Send a client by id a heap allocated buffer, allowing this function to * free the ram. */ static void send_client(cdata_t *cdata, const int64_t id, char *buf) @@ -680,6 +891,13 @@ static void send_client(cdata_t *cdata, const int64_t id, char *buf) return; } + if (unlikely(ckp->node && !id)) { + LOGDEBUG("Message for node: %s", buf); + send_proc(ckp->stratifier, buf); + free(buf); + return; + } + /* Grab a reference to this client until the sender_send has * completed processing. Is this a passthrough subclient ? */ if (id > 0xffffffffll) { @@ -710,6 +928,20 @@ static void send_client(cdata_t *cdata, const int64_t id, char *buf) free(buf); return; } + if (ckp->node) { + json_t *val = json_loads(buf, 0, NULL); + char *msg; + + json_object_set_new_nocheck(val, "client_id", json_integer(client->id)); + json_object_set_new_nocheck(val, "address", json_string(client->address_name)); + json_object_set_new_nocheck(val, "server", json_integer(client->server)); + msg = json_dumps(val, JSON_COMPACT); + json_decref(val); + send_proc(ckp->stratifier, msg); + free(msg); + } + if (ckp->redirector && !client->redirected) + test_redirector_shares(ckp, client, buf); } sender_send = ckzalloc(sizeof(sender_send_t)); @@ -724,6 +956,17 @@ static void send_client(cdata_t *cdata, const int64_t id, char *buf) mutex_unlock(&cdata->sender_lock); } +static bool client_exists(cdata_t *cdata, const int64_t id) +{ + client_instance_t *client; + + ck_rlock(&cdata->lock); + HASH_FIND_I64(cdata->clients, &id, client); + ck_runlock(&cdata->lock); + + return !!client; +} + static void passthrough_client(cdata_t *cdata, client_instance_t *client) { char *buf; @@ -742,7 +985,7 @@ static void process_client_msg(cdata_t *cdata, const char *buf) json_msg = json_loads(buf, 0, NULL); if (unlikely(!json_msg)) { - LOGWARNING("Invalid json message: %s", buf); + LOGWARNING("Invalid json message in process_client_msg: %s", buf); return; } @@ -753,7 +996,8 @@ static void process_client_msg(cdata_t *cdata, const char *buf) * upstream client_id instead of the passthrough's. */ if (client_id > 0xffffffffll) json_object_set_new_nocheck(json_msg, "client_id", json_integer(client_id & 0xffffffffll)); - msg = json_dumps(json_msg, JSON_EOL); + + msg = json_dumps(json_msg, JSON_EOL | JSON_COMPACT); send_client(cdata, client_id, msg); json_decref(json_msg); } @@ -874,6 +1118,17 @@ retry: dec_instance_ref(cdata, client); if (ret >= 0) LOGINFO("Connector dropped client id: %"PRId64, client_id); + } else if (cmdmatch(buf, "testclient")) { + ret = sscanf(buf, "testclient=%"PRId64, &client_id); + if (unlikely(ret < 0)) { + LOGDEBUG("Connector failed to parse testclient command: %s", buf); + goto retry; + } + client_id &= 0xffffffffll; + if (client_exists(cdata, client_id)) + goto retry; + LOGINFO("Connector detected non-existent client id: %"PRId64, client_id); + stratifier_drop_id(ckp, client_id); } else if (cmdmatch(buf, "ping")) { LOGDEBUG("Connector received ping request"); send_unix_msg(umsg->sockd, "pong"); @@ -883,7 +1138,8 @@ retry: } else if (cmdmatch(buf, "reject")) { LOGDEBUG("Connector received reject signal"); cdata->accept = false; - send_proc(ckp->stratifier, "dropall"); + if (ckp->passthrough) + drop_all_clients(cdata); } else if (cmdmatch(buf, "stats")) { char *msg; diff --git a/src/generator.c b/src/generator.c index 2d5f5de4..65b82da6 100644 --- a/src/generator.c +++ b/src/generator.c @@ -9,6 +9,7 @@ #include "config.h" +#include #include #include #include @@ -27,7 +28,7 @@ struct notify_instance { int id; char prevhash[68]; - char *jobid; + json_t *jobid; char *coinbase1; char *coinbase2; int coinb1len; @@ -43,13 +44,15 @@ struct notify_instance { typedef struct notify_instance notify_instance_t; +typedef struct proxy_instance proxy_instance_t; + struct share_msg { UT_hash_handle hh; - int64_t id; // Our own id for submitting upstream + int id; // Our own id for submitting upstream - int client_id; - int msg_id; // Stratum message id from client + int64_t client_id; time_t submit_time; + double diff; }; typedef struct share_msg share_msg_t; @@ -59,80 +62,107 @@ struct stratum_msg { struct stratum_msg *prev; json_t *json_msg; - int client_id; + int64_t client_id; }; typedef struct stratum_msg stratum_msg_t; struct pass_msg { + proxy_instance_t *proxy; connsock_t *cs; char *msg; }; typedef struct pass_msg pass_msg_t; - -typedef struct proxy_instance proxy_instance_t; +typedef struct cs_msg cs_msg_t; /* Per proxied pool instance data */ struct proxy_instance { - proxy_instance_t *next; - proxy_instance_t *prev; + UT_hash_handle hh; /* Proxy list */ + UT_hash_handle sh; /* Subproxy list */ + proxy_instance_t *next; /* For dead proxy list */ + proxy_instance_t *prev; /* For dead proxy list */ ckpool_t *ckp; - connsock_t *cs; - server_instance_t *si; + connsock_t cs; bool passthrough; - int id; /* Proxy server id */ + bool node; + int id; /* Proxy server id*/ + int subid; /* Subproxy id */ + int userid; /* User id if this proxy is bound to a user */ - const char *auth; - const char *pass; + char *url; + char *auth; + char *pass; char *enonce1; char *enonce1bin; int nonce1len; - char *sessionid; int nonce2len; tv_t last_message; double diff; + double diff_accepted; + double diff_rejected; + double total_accepted; /* Used only by parent proxy structures */ + double total_rejected; /* "" */ tv_t last_share; - int msg_id; /* Message id for sending stratum messages */ - bool no_sessionid; /* Doesn't support session id resume on subscribe */ bool no_params; /* Doesn't want any parameters on subscribe */ - bool notified; /* Received new template for work */ - bool diffed; /* Received new diff */ + bool global; /* Part of the global list of proxies */ + bool disabled; /* Subproxy no longer to be used */ bool reconnect; /* We need to drop and reconnect */ + bool reconnecting; /* Testing of parent in progress */ + int64_t recruit; /* No of recruiting requests in progress */ + bool alive; + bool authorised; - mutex_t notify_lock; - notify_instance_t *notify_instances; - notify_instance_t *current_notify; + /* Are we in the middle of a blocked write of this message? */ + cs_msg_t *sending; pthread_t pth_precv; - pthread_t pth_psend; - mutex_t psend_lock; - pthread_cond_t psend_cond; - - stratum_msg_t *psends; - - mutex_t share_lock; - share_msg_t *shares; - int64_t share_id; ckmsgq_t *passsends; // passthrough sends char_entry_t *recvd_lines; /* Linked list of unprocessed messages */ + + int epfd; /* Epoll fd used by the parent proxy */ + + mutex_t proxy_lock; /* Lock protecting hashlist of proxies */ + proxy_instance_t *parent; /* Parent proxy of subproxies */ + proxy_instance_t *subproxies; /* Hashlist of subproxies of this proxy */ + int64_t clients_per_proxy; /* Max number of clients of this proxy */ + int subproxy_count; /* Number of subproxies */ }; /* Private data for the generator */ struct generator_data { + ckpool_t *ckp; mutex_t lock; /* Lock protecting linked lists */ - proxy_instance_t *proxy_list; /* Linked list of all active proxies */ - int proxy_notify_id; /* Globally increasing notify id */ + proxy_instance_t *proxies; /* Hash list of all proxies */ + proxy_instance_t *dead_proxies; /* Disabled proxies */ + int proxies_generated; + int subproxies_generated; + + int proxy_notify_id; // Globally increasing notify id server_instance_t *si; /* Current server instance */ - ckmsgq_t *srvchk; // Server check message queue + pthread_t pth_uprecv; // User proxy receive thread + pthread_t pth_psend; // Combined proxy send thread + + mutex_t psend_lock; // Lock associated with conditional below + pthread_cond_t psend_cond; + + stratum_msg_t *psends; + int psends_generated; + + mutex_t notify_lock; + notify_instance_t *notify_instances; + + mutex_t share_lock; + share_msg_t *shares; + int64_t share_id; }; typedef struct generator_data gdata_t; @@ -181,7 +211,7 @@ static bool server_alive(ckpool_t *ckp, server_instance_t *si, bool pinging) goto out; } clear_gbtbase(gbt); - if (!validate_address(cs, ckp->btcaddress)) { + if (!ckp->node && !validate_address(cs, ckp->btcaddress)) { LOGWARNING("Invalid btcaddress: %s !", ckp->btcaddress); goto out; } @@ -254,20 +284,30 @@ static void kill_server(server_instance_t *si) dealloc(si->data); } +static void clear_unix_msg(unix_msg_t **umsg) +{ + if (*umsg) { + Close((*umsg)->sockd); + free((*umsg)->buf); + free(*umsg); + *umsg = NULL; + } +} + static int gen_loop(proc_instance_t *pi) { server_instance_t *si = NULL, *old_si; - int sockd = -1, ret = 0, selret; - unixsock_t *us = &pi->us; + unix_msg_t *umsg = NULL; ckpool_t *ckp = pi->ckp; bool started = false; char *buf = NULL; connsock_t *cs; gbtbase_t *gbt; char hash[68]; + int ret = 0; reconnect: - Close(sockd); + clear_unix_msg(&umsg); old_si = si; si = live_server(ckp); if (!si) @@ -285,34 +325,23 @@ reconnect: LOGWARNING("Failed over to bitcoind: %s:%s", cs->url, cs->port); retry: - Close(sockd); + clear_unix_msg(&umsg); do { - selret = wait_read_select(us->sockd, 5); - if (!selret && !ping_main(ckp)) { + umsg = get_unix_msg(pi); + if (unlikely(!umsg &&!ping_main(ckp))) { LOGEMERG("Generator failed to ping main process, exiting"); ret = 1; goto out; } - } while (selret < 1); + } while (!umsg); if (unlikely(!si->alive)) { LOGWARNING("%s:%s Bitcoind socket invalidated, will attempt failover", cs->url, cs->port); goto reconnect; } - sockd = accept(us->sockd, NULL, NULL); - if (sockd < 0) { - LOGEMERG("Failed to accept on generator socket"); - ret = 1; - goto out; - } - dealloc(buf); - buf = recv_unix_msg(sockd); - if (!buf) { - LOGWARNING("Failed to get message in gen_loop"); - goto retry; - } + buf = umsg->buf; LOGDEBUG("Generator received request: %s", buf); if (cmdmatch(buf, "shutdown")) { ret = 0; @@ -322,44 +351,44 @@ retry: if (!gen_gbtbase(cs, gbt)) { LOGWARNING("Failed to get block template from %s:%s", cs->url, cs->port); - send_unix_msg(sockd, "Failed"); si->alive = false; + send_unix_msg(umsg->sockd, "Failed"); goto reconnect; } else { char *s = json_dumps(gbt->json, JSON_NO_UTF8); - send_unix_msg(sockd, s); + send_unix_msg(umsg->sockd, s); free(s); clear_gbtbase(gbt); } } else if (cmdmatch(buf, "getbest")) { if (si->notify) - send_unix_msg(sockd, "notify"); + send_unix_msg(umsg->sockd, "notify"); else if (!get_bestblockhash(cs, hash)) { LOGINFO("No best block hash support from %s:%s", cs->url, cs->port); si->alive = false; - send_unix_msg(sockd, "failed"); + send_unix_msg(umsg->sockd, "failed"); } else { - send_unix_msg(sockd, hash); + send_unix_msg(umsg->sockd, hash); } } else if (cmdmatch(buf, "getlast")) { int height; if (si->notify) - send_unix_msg(sockd, "notify"); + send_unix_msg(umsg->sockd, "notify"); else if ((height = get_blockcount(cs)) == -1) { si->alive = false; - send_unix_msg(sockd, "failed"); + send_unix_msg(umsg->sockd, "failed"); goto reconnect; } else { LOGDEBUG("Height: %d", height); if (!get_blockhash(cs, height, hash)) { si->alive = false; - send_unix_msg(sockd, "failed"); + send_unix_msg(umsg->sockd, "failed"); goto reconnect; } else { - send_unix_msg(sockd, hash); + send_unix_msg(umsg->sockd, hash); LOGDEBUG("Hash: %s", hash); } } @@ -374,26 +403,26 @@ retry: send_proc(ckp->stratifier, blockmsg); } else if (cmdmatch(buf, "checkaddr:")) { if (validate_address(cs, buf + 10)) - send_unix_msg(sockd, "true"); + send_unix_msg(umsg->sockd, "true"); else - send_unix_msg(sockd, "false"); + send_unix_msg(umsg->sockd, "false"); } else if (cmdmatch(buf, "reconnect")) { goto reconnect; } else if (cmdmatch(buf, "loglevel")) { sscanf(buf, "loglevel=%d", &ckp->loglevel); } else if (cmdmatch(buf, "ping")) { LOGDEBUG("Generator received ping request"); - send_unix_msg(sockd, "pong"); + send_unix_msg(umsg->sockd, "pong"); } goto retry; out: kill_server(si); - dealloc(buf); return ret; } -static bool send_json_msg(connsock_t *cs, json_t *json_msg) +/* This is for blocking sends of json messages */ +static bool send_json_msg(connsock_t *cs, const json_t *json_msg) { int len, sent; char *s; @@ -404,14 +433,18 @@ static bool send_json_msg(connsock_t *cs, json_t *json_msg) sent = write_socket(cs->fd, s, len); dealloc(s); if (sent != len) { - LOGWARNING("Failed to send %d bytes sent %d in send_json_msg", len, sent); + LOGNOTICE("Failed to send %d bytes sent %d in send_json_msg", len, sent); return false; } return true; } -static bool connect_proxy(connsock_t *cs) +static bool connect_proxy(ckpool_t *ckp, connsock_t *cs, proxy_instance_t *proxy) { + if (cs->fd > 0) { + epoll_ctl(proxy->epfd, EPOLL_CTL_DEL, cs->fd, NULL); + Close(cs->fd); + } cs->fd = connect_socket(cs->url, cs->port); if (cs->fd < 0) { LOGINFO("Failed to connect socket to %s:%s in connect_proxy", @@ -419,6 +452,18 @@ static bool connect_proxy(connsock_t *cs) return false; } keep_sockalive(cs->fd); + if (!ckp->passthrough) { + struct epoll_event event; + + event.events = EPOLLIN | EPOLLRDHUP; + event.data.ptr = proxy; + /* Add this connsock_t to the epoll list */ + if (unlikely(epoll_ctl(proxy->epfd, EPOLL_CTL_ADD, cs->fd, &event) == -1)) { + LOGERR("Failed to add fd %d to epfd %d to epoll_ctl in proxy_alive", + cs->fd, proxy->epfd); + return false; + } + } return true; } @@ -442,7 +487,7 @@ static json_t *json_result(json_t *val) else ss = strdup("(unknown reason)"); - LOGWARNING("JSON-RPC decode failed: %s", ss); + LOGNOTICE("JSON-RPC decode of json_result failed: %s", ss); free(ss); } return res_val; @@ -549,10 +594,18 @@ out: return buf; } +static inline bool parent_proxy(const proxy_instance_t *proxy) +{ + return (proxy->parent == proxy); +} + +static void recruit_subproxies(proxy_instance_t *proxi, const int recruits); + static bool parse_subscribe(connsock_t *cs, proxy_instance_t *proxi) { json_t *val = NULL, *res_val, *notify_val, *tmp; bool parsed, ret = false; + proxy_instance_t *parent; int retries = 0, size; const char *string; char *buf, *old; @@ -560,8 +613,8 @@ static bool parse_subscribe(connsock_t *cs, proxy_instance_t *proxi) retry: parsed = true; if (!(buf = new_proxy_line(cs))) { - LOGNOTICE("Proxy %d:%s failed to receive line in parse_subscribe", - proxi->id, proxi->si->url); + LOGNOTICE("Proxy %d:%d %s failed to receive line in parse_subscribe", + proxi->id, proxi->subid, proxi->url); goto out; } LOGDEBUG("parse_subscribe received %s", buf); @@ -593,21 +646,11 @@ retry: buf = NULL; goto retry; } - LOGNOTICE("Proxy %d:%s failed to parse subscribe response in parse_subscribe", - proxi->id, proxi->si->url); + LOGNOTICE("Proxy %d:%d %s failed to parse subscribe response in parse_subscribe", + proxi->id, proxi->subid, proxi->url); goto out; } - /* Free up old data in place if we are re-subscribing */ - old = proxi->sessionid; - proxi->sessionid = NULL; - if (!proxi->no_params && !proxi->no_sessionid && json_array_size(notify_val) > 1) { - /* Copy the session id if one exists. */ - string = json_string_value(json_array_get(notify_val, 1)); - if (string) - proxi->sessionid = strdup(string); - } - free(old); tmp = json_array_get(res_val, 1); if (!tmp || !json_is_string(tmp)) { LOGWARNING("Failed to parse enonce1 in parse_subscribe"); @@ -636,18 +679,27 @@ retry: LOGWARNING("Invalid nonce2len %d in parse_subscribe", size); goto out; } - if (size == 3 || (size == 4 && proxi->ckp->clientsvspeed)) - LOGWARNING("Proxy %d:%s Nonce2 length %d means proxied clients can't be >5TH each", - proxi->id, proxi->si->url, size); - else if (size < 3) { - LOGWARNING("Proxy %d:%s Nonce2 length %d too small to be able to proxy", - proxi->id, proxi->si->url, size); - goto out; + if (size < 3) { + if (!proxi->subid) { + LOGWARNING("Proxy %d %s Nonce2 length %d too small for fast miners", + proxi->id, proxi->url, size); + } else { + LOGNOTICE("Proxy %d:%d Nonce2 length %d too small for fast miners", + proxi->id, proxi->subid, size); + } } proxi->nonce2len = size; + proxi->clients_per_proxy = 1ll << ((size - 3) * 8); + parent = proxi->parent; - LOGINFO("Found notify with enonce %s nonce2len %d", proxi->enonce1, - proxi->nonce2len); + mutex_lock(&parent->proxy_lock); + parent->recruit -= proxi->clients_per_proxy; + if (parent->recruit < 0) + parent->recruit = 0; + mutex_unlock(&parent->proxy_lock); + + LOGNOTICE("Found notify for new proxy %d:%d with enonce %s nonce2len %d", proxi->id, + proxi->subid, proxi->enonce1, proxi->nonce2len); ret = true; out: @@ -657,36 +709,31 @@ out: return ret; } -static bool subscribe_stratum(connsock_t *cs, proxy_instance_t *proxi) +/* cs semaphore must be held */ +static bool subscribe_stratum(ckpool_t *ckp, connsock_t *cs, proxy_instance_t *proxi) { bool ret = false; json_t *req; retry: - /* Attempt to reconnect if the pool supports resuming */ - if (proxi->sessionid) { - JSON_CPACK(req, "{s:i,s:s,s:[s,s]}", - "id", proxi->msg_id++, - "method", "mining.subscribe", - "params", PACKAGE"/"VERSION, proxi->sessionid); - /* Then attempt to connect with just the client description */ - } else if (!proxi->no_params) { + /* Attempt to connect with the client description g*/ + if (!proxi->no_params) { JSON_CPACK(req, "{s:i,s:s,s:[s]}", - "id", proxi->msg_id++, + "id", 0, "method", "mining.subscribe", "params", PACKAGE"/"VERSION); /* Then try without any parameters */ } else { JSON_CPACK(req, "{s:i,s:s,s:[]}", - "id", proxi->msg_id++, + "id", 0, "method", "mining.subscribe", "params"); } ret = send_json_msg(cs, req); json_decref(req); if (!ret) { - LOGNOTICE("Proxy %d:%s failed to send message in subscribe_stratum", - proxi->id, proxi->si->url); + LOGNOTICE("Proxy %d:%d %s failed to send message in subscribe_stratum", + proxi->id, proxi->subid, proxi->url); goto out; } ret = parse_subscribe(cs, proxi); @@ -694,41 +741,37 @@ retry: goto out; if (proxi->no_params) { - LOGNOTICE("Proxy %d:%s failed all subscription options in subscribe_stratum", - proxi->id, proxi->si->url); + LOGNOTICE("Proxy %d:%d %s failed all subscription options in subscribe_stratum", + proxi->id, proxi->subid, proxi->url); goto out; } - if (proxi->sessionid) { - LOGINFO("Proxy %d:%s failed sessionid reconnect in subscribe_stratum, retrying without", - proxi->id, proxi->si->url); - proxi->no_sessionid = true; - dealloc(proxi->sessionid); - } else { - LOGINFO("Proxy %d:%s failed connecting with parameters in subscribe_stratum, retrying without", - proxi->id, proxi->si->url); - proxi->no_params = true; - } - ret = connect_proxy(cs); + LOGINFO("Proxy %d:%d %s failed connecting with parameters in subscribe_stratum, retrying without", + proxi->id, proxi->subid, proxi->url); + proxi->no_params = true; + ret = connect_proxy(ckp, cs, proxi); if (!ret) { - LOGNOTICE("Proxy %d:%s failed to reconnect in subscribe_stratum", - proxi->id, proxi->si->url); + LOGNOTICE("Proxy %d:%d %s failed to reconnect in subscribe_stratum", + proxi->id, proxi->subid, proxi->url); goto out; } goto retry; out: - if (!ret) + if (!ret && cs->fd > 0) { + epoll_ctl(proxi->epfd, EPOLL_CTL_DEL, cs->fd, NULL); Close(cs->fd); + } return ret; } +/* cs semaphore must be held */ static bool passthrough_stratum(connsock_t *cs, proxy_instance_t *proxi) { json_t *req, *val = NULL, *res_val, *err_val; bool res, ret = false; float timeout = 10; - JSON_CPACK(req, "{s:s,s:[s]}", + JSON_CPACK(req, "{ss,s[s]}", "method", "mining.passthrough", "params", PACKAGE"/"VERSION); res = send_json_msg(cs, req); @@ -763,22 +806,73 @@ out: return ret; } -static bool parse_notify(proxy_instance_t *proxi, json_t *val) +/* cs semaphore must be held */ +static bool node_stratum(connsock_t *cs, proxy_instance_t *proxi) +{ + json_t *req, *val = NULL, *res_val, *err_val; + bool res, ret = false; + float timeout = 10; + + JSON_CPACK(req, "{ss,s[s]}", + "method", "mining.node", + "params", PACKAGE"/"VERSION); + + res = send_json_msg(cs, req); + json_decref(req); + if (!res) { + LOGWARNING("Failed to send message in node_stratum"); + goto out; + } + if (read_socket_line(cs, &timeout) < 1) { + LOGWARNING("Failed to receive line in node_stratum"); + goto out; + } + /* Ignore err_val here since we should always get a result from an + * upstream server */ + val = json_msg_result(cs->buf, &res_val, &err_val); + if (!val || !res_val) { + LOGWARNING("Failed to get a json result in node_stratum, got: %s", + cs->buf); + goto out; + } + ret = json_is_true(res_val); + if (!ret) { + LOGWARNING("Denied node setup for stratum"); + goto out; + } + proxi->node = true; +out: + if (val) + json_decref(val); + if (!ret) + Close(cs->fd); + return ret; +} + +static void send_notify(ckpool_t *ckp, proxy_instance_t *proxi, notify_instance_t *ni); + +static void reconnect_generator(const ckpool_t *ckp) +{ + send_proc(ckp->generator, "reconnect"); +} + +static bool parse_notify(ckpool_t *ckp, proxy_instance_t *proxi, json_t *val) { const char *prev_hash, *bbversion, *nbit, *ntime; - char *job_id, *coinbase1, *coinbase2; gdata_t *gdata = proxi->ckp->data; + char *coinbase1, *coinbase2; + const char *jobidbuf; bool clean, ret = false; notify_instance_t *ni; + json_t *arr, *job_id; int merkles, i; - json_t *arr; arr = json_array_get(val, 4); if (!arr || !json_is_array(arr)) goto out; merkles = json_array_size(arr); - job_id = json_array_string(val, 0); + job_id = json_copy(json_array_get(val, 0)); prev_hash = __json_array_string(val, 1); coinbase1 = json_array_string(val, 2); coinbase2 = json_array_string(val, 3); @@ -788,7 +882,7 @@ static bool parse_notify(proxy_instance_t *proxi, json_t *val) clean = json_is_true(json_array_get(val, 8)); if (!job_id || !prev_hash || !coinbase1 || !coinbase2 || !bbversion || !nbit || !ntime) { if (job_id) - free(job_id); + json_decref(job_id); if (coinbase1) free(coinbase1); if (coinbase2) @@ -796,10 +890,11 @@ static bool parse_notify(proxy_instance_t *proxi, json_t *val) goto out; } - LOGDEBUG("New notify"); + LOGDEBUG("Received new notify from proxy %d:%d", proxi->id, proxi->subid); ni = ckzalloc(sizeof(notify_instance_t)); ni->jobid = job_id; - LOGDEBUG("Job ID %s", job_id); + jobidbuf = json_string_value(job_id); + LOGDEBUG("JobID %s", jobidbuf); ni->coinbase1 = coinbase1; LOGDEBUG("Coinbase1 %s", coinbase1); ni->coinb1len = strlen(coinbase1) / 2; @@ -826,12 +921,13 @@ static bool parse_notify(proxy_instance_t *proxi, json_t *val) ret = true; ni->notify_time = time(NULL); - mutex_lock(&proxi->notify_lock); + /* Add the notify instance to the parent proxy list, not the subproxy */ + mutex_lock(&gdata->notify_lock); ni->id = gdata->proxy_notify_id++; - HASH_ADD_INT(proxi->notify_instances, id, ni); - proxi->current_notify = ni; - mutex_unlock(&proxi->notify_lock); + HASH_ADD_INT(gdata->notify_instances, id, ni); + mutex_unlock(&gdata->notify_lock); + send_notify(ckp, proxi, ni); out: return ret; } @@ -843,19 +939,17 @@ static bool parse_diff(proxy_instance_t *proxi, json_t *val) if (diff == 0 || diff == proxi->diff) return true; proxi->diff = diff; - proxi->diffed = true; return true; } static bool send_version(proxy_instance_t *proxi, json_t *val) { json_t *json_msg, *id_val = json_object_dup(val, "id"); - connsock_t *cs = proxi->cs; bool ret; JSON_CPACK(json_msg, "{sossso}", "id", id_val, "result", PACKAGE"/"VERSION, "error", json_null()); - ret = send_json_msg(cs, json_msg); + ret = send_json_msg(&proxi->cs, json_msg); json_decref(json_msg); return ret; } @@ -876,24 +970,131 @@ static bool show_message(json_t *val) static bool send_pong(proxy_instance_t *proxi, json_t *val) { json_t *json_msg, *id_val = json_object_dup(val, "id"); - connsock_t *cs = proxi->cs; bool ret; JSON_CPACK(json_msg, "{sossso}", "id", id_val, "result", "pong", "error", json_null()); - ret = send_json_msg(cs, json_msg); + ret = send_json_msg(&proxi->cs, json_msg); json_decref(json_msg); return ret; } -static bool parse_reconnect(proxy_instance_t *proxi, json_t *val) +static void prepare_proxy(proxy_instance_t *proxi); + +/* Creates a duplicate instance or proxi to be used as a subproxy, ignoring + * fields we don't use in the subproxy. */ +static proxy_instance_t *create_subproxy(ckpool_t *ckp, gdata_t *gdata, proxy_instance_t *proxi, + const char *url) { - server_instance_t *newsi, *si = proxi->si; - proxy_instance_t *newproxi; - ckpool_t *ckp = proxi->ckp; + proxy_instance_t *subproxy; + + mutex_lock(&gdata->lock); + if (gdata->dead_proxies) { + /* Recycle an old proxy instance if one exists */ + subproxy = gdata->dead_proxies; + DL_DELETE(gdata->dead_proxies, subproxy); + subproxy->disabled = false; + } else { + gdata->subproxies_generated++; + subproxy = ckzalloc(sizeof(proxy_instance_t)); + } + mutex_unlock(&gdata->lock); + + subproxy->cs.ckp = subproxy->ckp = ckp; + + mutex_lock(&proxi->proxy_lock); + subproxy->subid = ++proxi->subproxy_count; + mutex_unlock(&proxi->proxy_lock); + + subproxy->id = proxi->id; + subproxy->userid = proxi->userid; + subproxy->global = proxi->global; + subproxy->url = strdup(url); + subproxy->auth = strdup(proxi->auth); + subproxy->pass = strdup(proxi->pass); + subproxy->parent = proxi; + subproxy->epfd = proxi->epfd; + cksem_init(&subproxy->cs.sem); + cksem_post(&subproxy->cs.sem); + return subproxy; +} + +static void add_subproxy(proxy_instance_t *proxi, proxy_instance_t *subproxy) +{ + mutex_lock(&proxi->proxy_lock); + HASH_ADD(sh, proxi->subproxies, subid, sizeof(int), subproxy); + mutex_unlock(&proxi->proxy_lock); +} + +static proxy_instance_t *__subproxy_by_id(proxy_instance_t *proxy, const int subid) +{ + proxy_instance_t *subproxy; + + HASH_FIND(sh, proxy->subproxies, &subid, sizeof(int), subproxy); + return subproxy; +} + +/* Add to the dead list to be recycled if possible */ +static void store_proxy(gdata_t *gdata, proxy_instance_t *proxy) +{ + LOGINFO("Recycling data from proxy %d:%d", proxy->id, proxy->subid); + + mutex_lock(&gdata->lock); + dealloc(proxy->enonce1); + dealloc(proxy->url); + dealloc(proxy->auth); + dealloc(proxy->pass); + DL_APPEND(gdata->dead_proxies, proxy); + mutex_unlock(&gdata->lock); +} + +static void send_stratifier_deadproxy(ckpool_t *ckp, const int id, const int subid) +{ + char buf[256]; + + if (ckp->passthrough) + return; + sprintf(buf, "deadproxy=%d:%d", id, subid); + send_proc(ckp->stratifier, buf); +} + +/* Remove the subproxy from the proxi list and put it on the dead list. + * Further use of the subproxy pointer may point to a new proxy but will not + * dereference. This will only disable subproxies so parent proxies need to + * have their disabled bool set manually. */ +static void disable_subproxy(gdata_t *gdata, proxy_instance_t *proxi, proxy_instance_t *subproxy) +{ + subproxy->alive = false; + send_stratifier_deadproxy(gdata->ckp, subproxy->id, subproxy->subid); + if (subproxy->cs.fd > 0) { + epoll_ctl(proxi->epfd, EPOLL_CTL_DEL, subproxy->cs.fd, NULL); + Close(subproxy->cs.fd); + } + if (parent_proxy(subproxy)) + return; + + subproxy->disabled = true; + + mutex_lock(&proxi->proxy_lock); + /* Make sure subproxy is still in the list */ + subproxy = __subproxy_by_id(proxi, subproxy->subid); + if (likely(subproxy)) + HASH_DELETE(sh, proxi->subproxies, subproxy); + mutex_unlock(&proxi->proxy_lock); + + if (subproxy) { + send_stratifier_deadproxy(gdata->ckp, subproxy->id, subproxy->subid); + store_proxy(gdata, subproxy); + } +} + +static bool parse_reconnect(proxy_instance_t *proxy, json_t *val) +{ + bool sameurl = false, ret = false; + ckpool_t *ckp = proxy->ckp; gdata_t *gdata = ckp->data; + proxy_instance_t *parent; const char *new_url; - bool ret = false; int new_port; char *url; @@ -911,10 +1112,10 @@ static bool parse_reconnect(proxy_instance_t *proxi, json_t *val) char *dot_pool, *dot_reconnect; int len; - dot_pool = strchr(si->url, '.'); + dot_pool = strchr(proxy->url, '.'); if (!dot_pool) { LOGWARNING("Denied stratum reconnect request from server without domain %s", - si->url); + proxy->url); goto out; } dot_reconnect = strchr(new_url, '.'); @@ -926,50 +1127,108 @@ static bool parse_reconnect(proxy_instance_t *proxi, json_t *val) len = strlen(dot_reconnect); if (strncmp(dot_pool, dot_reconnect, len)) { LOGWARNING("Denied stratum reconnect request from %s to non-matching domain %s", - si->url, new_url); + proxy->url, new_url); goto out; } ASPRINTF(&url, "%s:%d", new_url, new_port); - } else - url = strdup(si->url); + } else { + url = strdup(proxy->url); + sameurl = true; + } LOGINFO("Processing reconnect request to %s", url); ret = true; - newsi = ckzalloc(sizeof(server_instance_t)); + parent = proxy->parent; + disable_subproxy(gdata, parent, proxy); + if (parent != proxy) { + /* If this is a subproxy we only need to create a new one if + * the url has changed. Otherwise automated recruiting will + * take care of creating one if needed. */ + if (!sameurl) + create_subproxy(ckp, gdata, parent, url); + goto out; + } - mutex_lock(&gdata->lock); - newsi->id = si->id; /* Inherit the old connection's id */ - si->id = ckp->proxies++; /* Give the old connection the lowest id */ - ckp->servers = realloc(ckp->servers, sizeof(server_instance_t *) * ckp->proxies); - ckp->servers[newsi->id] = newsi; - newsi->url = url; - newsi->auth = strdup(si->auth); - newsi->pass = strdup(si->pass); - proxi->reconnect = true; - - newproxi = ckzalloc(sizeof(proxy_instance_t)); - newsi->data = newproxi; - newproxi->auth = newsi->auth; - newproxi->pass = newsi->pass; - newproxi->si = newsi; - newproxi->ckp = ckp; - newproxi->cs = &newsi->cs; - mutex_unlock(&gdata->lock); + proxy->reconnect = true; + LOGWARNING("Proxy %d:%s reconnect issue to %s, dropping existing connection", + proxy->id, proxy->url, url); + if (!sameurl) { + char *oldurl = proxy->url; + + proxy->url = url; + free(oldurl); + } out: return ret; } -static bool parse_method(proxy_instance_t *proxi, const char *msg) +static void send_diff(ckpool_t *ckp, proxy_instance_t *proxi) +{ + proxy_instance_t *proxy = proxi->parent; + json_t *json_msg; + char *msg, *buf; + + /* Not set yet */ + if (!proxi->diff) + return; + + JSON_CPACK(json_msg, "{sIsisf}", + "proxy", proxy->id, + "subproxy", proxi->subid, + "diff", proxi->diff); + msg = json_dumps(json_msg, JSON_NO_UTF8); + json_decref(json_msg); + ASPRINTF(&buf, "diff=%s", msg); + free(msg); + send_proc(ckp->stratifier, buf); + free(buf); +} + +static void send_notify(ckpool_t *ckp, proxy_instance_t *proxi, notify_instance_t *ni) +{ + proxy_instance_t *proxy = proxi->parent; + json_t *json_msg, *merkle_arr; + char *msg, *buf; + int i; + + merkle_arr = json_array(); + + for (i = 0; i < ni->merkles; i++) + json_array_append_new(merkle_arr, json_string(&ni->merklehash[i][0])); + /* Use our own jobid instead of the server's one for easy lookup */ + JSON_CPACK(json_msg, "{sIsisisssisssssosssssssb}", + "proxy", proxy->id, "subproxy", proxi->subid, + "jobid", ni->id, "prevhash", ni->prevhash, "coinb1len", ni->coinb1len, + "coinbase1", ni->coinbase1, "coinbase2", ni->coinbase2, + "merklehash", merkle_arr, "bbversion", ni->bbversion, + "nbit", ni->nbit, "ntime", ni->ntime, + "clean", ni->clean); + + msg = json_dumps(json_msg, JSON_NO_UTF8); + json_decref(json_msg); + ASPRINTF(&buf, "notify=%s", msg); + free(msg); + send_proc(ckp->stratifier, buf); + free(buf); + + /* Send diff now as stratifier will not accept diff till it has a + * valid workbase */ + send_diff(ckp, proxi); +} + +static bool parse_method(ckpool_t *ckp, proxy_instance_t *proxi, const char *msg) { json_t *val = NULL, *method, *err_val, *params; json_error_t err; bool ret = false; const char *buf; + if (!msg) + goto out; memset(&err, 0, sizeof(err)); val = json_loads(msg, 0, &err); if (!val) { - LOGWARNING("JSON decode failed(%d): %s", err.line, err.text); + LOGWARNING("JSON decode of msg %s failed(%d): %s", msg, err.line, err.text); goto out; } @@ -1004,16 +1263,16 @@ static bool parse_method(proxy_instance_t *proxi, const char *msg) goto out; } + LOGDEBUG("Proxy %d:%d received method %s", proxi->id, proxi->subid, buf); if (cmdmatch(buf, "mining.notify")) { - if (parse_notify(proxi, params)) - proxi->notified = ret = true; - else - proxi->notified = ret = false; + ret = parse_notify(ckp, proxi, params); goto out; } if (cmdmatch(buf, "mining.set_difficulty")) { ret = parse_diff(proxi, params); + if (likely(ret)) + send_diff(ckp, proxi); goto out; } @@ -1042,22 +1301,26 @@ out: return ret; } -static bool auth_stratum(connsock_t *cs, proxy_instance_t *proxi) +/* cs semaphore must be held */ +static bool auth_stratum(ckpool_t *ckp, connsock_t *cs, proxy_instance_t *proxi) { json_t *val = NULL, *res_val, *req, *err_val; char *buf = NULL; bool ret; JSON_CPACK(req, "{s:i,s:s,s:[s,s]}", - "id", proxi->msg_id++, + "id", 42, "method", "mining.authorize", "params", proxi->auth, proxi->pass); ret = send_json_msg(cs, req); json_decref(req); if (!ret) { - LOGNOTICE("Proxy %d:%s failed to send message in auth_stratum", - proxi->id, proxi->si->url); - Close(cs->fd); + LOGNOTICE("Proxy %d:%d %s failed to send message in auth_stratum", + proxi->id, proxi->subid, proxi->url); + if (cs->fd > 0) { + epoll_ctl(proxi->epfd, EPOLL_CTL_DEL, cs->fd, NULL); + Close(cs->fd); + } goto out; } @@ -1067,38 +1330,43 @@ static bool auth_stratum(connsock_t *cs, proxy_instance_t *proxi) free(buf); buf = next_proxy_line(cs, proxi); if (!buf) { - LOGNOTICE("Proxy %d:%s failed to receive line in auth_stratum", - proxi->id, proxi->si->url); + LOGNOTICE("Proxy %d:%d %s failed to receive line in auth_stratum", + proxi->id, proxi->subid, proxi->url); ret = false; goto out; } - ret = parse_method(proxi, buf); + ret = parse_method(ckp, proxi, buf); } while (ret); val = json_msg_result(buf, &res_val, &err_val); if (!val) { - LOGWARNING("Proxy %d:%s failed to get a json result in auth_stratum, got: %s", - proxi->id, proxi->si->url, buf); + if (proxi->global) { + LOGWARNING("Proxy %d:%d %s failed to get a json result in auth_stratum, got: %s", + proxi->id, proxi->subid, proxi->url, buf); + } else { + LOGNOTICE("Proxy %d:%d %s failed to get a json result in auth_stratum, got: %s", + proxi->id, proxi->subid, proxi->url, buf); + } goto out; } if (err_val && !json_is_null(err_val)) { - LOGWARNING("Proxy %d:%s failed to authorise in auth_stratum due to err_val, got: %s", - proxi->id, proxi->si->url, buf); + LOGWARNING("Proxy %d:%d %s failed to authorise in auth_stratum due to err_val, got: %s", + proxi->id, proxi->subid, proxi->url, buf); goto out; } if (res_val) { ret = json_is_true(res_val); if (!ret) { - LOGWARNING("Proxy %d:%s failed to authorise in auth_stratum", - proxi->id, proxi->si->url); + LOGWARNING("Proxy %d:%d %s failed to authorise in auth_stratum, got: %s", + proxi->id, proxi->subid, proxi->url, buf); goto out; } } else { /* No result and no error but successful val means auth success */ ret = true; } - LOGINFO("Proxy %d:%s auth success in auth_stratum", proxi->id, proxi->si->url); + LOGINFO("Proxy %d:%d %s auth success in auth_stratum", proxi->id, proxi->subid, proxi->url); out: if (val) json_decref(val); @@ -1110,143 +1378,244 @@ out: buf = cached_proxy_line(proxi); if (!buf) break; - parse_method(proxi, buf); + parse_method(ckp, proxi, buf); }; + } else if (!proxi->global) { + LOGNOTICE("Disabling userproxy %d:%d %s that failed authorisation as %s", + proxi->id, proxi->subid, proxi->url, proxi->auth); + proxi->disabled = true; + disable_subproxy(ckp->data, proxi->parent, proxi); } return ret; } -static void send_subscribe(proxy_instance_t *proxi, int *sockd) +static proxy_instance_t *proxy_by_id(gdata_t *gdata, const int id) +{ + proxy_instance_t *proxi; + + mutex_lock(&gdata->lock); + HASH_FIND_INT(gdata->proxies, &id, proxi); + mutex_unlock(&gdata->lock); + + return proxi; +} + +static void send_subscribe(ckpool_t *ckp, proxy_instance_t *proxi) { json_t *json_msg; - char *msg; + char *msg, *buf; - JSON_CPACK(json_msg, "{sssi}", "enonce1", proxi->enonce1, - "nonce2len", proxi->nonce2len); + JSON_CPACK(json_msg, "{ss,ss,ss,sI,si,ss,si,sb,si}", + "url", proxi->url, "auth", proxi->auth, "pass", proxi->pass, + "proxy", proxi->id, "subproxy", proxi->subid, + "enonce1", proxi->enonce1, "nonce2len", proxi->nonce2len, + "global", proxi->global, "userid", proxi->userid); msg = json_dumps(json_msg, JSON_NO_UTF8); json_decref(json_msg); - send_unix_msg(*sockd, msg); + ASPRINTF(&buf, "subscribe=%s", msg); free(msg); - _Close(sockd); + send_proc(ckp->stratifier, buf); + free(buf); } -static void send_notify(proxy_instance_t *proxi, int *sockd) +static proxy_instance_t *subproxy_by_id(proxy_instance_t *proxy, const int subid) { - json_t *json_msg, *merkle_arr; - notify_instance_t *ni; - char *msg; - int i; + proxy_instance_t *subproxy; - merkle_arr = json_array(); + mutex_lock(&proxy->proxy_lock); + subproxy = __subproxy_by_id(proxy, subid); + mutex_unlock(&proxy->proxy_lock); + + return subproxy; +} + +static void drop_proxy(gdata_t *gdata, const char *buf) +{ + proxy_instance_t *proxy, *subproxy; + int id = -1, subid = -1; - mutex_lock(&proxi->notify_lock); - ni = proxi->current_notify; - if (unlikely(!ni)) { - mutex_unlock(&proxi->notify_lock); - goto out_close; + sscanf(buf, "dropproxy=%d:%d", &id, &subid); + if (unlikely(!subid)) { + LOGWARNING("Generator asked to drop parent proxy %d", id); + return; } - for (i = 0; i < ni->merkles; i++) - json_array_append_new(merkle_arr, json_string(&ni->merklehash[i][0])); - /* Use our own jobid instead of the server's one for easy lookup */ - JSON_CPACK(json_msg, "{si,ss,si,ss,ss,so,ss,ss,ss,sb}", - "jobid", ni->id, "prevhash", ni->prevhash, "coinb1len", ni->coinb1len, - "coinbase1", ni->coinbase1, "coinbase2", ni->coinbase2, - "merklehash", merkle_arr, "bbversion", ni->bbversion, - "nbit", ni->nbit, "ntime", ni->ntime, - "clean", ni->clean); - mutex_unlock(&proxi->notify_lock); + proxy = proxy_by_id(gdata, id); + if (unlikely(!proxy)) { + LOGINFO("Generator asked to drop subproxy from non-existent parent %d", id); + return; + } + subproxy = subproxy_by_id(proxy, subid); + if (!subproxy) { + LOGINFO("Generator asked to drop non-existent subproxy %d:%d", id, subid); + return; + } + LOGNOTICE("Generator asked to drop proxy %d:%d", id, subid); + disable_subproxy(gdata, proxy, subproxy); +} - msg = json_dumps(json_msg, JSON_NO_UTF8); - json_decref(json_msg); - send_unix_msg(*sockd, msg); - free(msg); -out_close: - _Close(sockd); +static void stratifier_reconnect_client(ckpool_t *ckp, const int64_t id) +{ + char buf[256]; + + sprintf(buf, "reconnclient=%"PRId64, id); + send_proc(ckp->stratifier, buf); } -static void send_diff(proxy_instance_t *proxi, int *sockd) +/* Add a share to the gdata share hashlist. Returns the share id */ +static int add_share(gdata_t *gdata, const int64_t client_id, const double diff) { - json_t *json_msg; - char *msg; + share_msg_t *share = ckzalloc(sizeof(share_msg_t)), *tmpshare; + time_t now; + int ret; - JSON_CPACK(json_msg, "{sf}", "diff", proxi->diff); - msg = json_dumps(json_msg, JSON_NO_UTF8); - json_decref(json_msg); - send_unix_msg(*sockd, msg); - free(msg); - _Close(sockd); + share->submit_time = now = time(NULL); + share->client_id = client_id; + share->diff = diff; + + /* Add new share entry to the share hashtable. Age old shares */ + mutex_lock(&gdata->share_lock); + ret = share->id = gdata->share_id++; + HASH_ADD_I64(gdata->shares, id, share); + HASH_ITER(hh, gdata->shares, share, tmpshare) { + if (share->submit_time < now - 120) + HASH_DEL(gdata->shares, share); + } + mutex_unlock(&gdata->share_lock); + + return ret; } -static void submit_share(proxy_instance_t *proxi, json_t *val) +static void submit_share(gdata_t *gdata, json_t *val) { + proxy_instance_t *proxy, *proxi; + ckpool_t *ckp = gdata->ckp; + int id, subid, share_id; + bool success = false; stratum_msg_t *msg; - share_msg_t *share; + int64_t client_id; + + /* Get the client id so we can tell the stratifier to drop it if the + * proxy it's bound to is not functional */ + if (unlikely(!json_get_int64(&client_id, val, "client_id"))) { + LOGWARNING("Got no client_id in share"); + goto out; + } + if (unlikely(!json_get_int(&id, val, "proxy"))) { + LOGWARNING("Got no proxy in share"); + goto out; + } + if (unlikely(!json_get_int(&subid, val, "subproxy"))) { + LOGWARNING("Got no subproxy in share"); + goto out; + } + proxy = proxy_by_id(gdata, id); + if (unlikely(!proxy)) { + LOGINFO("Client %"PRId64" sending shares to non existent proxy %d, dropping", + client_id, id); + stratifier_reconnect_client(ckp, client_id); + goto out; + } + proxi = subproxy_by_id(proxy, subid); + if (unlikely(!proxi)) { + LOGINFO("Client %"PRId64" sending shares to non existent subproxy %d:%d, dropping", + client_id, id, subid); + stratifier_reconnect_client(ckp, client_id); + goto out; + } + if (!proxi->alive) { + LOGINFO("Client %"PRId64" sending shares to dead subproxy %d:%d, dropping", + client_id, id, subid); + stratifier_reconnect_client(ckp, client_id); + goto out; + } + success = true; msg = ckzalloc(sizeof(stratum_msg_t)); - share = ckzalloc(sizeof(share_msg_t)); - share->submit_time = time(NULL); - share->client_id = json_integer_value(json_object_get(val, "client_id")); - share->msg_id = json_integer_value(json_object_get(val, "msg_id")); - json_object_del(val, "client_id"); - json_object_del(val, "msg_id"); msg->json_msg = val; - - /* Add new share entry to the share hashtable */ - mutex_lock(&proxi->share_lock); - share->id = proxi->share_id++; - HASH_ADD_I64(proxi->shares, id, share); - mutex_unlock(&proxi->share_lock); - - json_object_set_nocheck(val, "id", json_integer(share->id)); + share_id = add_share(gdata, client_id, proxi->diff); + json_object_set_nocheck(val, "id", json_integer(share_id)); /* Add the new message to the psend list */ - mutex_lock(&proxi->psend_lock); - DL_APPEND(proxi->psends, msg); - pthread_cond_signal(&proxi->psend_cond); - mutex_unlock(&proxi->psend_lock); + mutex_lock(&gdata->psend_lock); + gdata->psends_generated++; + DL_APPEND(gdata->psends, msg); + pthread_cond_signal(&gdata->psend_cond); + mutex_unlock(&gdata->psend_lock); + +out: + if (!success) + json_decref(val); } static void clear_notify(notify_instance_t *ni) { - free(ni->jobid); + if (ni->jobid) + json_decref(ni->jobid); free(ni->coinbase1); free(ni->coinbase2); free(ni); } -/* FIXME: Return something useful to the stratifier based on this result */ -static bool parse_share(proxy_instance_t *proxi, const char *buf) +static void account_shares(proxy_instance_t *proxy, const double diff, const bool result) { - json_t *val = NULL, *idval; - share_msg_t *share; - bool ret = false; + proxy_instance_t *parent = proxy->parent; + + mutex_lock(&parent->proxy_lock); + if (result) { + proxy->diff_accepted += diff; + parent->total_accepted += diff; + } else { + proxy->diff_rejected += diff; + parent->total_rejected += diff; + } + mutex_unlock(&parent->proxy_lock); +} + +/* Returns zero if it is not recognised as a share, 1 if it is a valid share + * and -1 if it is recognised as a share but invalid. */ +static int parse_share(gdata_t *gdata, proxy_instance_t *proxi, const char *buf) +{ + json_t *val = NULL, *idval; + bool result = false; + share_msg_t *share; + int ret = 0; int64_t id; val = json_loads(buf, 0, NULL); - if (!val) { - LOGINFO("Failed to parse json msg: %s", buf); + if (unlikely(!val)) { + LOGINFO("Failed to parse upstream json msg: %s", buf); goto out; } idval = json_object_get(val, "id"); - if (!idval) { - LOGINFO("Failed to find id in json msg: %s", buf); + if (unlikely(!idval)) { + LOGINFO("Failed to find id in upstream json msg: %s", buf); goto out; } id = json_integer_value(idval); + if (unlikely(!json_get_bool(&result, val, "result"))) { + LOGINFO("Failed to find result in upstream json msg: %s", buf); + goto out; + } - mutex_lock(&proxi->share_lock); - HASH_FIND_I64(proxi->shares, &id, share); + mutex_lock(&gdata->share_lock); + HASH_FIND_I64(gdata->shares, &id, share); if (share) - HASH_DEL(proxi->shares, share); - mutex_unlock(&proxi->share_lock); + HASH_DEL(gdata->shares, share); + mutex_unlock(&gdata->share_lock); if (!share) { - LOGINFO("Failed to find matching share to result: %s", buf); + LOGINFO("Proxy %d:%d failed to find matching share to result: %s", + proxi->id, proxi->subid, buf); + /* We don't know what diff these shares are so assume the + * current proxy diff. */ + account_shares(proxi, proxi->diff, result); + ret = -1; goto out; } - ret = true; - LOGDEBUG("Found share from client %d with msg_id %d", share->client_id, - share->msg_id); + ret = 1; + account_shares(proxi, share->diff, result); + LOGINFO("Proxy %d:%d share result %s from client %"PRId64, proxi->id, proxi->subid, + buf, share->client_id); free(share); out: if (val) @@ -1254,13 +1623,458 @@ out: return ret; } +struct cs_msg { + cs_msg_t *next; + cs_msg_t *prev; + proxy_instance_t *proxy; + char *buf; + int len; + int ofs; +}; + +/* Sends all messages in the queue ready to be dispatched, leaving those that + * would block to be handled next pass */ +static void send_json_msgq(gdata_t *gdata, cs_msg_t **csmsgq) +{ + cs_msg_t *csmsg, *tmp; + int ret; + + DL_FOREACH_SAFE(*csmsgq, csmsg, tmp) { + proxy_instance_t *proxy = csmsg->proxy; + + /* Only try to send one message at a time to each proxy + * to avoid sending parts of different messages */ + if (proxy->sending && proxy->sending != csmsg) + continue; + while (csmsg->len) { + int fd; + + proxy->sending = csmsg; + fd = proxy->cs.fd; + ret = send(fd, csmsg->buf + csmsg->ofs, csmsg->len, MSG_DONTWAIT); + if (ret < 1) { + if (errno == EAGAIN || errno == EWOULDBLOCK || !ret) + break; + csmsg->len = 0; + LOGNOTICE("Proxy %d:%d %s failed to send msg in send_json_msgq, dropping", + proxy->id, proxy->subid, proxy->url); + disable_subproxy(gdata, proxy->parent, proxy); + } + csmsg->ofs += ret; + csmsg->len -= ret; + } + if (!csmsg->len) { + proxy->sending = NULL; + DL_DELETE(*csmsgq, csmsg); + free(csmsg->buf); + free(csmsg); + } + } +} + +static void add_json_msgq(cs_msg_t **csmsgq, proxy_instance_t *proxy, json_t **val) +{ + cs_msg_t *csmsg = ckzalloc(sizeof(cs_msg_t)); + + csmsg->buf = json_dumps(*val, JSON_ESCAPE_SLASH | JSON_EOL); + json_decref(*val); + *val = NULL; + if (unlikely(!csmsg->buf)) { + LOGWARNING("Failed to create json dump in add_json_msgq"); + return; + } + csmsg->len = strlen(csmsg->buf); + csmsg->proxy = proxy; + DL_APPEND(*csmsgq, csmsg); +} + +/* For processing and sending shares. proxy refers to parent proxy here */ +static void *proxy_send(void *arg) +{ + ckpool_t *ckp = (ckpool_t *)arg; + gdata_t *gdata = ckp->data; + stratum_msg_t *msg = NULL; + cs_msg_t *csmsgq = NULL; + + rename_proc("proxysend"); + + pthread_detach(pthread_self()); + + while (42) { + proxy_instance_t *proxy, *subproxy; + int proxyid = 0, subid = 0; + int64_t client_id = 0, id; + notify_instance_t *ni; + json_t *jobid = NULL; + json_t *val; + + if (unlikely(msg)) { + json_decref(msg->json_msg); + free(msg); + } + + mutex_lock(&gdata->psend_lock); + if (!gdata->psends) { + /* Poll every 10ms */ + const ts_t polltime = {0, 10000000}; + ts_t timeout_ts; + + ts_realtime(&timeout_ts); + timeraddspec(&timeout_ts, &polltime); + cond_timedwait(&gdata->psend_cond, &gdata->psend_lock, &timeout_ts); + } + msg = gdata->psends; + if (likely(msg)) + DL_DELETE(gdata->psends, msg); + mutex_unlock(&gdata->psend_lock); + + if (!msg) { + send_json_msgq(gdata, &csmsgq); + continue; + } + + if (unlikely(!json_get_int(&subid, msg->json_msg, "subproxy"))) { + LOGWARNING("Failed to find subproxy in proxy_send msg"); + continue; + } + if (unlikely(!json_get_int64(&id, msg->json_msg, "jobid"))) { + LOGWARNING("Failed to find jobid in proxy_send msg"); + continue; + } + if (unlikely(!json_get_int(&proxyid, msg->json_msg, "proxy"))) { + LOGWARNING("Failed to find proxy in proxy_send msg"); + continue; + } + if (unlikely(!json_get_int64(&client_id, msg->json_msg, "client_id"))) { + LOGWARNING("Failed to find client_id in proxy_send msg"); + continue; + } + proxy = proxy_by_id(gdata, proxyid); + if (unlikely(!proxy)) { + LOGWARNING("Proxysend for got message for non-existent proxy %d", + proxyid); + continue; + } + subproxy = subproxy_by_id(proxy, subid); + if (unlikely(!subproxy)) { + LOGWARNING("Proxysend for got message for non-existent subproxy %d:%d", + proxyid, subid); + continue; + } + + mutex_lock(&gdata->notify_lock); + HASH_FIND_INT(gdata->notify_instances, &id, ni); + if (ni) + jobid = json_copy(ni->jobid); + mutex_unlock(&gdata->notify_lock); + + if (unlikely(!jobid)) { + stratifier_reconnect_client(ckp, client_id); + LOGNOTICE("Proxy %d:%s failed to find matching jobid in proxysend", + subproxy->id, subproxy->url); + continue; + } + + 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); + } + return NULL; +} + +static void passthrough_send(ckpool_t *ckp, pass_msg_t *pm) +{ + connsock_t *cs = pm->cs; + int len, sent; + + LOGDEBUG("Sending upstream json msg: %s", pm->msg); + len = strlen(pm->msg); + sent = write_socket(cs->fd, pm->msg, len); + if (unlikely(sent != len && cs->fd)) { + LOGWARNING("Failed to passthrough %d bytes of message %s, attempting reconnect", + len, pm->msg); + Close(cs->fd); + pm->proxy->alive = false; + reconnect_generator(ckp); + } + free(pm->msg); + free(pm); +} + +static void passthrough_add_send(proxy_instance_t *proxy, const char *msg) +{ + pass_msg_t *pm = ckzalloc(sizeof(pass_msg_t)); + + pm->proxy = proxy; + pm->cs = &proxy->cs; + ASPRINTF(&pm->msg, "%s\n", msg); + ckmsgq_add(proxy->passsends, pm); +} + +static bool proxy_alive(ckpool_t *ckp, proxy_instance_t *proxi, connsock_t *cs, + bool pinging) +{ + bool ret = false; + + /* Has this proxy already been reconnected? */ + if (proxi->alive) + return true; + if (proxi->disabled) + return false; + + /* Serialise all send/recvs here with the cs semaphore */ + cksem_wait(&cs->sem); + /* Check again after grabbing semaphore */ + if (unlikely(proxi->alive)) { + ret = true; + goto out; + } + if (!extract_sockaddr(proxi->url, &cs->url, &cs->port)) { + LOGWARNING("Failed to extract address from %s", proxi->url); + goto out; + } + if (!connect_proxy(ckp, cs, proxi)) { + if (!pinging) { + LOGINFO("Failed to connect to %s:%s in proxy_mode!", + cs->url, cs->port); + } + goto out; + } + if (ckp->node) { + if (!node_stratum(cs, proxi)) { + LOGWARNING("Failed initial node setup to %s:%s !", + cs->url, cs->port); + goto out; + } + ret = true; + goto out; + } + if (ckp->passthrough) { + if (!passthrough_stratum(cs, proxi)) { + LOGWARNING("Failed initial passthrough to %s:%s !", + cs->url, cs->port); + goto out; + } + ret = true; + goto out; + } + /* Test we can connect, authorise and get stratum information */ + if (!subscribe_stratum(ckp, cs, proxi)) { + if (!pinging) { + LOGWARNING("Failed initial subscribe to %s:%s !", + cs->url, cs->port); + } + goto out; + } + if (!ckp->passthrough) + send_subscribe(ckp, proxi); + if (!auth_stratum(ckp, cs, proxi)) { + if (!pinging) { + LOGWARNING("Failed initial authorise to %s:%s with %s:%s !", + cs->url, cs->port, proxi->auth, proxi->pass); + } + goto out; + } + proxi->authorised = ret = true; +out: + if (!ret) { + send_stratifier_deadproxy(ckp, proxi->id, proxi->subid); + /* Close and invalidate the file handle */ + Close(cs->fd); + } + proxi->alive = ret; + cksem_post(&cs->sem); + + return ret; +} + +static void *proxy_recruit(void *arg) +{ + proxy_instance_t *proxy, *parent = (proxy_instance_t *)arg; + ckpool_t *ckp = parent->ckp; + gdata_t *gdata = ckp->data; + bool recruit, alive; + + pthread_detach(pthread_self()); + +retry: + recruit = false; + proxy = create_subproxy(ckp, gdata, parent, parent->url); + alive = proxy_alive(ckp, proxy, &proxy->cs, false); + if (!alive) { + LOGNOTICE("Subproxy failed proxy_alive testing"); + store_proxy(gdata, proxy); + } else + add_subproxy(parent, proxy); + + mutex_lock(&parent->proxy_lock); + if (alive && parent->recruit > 0) + recruit = true; + else /* Reset so the next request will try again */ + parent->recruit = 0; + mutex_unlock(&parent->proxy_lock); + + if (recruit) + goto retry; + + return NULL; +} + +static void recruit_subproxies(proxy_instance_t *proxi, const int recruits) +{ + bool recruit = false; + pthread_t pth; + + mutex_lock(&proxi->proxy_lock); + if (!proxi->recruit) + recruit = true; + if (proxi->recruit < recruits) + proxi->recruit = recruits; + mutex_unlock(&proxi->proxy_lock); + + if (recruit) + create_pthread(&pth, proxy_recruit, proxi); +} + +/* Queue up to the requested amount */ +static void recruit_subproxy(gdata_t *gdata, const char *buf) +{ + int recruits = 1, id = 0; + proxy_instance_t *proxy; + + sscanf(buf, "recruit=%d:%d", &id, &recruits); + proxy = proxy_by_id(gdata, id); + if (unlikely(!proxy)) { + LOGNOTICE("Generator failed to find proxy id %d to recruit subproxies", + id); + return; + } + recruit_subproxies(proxy, recruits); +} + +static void *proxy_reconnect(void *arg) +{ + proxy_instance_t *proxy = (proxy_instance_t *)arg; + connsock_t *cs = &proxy->cs; + ckpool_t *ckp = proxy->ckp; + + pthread_detach(pthread_self()); + proxy_alive(ckp, proxy, cs, true); + proxy->reconnecting = false; + return NULL; +} + +/* For reconnecting the parent proxy instance async */ +static void reconnect_proxy(proxy_instance_t *proxi) +{ + pthread_t pth; + + if (proxi->reconnecting) + return; + proxi->reconnecting = true; + create_pthread(&pth, proxy_reconnect, proxi); +} + +/* For receiving messages from an upstream pool to pass downstream. Responsible + * for setting up the connection and testing pool is live. */ +static void *passthrough_recv(void *arg) +{ + proxy_instance_t *proxi = (proxy_instance_t *)arg; + connsock_t *cs = &proxi->cs; + ckpool_t *ckp = proxi->ckp; + bool alive; + + rename_proc("passrecv"); + + if (proxy_alive(ckp, proxi, cs, false)) + LOGWARNING("Passthrough proxy %d:%s connection established", proxi->id, proxi->url); + alive = proxi->alive; + + while (42) { + float timeout = 90; + int ret; + + while (!proxy_alive(ckp, proxi, cs, true)) { + alive = false; + sleep(5); + } + if (!alive) { + reconnect_generator(ckp); + LOGWARNING("Passthrough %d:%s recovered", proxi->id, proxi->url); + alive = true; + } + + /* Make sure we receive a line within 90 seconds */ + cksem_wait(&cs->sem); + ret = read_socket_line(cs, &timeout); + /* Simply forward the message on, as is, to the connector to + * process. Possibly parse parameters sent by upstream pool + * here */ + if (likely(ret > 0)) { + LOGDEBUG("Received upstream msg: %s", cs->buf); + send_proc(ckp->connector, cs->buf); + } else if (ret < 0) { + /* Read failure */ + LOGWARNING("Passthrough %d:%s failed to read_socket_line in passthrough_recv, attempting reconnect", + proxi->id, proxi->url); + alive = proxi->alive = false; + Close(cs->fd); + reconnect_generator(ckp); + } else /* Idle, likely no clients */ + LOGDEBUG("Passthrough %d:%s no messages received", proxi->id, proxi->url); + cksem_post(&cs->sem); + } + return NULL; +} + +static bool subproxies_alive(proxy_instance_t *proxy) +{ + proxy_instance_t *subproxy, *tmp; + bool ret = false; + + mutex_lock(&proxy->proxy_lock); + HASH_ITER(sh, proxy->subproxies, subproxy, tmp) { + if (subproxy->alive) { + ret = true; + break; + } + } + mutex_unlock(&proxy->proxy_lock); + + return ret; +} + +/* For receiving messages from the upstream proxy, also responsible for setting + * up the connection and testing it's alive. */ static void *proxy_recv(void *arg) { proxy_instance_t *proxi = (proxy_instance_t *)arg; - connsock_t *cs = proxi->cs; + connsock_t *cs = &proxi->cs; + proxy_instance_t *subproxy; ckpool_t *ckp = proxi->ckp; + gdata_t *gdata = ckp->data; + struct epoll_event event; + bool alive; + int epfd; rename_proc("proxyrecv"); + pthread_detach(pthread_self()); + + proxi->epfd = epfd = epoll_create1(EPOLL_CLOEXEC); + if (epfd < 0){ + LOGEMERG("FATAL: Failed to create epoll in proxyrecv"); + return NULL; + } + + if (proxy_alive(ckp, proxi, cs, false)) + LOGWARNING("Proxy %d:%s connection established", proxi->id, proxi->url); + + alive = proxi->alive; while (42) { notify_instance_t *ni, *tmp; @@ -1269,423 +2083,775 @@ static void *proxy_recv(void *arg) time_t now; int ret; + subproxy = proxi; + if (!proxi->alive) { + reconnect_proxy(proxi); + while (!subproxies_alive(proxi)) { + reconnect_proxy(proxi); + if (alive) { + reconnect_generator(ckp); + LOGWARNING("Proxy %d:%s failed, attempting reconnect", + proxi->id, proxi->url); + alive = false; + } + sleep(5); + } + } + if (!alive) { + reconnect_generator(ckp); + LOGWARNING("Proxy %d:%s recovered", proxi->id, proxi->url); + alive = true; + } + now = time(NULL); /* Age old notifications older than 10 mins old */ - mutex_lock(&proxi->notify_lock); - HASH_ITER(hh, proxi->notify_instances, ni, tmp) { - if (HASH_COUNT(proxi->notify_instances) < 3) + mutex_lock(&gdata->notify_lock); + HASH_ITER(hh, gdata->notify_instances, ni, tmp) { + if (HASH_COUNT(gdata->notify_instances) < 3) break; if (ni->notify_time < now - 600) { - HASH_DEL(proxi->notify_instances, ni); + HASH_DEL(gdata->notify_instances, ni); clear_notify(ni); } } - mutex_unlock(&proxi->notify_lock); + mutex_unlock(&gdata->notify_lock); /* Similary with shares older than 2 mins without response */ - mutex_lock(&proxi->share_lock); - HASH_ITER(hh, proxi->shares, share, tmpshare) { + mutex_lock(&gdata->share_lock); + HASH_ITER(hh, gdata->shares, share, tmpshare) { if (share->submit_time < now - 120) { - HASH_DEL(proxi->shares, share); + HASH_DEL(gdata->shares, share); } } - mutex_unlock(&proxi->share_lock); + mutex_unlock(&gdata->share_lock); + cs = NULL; /* If we don't get an update within 10 minutes the upstream pool * has likely stopped responding. */ - timeout = 600; - do { - if (cs->fd == -1) { + ret = epoll_wait(epfd, &event, 1, 600000); + if (likely(ret > 0)) { + subproxy = event.data.ptr; + cs = &subproxy->cs; + if (!subproxy->alive) + continue; + + /* Serialise messages from here once we have a cs by + * holding the semaphore. */ + cksem_wait(&cs->sem); + if (event.events & (EPOLLHUP | EPOLLERR | EPOLLRDHUP)) ret = -1; - break; + else { + timeout = 30; + ret = read_socket_line(cs, &timeout); } - ret = read_socket_line(cs, &timeout); - } while (ret == 0 && timeout > 0); + } + if (ret < 1) { + LOGNOTICE("Proxy %d:%d %s failed to epoll/read_socket_line in proxy_recv", + proxi->id, subproxy->subid, subproxy->url); + disable_subproxy(gdata, proxi, subproxy); + } else do { + /* subproxy may have been recycled here if it is not a + * parent and reconnect was issued */ + if (parse_method(ckp, subproxy, cs->buf)) + continue; + /* If it's not a method it should be a share result */ + if (!parse_share(gdata, subproxy, cs->buf)) { + LOGNOTICE("Proxy %d:%d unhandled stratum message: %s", + subproxy->id, subproxy->subid, cs->buf); + } + timeout = 0; + } while ((ret = read_socket_line(cs, &timeout)) > 0); + if (cs) + cksem_post(&cs->sem); + } + + return NULL; +} + +/* Thread that handles all received messages from user proxies */ +static void *userproxy_recv(void *arg) +{ + ckpool_t *ckp = (ckpool_t *)arg; + gdata_t *gdata = ckp->data; + struct epoll_event event; + int epfd; + + rename_proc("uproxyrecv"); + pthread_detach(pthread_self()); + + epfd = epoll_create1(EPOLL_CLOEXEC); + if (epfd < 0){ + LOGEMERG("FATAL: Failed to create epoll in userproxy_recv"); + return NULL; + } + + while (42) { + proxy_instance_t *proxy, *tmpproxy; + share_msg_t *share, *tmpshare; + notify_instance_t *ni, *tmp; + connsock_t *cs; + float timeout; + time_t now; + int ret; + + mutex_lock(&gdata->lock); + HASH_ITER(hh, gdata->proxies, proxy, tmpproxy) { + if (!proxy->global && !proxy->alive) { + proxy->epfd = epfd; + reconnect_proxy(proxy); + } + } + mutex_unlock(&gdata->lock); + ret = epoll_wait(epfd, &event, 1, 1000); if (ret < 1) { - /* Send ourselves a reconnect message */ - LOGWARNING("Failed to read_socket_line in proxy_recv, attempting reconnect"); - send_proc(ckp->generator, "reconnect"); + if (likely(!ret)) + continue; + LOGEMERG("Failed to epoll_wait in userproxy_recv"); break; } - if (parse_method(proxi, cs->buf)) { - if (proxi->notified) { - send_proc(ckp->stratifier, "notify"); - proxi->notified = false; - } - if (proxi->diffed) { - send_proc(ckp->stratifier, "diff"); - proxi->diffed = false; - } - if (proxi->reconnect) { - proxi->reconnect = false; - LOGWARNING("Reconnect issue, dropping existing connection"); - send_proc(ckp->generator, "reconnect"); + proxy = event.data.ptr; + /* Make sure we haven't popped this off before we've finished + * subscribe/auth */ + if (unlikely(!proxy->authorised)) + continue; + + if (event.events & (EPOLLHUP | EPOLLERR | EPOLLRDHUP)) { + LOGNOTICE("Proxy %d:%d %s hung up in epoll_wait", proxy->id, + proxy->subid, proxy->url); + disable_subproxy(gdata, proxy->parent, proxy); + continue; + } + now = time(NULL); + + mutex_lock(&gdata->notify_lock); + HASH_ITER(hh, gdata->notify_instances, ni, tmp) { + if (HASH_COUNT(gdata->notify_instances) < 3) break; + if (ni->notify_time < now - 600) { + HASH_DEL(gdata->notify_instances, ni); + clear_notify(ni); } - continue; } - if (parse_share(proxi, cs->buf)) { + mutex_unlock(&gdata->notify_lock); + + /* Similary with shares older than 2 mins without response */ + mutex_lock(&gdata->share_lock); + HASH_ITER(hh, gdata->shares, share, tmpshare) { + if (share->submit_time < now - 120) { + HASH_DEL(gdata->shares, share); + } + } + mutex_unlock(&gdata->share_lock); + + timeout = 0; + cs = &proxy->cs; + + if (!proxy->alive) continue; + + cksem_wait(&cs->sem); + while ((ret = read_socket_line(cs, &timeout)) > 0) { + /* proxy may have been recycled here if it is not a + * parent and reconnect was issued */ + if (parse_method(ckp, proxy, cs->buf)) + continue; + /* If it's not a method it should be a share result */ + if (!parse_share(gdata, proxy, cs->buf)) { + LOGNOTICE("Proxy %d:%d unhandled stratum message: %s", + proxy->id, proxy->subid, cs->buf); + } + timeout = 0; } - /* If it's not a method it should be a share result */ - LOGWARNING("Unhandled stratum message: %s", cs->buf); + cksem_post(&cs->sem); } return NULL; } -/* For processing and sending shares */ -static void *proxy_send(void *arg) +static void prepare_proxy(proxy_instance_t *proxi) { - proxy_instance_t *proxi = (proxy_instance_t *)arg; - connsock_t *cs = proxi->cs; + proxi->parent = proxi; + mutex_init(&proxi->proxy_lock); + add_subproxy(proxi, proxi); + if (proxi->global) + create_pthread(&proxi->pth_precv, proxy_recv, proxi); +} - rename_proc("proxysend"); +static proxy_instance_t *wait_best_proxy(ckpool_t *ckp, gdata_t *gdata) +{ + proxy_instance_t *ret = NULL, *proxi, *tmp; + int retries = 0; + + while (42) { + if (!ping_main(ckp)) + break; + + mutex_lock(&gdata->lock); + HASH_ITER(hh, gdata->proxies, proxi, tmp) { + if (proxi->disabled || !proxi->global) + continue; + if (proxi->alive || subproxies_alive(proxi)) { + if (!ret || proxi->id < ret->id) + ret = proxi; + } + } + mutex_unlock(&gdata->lock); + + if (ret) + break; + /* Send reject message if we are unable to find an active + * proxy for more than 5 seconds */ + if (!((retries++) % 5)) + send_proc(ckp->connector, "reject"); + sleep(1); + } + send_proc(ckp->connector, ret ? "accept" : "reject"); + return ret; +} + +static void send_list(gdata_t *gdata, const int sockd) +{ + proxy_instance_t *proxy, *tmp; + json_t *val, *array_val; + + array_val = json_array(); + + mutex_lock(&gdata->lock); + HASH_ITER(hh, gdata->proxies, proxy, tmp) { + JSON_CPACK(val, "{si,sb,si,ss,ss,sf,sb,sb,si}", + "id", proxy->id, "global", proxy->global, "userid", proxy->userid, + "auth", proxy->auth, "pass", proxy->pass, + "diff", proxy->diff, + "disabled", proxy->disabled, "alive", proxy->alive, + "subproxies", proxy->subproxy_count); + if (proxy->enonce1) { + json_set_string(val, "enonce1", proxy->enonce1); + json_set_int(val, "nonce1len", proxy->nonce1len); + json_set_int(val, "nonce2len", proxy->nonce2len); + } + json_array_append_new(array_val, val); + } + mutex_unlock(&gdata->lock); - while (42) { - notify_instance_t *ni; - stratum_msg_t *msg; - char *jobid = NULL; - bool ret = true; - json_t *val; - uint32_t id; + JSON_CPACK(val, "{so}", "proxies", array_val); + send_api_response(val, sockd); +} - mutex_lock(&proxi->psend_lock); - if (!proxi->psends) - cond_wait(&proxi->psend_cond, &proxi->psend_lock); - msg = proxi->psends; - if (likely(msg)) - DL_DELETE(proxi->psends, msg); - mutex_unlock(&proxi->psend_lock); +static void send_sublist(gdata_t *gdata, const int sockd, const char *buf) +{ + proxy_instance_t *proxy, *subproxy, *tmp; + json_t *val = NULL, *array_val; + json_error_t err_val; + int64_t id; - if (unlikely(!msg)) - continue; + array_val = json_array(); - json_uintcpy(&id, msg->json_msg, "jobid"); + val = json_loads(buf, 0, &err_val); + if (unlikely(!val)) { + val = json_encode_errormsg(&err_val); + goto out; + } + if (unlikely(!json_get_int64(&id, val, "id"))) { + val = json_errormsg("Failed to get ID in send_sublist JSON: %s", buf); + goto out; + } + proxy = proxy_by_id(gdata, id); + if (unlikely(!proxy)) { + val = json_errormsg("Failed to find proxy %"PRId64" in send_sublist", id); + goto out; + } - mutex_lock(&proxi->notify_lock); - HASH_FIND_INT(proxi->notify_instances, &id, ni); - if (ni) - jobid = strdup(ni->jobid); - mutex_unlock(&proxi->notify_lock); - - if (jobid) { - JSON_CPACK(val, "{s[ssooo]soss}", "params", proxi->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"); - free(jobid); - ret = send_json_msg(cs, val); - json_decref(val); - } else - LOGWARNING("Failed to find matching jobid in proxysend"); - json_decref(msg->json_msg); - free(msg); - if (!ret && cs->fd > 0) { - LOGWARNING("Failed to send msg in proxy_send, dropping to reconnect"); - Close(cs->fd); + mutex_lock(&gdata->lock); + HASH_ITER(sh, proxy->subproxies, subproxy, tmp) { + JSON_CPACK(val, "{si,ss,ss,sf,sb,sb}", + "subid", subproxy->id, + "auth", subproxy->auth, "pass", subproxy->pass, + "diff", subproxy->diff, + "disabled", subproxy->disabled, "alive", subproxy->alive); + if (subproxy->enonce1) { + json_set_string(val, "enonce1", subproxy->enonce1); + json_set_int(val, "nonce1len", subproxy->nonce1len); + json_set_int(val, "nonce2len", subproxy->nonce2len); } + json_array_append_new(array_val, val); } - return NULL; + mutex_unlock(&gdata->lock); + + JSON_CPACK(val, "{so}", "subproxies", array_val); +out: + send_api_response(val, sockd); } -/* For receiving messages from an upstream pool to pass downstream */ -static void *passthrough_recv(void *arg) -{ - proxy_instance_t *proxi = (proxy_instance_t *)arg; - connsock_t *cs = proxi->cs; - ckpool_t *ckp = proxi->ckp; +static proxy_instance_t *__add_proxy(ckpool_t *ckp, gdata_t *gdata, const int num); - rename_proc("passrecv"); +static proxy_instance_t *__add_userproxy(ckpool_t *ckp, gdata_t *gdata, const int id, + const int userid, char *url, char *auth, char *pass) +{ + proxy_instance_t *proxy; + + gdata->proxies_generated++; + proxy = ckzalloc(sizeof(proxy_instance_t)); + proxy->id = id; + proxy->userid = userid; + proxy->url = url; + proxy->auth = auth; + proxy->pass = pass; + proxy->ckp = proxy->cs.ckp = ckp; + cksem_init(&proxy->cs.sem); + cksem_post(&proxy->cs.sem); + HASH_ADD_INT(gdata->proxies, id, proxy); + return proxy; +} - while (42) { - float timeout = 60; - int ret; +static void add_userproxy(ckpool_t *ckp, gdata_t *gdata, const int userid, + const char *url, const char *auth, const char *pass) +{ + proxy_instance_t *proxy; + char *newurl = strdup(url); + char *newauth = strdup(auth); + char *newpass = strdup(pass); + int id; - do { - ret = read_socket_line(cs, &timeout); - } while (ret == 0 && timeout > 0); + mutex_lock(&gdata->lock); + id = ckp->proxies++; + proxy = __add_userproxy(ckp, gdata, id, userid, newurl, newauth, newpass); + mutex_unlock(&gdata->lock); - if (ret < 1) { - /* Send ourselves a reconnect message */ - LOGWARNING("Failed to read_socket_line in proxy_recv, attempting reconnect"); - send_proc(ckp->generator, "reconnect"); - break; - } - /* Simply forward the message on, as is, to the connector to - * process. Possibly parse parameters sent by upstream pool - * here */ - send_proc(ckp->connector, cs->buf); - } - return NULL; + LOGWARNING("Adding non global user %s, %d proxy %d:%s", auth, userid, id, url); + prepare_proxy(proxy); } -static void passthrough_send(ckpool_t *ckp, pass_msg_t *pm) +static void parse_addproxy(ckpool_t *ckp, gdata_t *gdata, const int sockd, const char *buf) { - int len, sent; + char *url = NULL, *auth = NULL, *pass = NULL; + proxy_instance_t *proxy; + json_error_t err_val; + json_t *val = NULL; + int id, userid; + bool global; + + val = json_loads(buf, 0, &err_val); + if (unlikely(!val)) { + val = json_encode_errormsg(&err_val); + goto out; + } + json_get_string(&url, val, "url"); + json_get_string(&auth, val, "auth"); + json_get_string(&pass, val, "pass"); + if (json_get_int(&userid, val, "userid")) + global = false; + else + global = true; + json_decref(val); + if (unlikely(!url || !auth || !pass)) { + val = json_errormsg("Failed to decode url/auth/pass in addproxy %s", buf); + goto out; + } - LOGDEBUG("Sending upstream json msg: %s", pm->msg); - len = strlen(pm->msg); - sent = write_socket(pm->cs->fd, pm->msg, len); - if (sent != len) { - LOGWARNING("Failed to passthrough %d bytes of message %s, attempting reconnect", - len, pm->msg); - send_proc(ckp->generator, "reconnect"); + mutex_lock(&gdata->lock); + id = ckp->proxies++; + if (global) { + ckp->proxyurl = realloc(ckp->proxyurl, sizeof(char **) * ckp->proxies); + ckp->proxyauth = realloc(ckp->proxyauth, sizeof(char **) * ckp->proxies); + ckp->proxypass = realloc(ckp->proxypass, sizeof(char **) * ckp->proxies); + ckp->proxyurl[id] = url; + ckp->proxyauth[id] = auth; + ckp->proxypass[id] = pass; + proxy = __add_proxy(ckp, gdata, id); + } else + proxy = __add_userproxy(ckp, gdata, id, userid, url, auth, pass); + mutex_unlock(&gdata->lock); + + if (global) + LOGNOTICE("Adding global proxy %d:%s", id, proxy->url); + else + LOGNOTICE("Adding user %d proxy %d:%s", userid, id, proxy->url); + prepare_proxy(proxy); + if (global) { + JSON_CPACK(val, "{si,ss,ss,ss}", + "id", proxy->id, "url", url, "auth", auth, "pass", pass); + } else { + JSON_CPACK(val, "{si,ss,ss,ss,si}", + "id", proxy->id, "url", url, "auth", auth, "pass", pass, + "userid", proxy->userid); } - free(pm->msg); - free(pm); +out: + send_api_response(val, sockd); } -static void passthrough_add_send(proxy_instance_t *proxi, const char *msg) +static void delete_proxy(ckpool_t *ckp, gdata_t *gdata, proxy_instance_t *proxy) { - pass_msg_t *pm = ckzalloc(sizeof(pass_msg_t)); + proxy_instance_t *subproxy; - pm->cs = proxi->cs; - ASPRINTF(&pm->msg, "%s\n", msg); - ckmsgq_add(proxi->passsends, pm); + /* Remove the proxy from the master list first */ + mutex_lock(&gdata->lock); + HASH_DEL(gdata->proxies, proxy); + /* Disable all its threads */ + pthread_cancel(proxy->pth_precv); + Close(proxy->cs.fd); + mutex_unlock(&gdata->lock); + + /* Recycle all its subproxies */ + do { + mutex_lock(&proxy->proxy_lock); + subproxy = proxy->subproxies; + if (subproxy) + HASH_DELETE(sh, proxy->subproxies, subproxy); + mutex_unlock(&proxy->proxy_lock); + + send_stratifier_deadproxy(ckp, subproxy->id, subproxy->subid); + if (subproxy && proxy != subproxy) + store_proxy(gdata, subproxy); + } while (subproxy); + + /* Recycle the proxy itself */ + store_proxy(gdata, proxy); } -static bool proxy_alive(ckpool_t *ckp, server_instance_t *si, proxy_instance_t *proxi, - connsock_t *cs, bool pinging) +static void parse_delproxy(ckpool_t *ckp, gdata_t *gdata, const int sockd, const char *buf) { - bool ret = false; - - /* Has this proxy already been reconnected? */ - if (cs->fd > 0) - return true; - if (!extract_sockaddr(si->url, &cs->url, &cs->port)) { - LOGWARNING("Failed to extract address from %s", si->url); - return ret; - } - if (!connect_proxy(cs)) { - if (!pinging) { - LOGINFO("Failed to connect to %s:%s in proxy_mode!", - cs->url, cs->port); - } - return ret; + proxy_instance_t *proxy; + json_error_t err_val; + json_t *val = NULL; + int id = -1; + + val = json_loads(buf, 0, &err_val); + if (unlikely(!val)) { + val = json_encode_errormsg(&err_val); + goto out; } - if (ckp->passthrough) { - if (!passthrough_stratum(cs, proxi)) { - LOGWARNING("Failed initial passthrough to %s:%s !", - cs->url, cs->port); - goto out; - } - ret = true; + json_get_int(&id, val, "id"); + proxy = proxy_by_id(gdata, id); + if (!proxy) { + val = json_errormsg("Proxy id %d not found", id); goto out; } - /* Test we can connect, authorise and get stratum information */ - if (!subscribe_stratum(cs, proxi)) { - if (!pinging) { - LOGWARNING("Failed initial subscribe to %s:%s !", - cs->url, cs->port); - } + JSON_CPACK(val, "{si,ss,ss,ss}", "id", proxy->id, "url", proxy->url, + "auth", proxy->auth, "pass", proxy->pass); + + LOGNOTICE("Deleting proxy %d:%s", proxy->id, proxy->url); + delete_proxy(ckp, gdata, proxy); +out: + send_api_response(val, sockd); +} + +static void parse_ableproxy(gdata_t *gdata, const int sockd, const char *buf, bool disable) +{ + proxy_instance_t *proxy; + json_error_t err_val; + json_t *val = NULL; + int id = -1; + + val = json_loads(buf, 0, &err_val); + if (unlikely(!val)) { + val = json_encode_errormsg(&err_val); goto out; } - if (!auth_stratum(cs, proxi)) { - if (!pinging) { - LOGWARNING("Failed initial authorise to %s:%s with %s:%s !", - cs->url, cs->port, si->auth, si->pass); - } + json_get_int(&id, val, "id"); + proxy = proxy_by_id(gdata, id); + if (!proxy) { + val = json_errormsg("Proxy id %d not found", id); goto out; } - ret = true; -out: - if (!ret) { - /* Close and invalidate the file handle */ - Close(cs->fd); + JSON_CPACK(val, "{si,ss,ss,ss}", "id", proxy->id, "url", proxy->url, + "auth", proxy->auth, "pass", proxy->pass); + if (proxy->disabled != disable) { + proxy->disabled = disable; + LOGNOTICE("%sabling proxy %d:%s", disable ? "Dis" : "En", id, proxy->url); + } + if (disable) { + /* Set disabled bool here in case this is a parent proxy */ + proxy->disabled = true; + disable_subproxy(gdata, proxy, proxy); } else - keep_sockalive(cs->fd); - return ret; + reconnect_proxy(proxy); +out: + send_api_response(val, sockd); } -/* Cycle through the available proxies and find the first alive one */ -static proxy_instance_t *live_proxy(ckpool_t *ckp) +static void send_stats(gdata_t *gdata, const int sockd) { - proxy_instance_t *alive = NULL; - gdata_t *gdata = ckp->data; - connsock_t *cs; - int i, srvs; - - LOGDEBUG("Attempting to connect to proxy"); -retry: - if (!ping_main(ckp)) - goto out; + json_t *val = json_object(), *subval; + int total_objects, objects, generated; + proxy_instance_t *proxy; + stratum_msg_t *msg; + int64_t memsize; mutex_lock(&gdata->lock); - srvs = ckp->proxies; + objects = HASH_COUNT(gdata->proxies); + memsize = SAFE_HASH_OVERHEAD(gdata->proxies) + sizeof(proxy_instance_t) * objects; + generated = gdata->proxies_generated; + JSON_CPACK(subval, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); + json_set_object(val, "proxies", subval); + + DL_COUNT(gdata->dead_proxies, proxy, objects); + memsize = sizeof(proxy_instance_t) * objects; + JSON_CPACK(subval, "{si,si}", "count", objects, "memory", memsize); + json_set_object(val, "dead_proxies", subval); + + total_objects = memsize = 0; + for (proxy = gdata->proxies; proxy; proxy=proxy->hh.next) { + mutex_lock(&proxy->proxy_lock); + total_objects += objects = HASH_COUNT(proxy->subproxies); + memsize += SAFE_HASH_OVERHEAD(proxy->subproxies) + sizeof(proxy_instance_t) * objects; + mutex_unlock(&proxy->proxy_lock); + } + generated = gdata->subproxies_generated; mutex_unlock(&gdata->lock); - for (i = 0; i < srvs; i++) { - proxy_instance_t *proxi; - server_instance_t *si; + JSON_CPACK(subval, "{si,si,si}", "count", total_objects, "memory", memsize, "generated", generated); + json_set_object(val, "subproxies", subval); - mutex_lock(&gdata->lock); - si = ckp->servers[i]; - proxi = si->data; - mutex_unlock(&gdata->lock); + mutex_lock(&gdata->notify_lock); + objects = HASH_COUNT(gdata->notify_instances); + memsize = SAFE_HASH_OVERHEAD(gdata->notify_instances) + sizeof(notify_instance_t) * objects; + generated = gdata->proxy_notify_id; + mutex_unlock(&gdata->notify_lock); - cs = proxi->cs; - if (proxy_alive(ckp, si, proxi, cs, false)) { - alive = proxi; - break; - } + JSON_CPACK(subval, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); + json_set_object(val, "notifies", subval); + + mutex_lock(&gdata->share_lock); + objects = HASH_COUNT(gdata->shares); + memsize = SAFE_HASH_OVERHEAD(gdata->shares) + sizeof(share_msg_t) * objects; + generated = gdata->share_id; + mutex_unlock(&gdata->share_lock); + + JSON_CPACK(subval, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); + json_set_object(val, "shares", subval); + + mutex_lock(&gdata->psend_lock); + DL_COUNT(gdata->psends, msg, objects); + generated = gdata->psends_generated; + mutex_unlock(&gdata->psend_lock); + + memsize = sizeof(stratum_msg_t) * objects; + JSON_CPACK(subval, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); + json_set_object(val, "psends", subval); + + send_api_response(val, sockd); +} + +static json_t *proxystats(const proxy_instance_t *proxy) +{ + json_t *val; + + val = json_object(); + json_set_int(val, "id", proxy->id); + json_set_int(val, "userid", proxy->userid); + json_set_string(val, "url", proxy->url); + json_set_string(val, "auth", proxy->auth); + json_set_string(val, "pass", proxy->pass); + json_set_string(val, "enonce1", proxy->enonce1 ? proxy->enonce1 : ""); + json_set_int(val, "nonce1len", proxy->nonce1len); + json_set_int(val, "nonce2len", proxy->nonce2len); + json_set_double(val, "diff", proxy->diff); + if (parent_proxy(proxy)) { + json_set_double(val, "total_accepted", proxy->total_accepted); + json_set_double(val, "total_rejected", proxy->total_rejected); + json_set_int(val, "subproxies", proxy->subproxy_count); + } + json_set_double(val, "accepted", proxy->diff_accepted); + json_set_double(val, "rejected", proxy->diff_rejected); + json_set_int(val, "lastshare", proxy->last_share.tv_sec); + json_set_bool(val, "global", proxy->global); + json_set_bool(val, "disabled", proxy->disabled); + json_set_bool(val, "alive", proxy->alive); + json_set_int(val, "maxclients", proxy->clients_per_proxy); + return val; +} + +static void parse_proxystats(gdata_t *gdata, const int sockd, const char *buf) +{ + proxy_instance_t *proxy; + json_error_t err_val; + bool totals = false; + json_t *val = NULL; + int id, subid = 0; + + val = json_loads(buf, 0, &err_val); + if (unlikely(!val)) { + val = json_encode_errormsg(&err_val); + goto out; } - if (!alive) { - send_proc(ckp->connector, "reject"); - send_proc(ckp->stratifier, "dropall"); - LOGWARNING("Failed to connect to any servers as proxy, retrying in 5s!"); - sleep(5); - goto retry; + if (!json_get_int(&id, val, "id")) { + val = json_errormsg("Failed to find id key"); + goto out; } - - cs = alive->cs; - if (ckp->passthrough) { - LOGWARNING("Connected to upstream server %s:%s as proxy in passthrough mode", - cs->url, cs->port); - create_pthread(&alive->pth_precv, passthrough_recv, alive); - alive->passsends = create_ckmsgq(ckp, "passsend", &passthrough_send); - } else { - LOGNOTICE("Connected to upstream server %s:%s as proxy", cs->url, cs->port); - create_pthread(&alive->pth_precv, proxy_recv, alive); - mutex_init(&alive->psend_lock); - cond_init(&alive->psend_cond); - create_pthread(&alive->pth_psend, proxy_send, alive); + if (!json_get_int(&subid, val, "subid")) + totals = true; + proxy = proxy_by_id(gdata, id); + if (!proxy) { + val = json_errormsg("Proxy id %d not found", id); + goto out; + } + if (!totals) + proxy = subproxy_by_id(proxy, subid); + if (!proxy) { + val = json_errormsg("Proxy id %d:%d not found", id, subid); + goto out; } + val = proxystats(proxy); out: - send_proc(ckp->connector, alive ? "accept" : "reject"); - return alive; + send_api_response(val, sockd); } -static void kill_proxy(ckpool_t *ckp, proxy_instance_t *proxi) +static void parse_globaluser(ckpool_t *ckp, gdata_t *gdata, const char *buf) { - notify_instance_t *ni, *tmp; - connsock_t *cs; + char *url, *username, *pass = strdupa(buf); + int userid = -1, proxyid = -1; + proxy_instance_t *proxy, *tmp; + int64_t clientid = -1; + bool found = false; + + sscanf(buf, "%d:%d:%"PRId64":%s", &proxyid, &userid, &clientid, pass); + if (unlikely(clientid < 0 || userid < 0 || proxyid < 0)) { + LOGWARNING("Failed to parse_globaluser ids from command %s", buf); + return; + } + username = strsep(&pass, ","); + if (unlikely(!username)) { + LOGWARNING("Failed to parse_globaluser username from command %s", buf); + return; + } - send_proc(ckp->stratifier, "reconnect"); - send_proc(ckp->connector, "reject"); + LOGDEBUG("Checking userproxy proxy %d user %d:%"PRId64" worker %s pass %s", + proxyid, userid, clientid, username, pass); - if (!proxi) // This shouldn't happen + if (unlikely(proxyid >= ckp->proxies)) { + LOGWARNING("Trying to find non-existent proxy id %d in parse_globaluser", proxyid); return; + } - LOGNOTICE("Killing proxy connection to %s", proxi->si->url); - cs = proxi->cs; - Close(cs->fd); - empty_buffer(cs); - - /* All our notify data is invalid if we reconnect so discard them */ - mutex_lock(&proxi->notify_lock); - HASH_ITER(hh, proxi->notify_instances, ni, tmp) { - HASH_DEL(proxi->notify_instances, ni); - clear_notify(ni); + mutex_lock(&gdata->lock); + url = ckp->proxyurl[proxyid]; + HASH_ITER(hh, gdata->proxies, proxy, tmp) { + if (!strcmp(proxy->auth, username)) { + found = true; + break; + } } - mutex_unlock(&proxi->notify_lock); + mutex_unlock(&gdata->lock); - pthread_cancel(proxi->pth_precv); - pthread_cancel(proxi->pth_psend); + if (found) + return; + add_userproxy(ckp, gdata, userid, url, username, pass); } static int proxy_loop(proc_instance_t *pi) { - int sockd = -1, ret = 0, selret; - proxy_instance_t *proxi = NULL; - bool reconnecting = false; - unixsock_t *us = &pi->us; + proxy_instance_t *proxi = NULL, *cproxy; + server_instance_t *si = NULL, *old_si; ckpool_t *ckp = pi->ckp; gdata_t *gdata = ckp->data; + unix_msg_t *umsg = NULL; + connsock_t *cs = NULL; + bool started = false; char *buf = NULL; + int ret = 0; reconnect: - if (proxi) { - kill_proxy(ckp, proxi); - reconnecting = true; + clear_unix_msg(&umsg); + + if (ckp->node) { + old_si = si; + si = live_server(ckp); + if (!si) + goto out; + cs = &si->cs; + if (!old_si) + LOGWARNING("Connected to bitcoind: %s:%s", cs->url, cs->port); + else if (si != old_si) + LOGWARNING("Failed over to bitcoind: %s:%s", cs->url, cs->port); } - proxi = live_proxy(ckp); - if (!proxi) + + /* This does not necessarily mean we reconnect, but a change has + * occurred and we need to reexamine the proxies. */ + cproxy = wait_best_proxy(ckp, gdata); + if (!cproxy) goto out; - if (reconnecting) { - reconnecting = false; - connsock_t *cs = proxi->cs; - LOGWARNING("Successfully reconnected to %s:%s as proxy", - cs->url, cs->port); + if (proxi != cproxy) { + proxi = cproxy; + LOGWARNING("Successfully connected to pool %d %s as proxy%s", + proxi->id, proxi->url, ckp->passthrough ? " in passthrough mode" : ""); } - /* We've just subscribed and authorised so tell the stratifier to - * retrieve the first subscription. */ - if (!ckp->passthrough) { - send_proc(ckp->stratifier, "subscribe"); - send_proc(ckp->stratifier, "notify"); - proxi->notified = false; + if (unlikely(!started)) { + started = true; + LOGWARNING("%s generator ready", ckp->name); } - retry: - ckmsgq_add(gdata->srvchk, proxi->si); - + clear_unix_msg(&umsg); do { - selret = wait_read_select(us->sockd, 5); - if (!selret && !ping_main(ckp)) { + umsg = get_unix_msg(pi); + if (unlikely(!umsg &&!ping_main(ckp))) { LOGEMERG("Generator failed to ping main process, exiting"); ret = 1; goto out; } - } while (selret < 1); + } while (!umsg); - if (unlikely(proxi->cs->fd < 0)) { - LOGWARNING("Upstream socket invalidated, will attempt failover"); - goto reconnect; - } - - sockd = accept(us->sockd, NULL, NULL); - if (sockd < 0) { - LOGEMERG("Failed to accept on proxy socket"); - ret = 1; - goto out; - } - dealloc(buf); - buf = recv_unix_msg(sockd); - if (!buf) { - LOGWARNING("Failed to get message in proxy_loop"); - Close(sockd); - goto retry; - } + buf = umsg->buf; LOGDEBUG("Proxy received request: %s", buf); - if (cmdmatch(buf, "shutdown")) { + if (likely(buf[0] == '{')) { + if (ckp->passthrough) + passthrough_add_send(proxi, buf); + else { + /* Anything remaining should be share submissions */ + json_t *val = json_loads(buf, 0, NULL); + + if (unlikely(!val)) + LOGWARNING("Generator received invalid json message: %s", buf); + else + submit_share(gdata, val); + } + } else if (cmdmatch(buf, "stats")) { + send_stats(gdata, umsg->sockd); + } else if (cmdmatch(buf, "list")) { + send_list(gdata, umsg->sockd); + } else if (cmdmatch(buf, "sublist")) { + send_sublist(gdata, umsg->sockd, buf + 8); + } else if (cmdmatch(buf, "addproxy")) { + parse_addproxy(ckp, gdata, umsg->sockd, buf + 9); + } else if (cmdmatch(buf, "delproxy")) { + parse_delproxy(ckp, gdata, umsg->sockd, buf + 9); + } else if (cmdmatch(buf, "enableproxy")) { + parse_ableproxy(gdata, umsg->sockd, buf + 12, false); + } else if (cmdmatch(buf, "disableproxy")) { + parse_ableproxy(gdata, umsg->sockd, buf + 13, true); + } else if (cmdmatch(buf, "proxystats")) { + parse_proxystats(gdata, umsg->sockd, buf + 11); + } else if (cmdmatch(buf, "globaluser")) { + parse_globaluser(ckp, gdata, buf + 11); + } else if (cmdmatch(buf, "shutdown")) { ret = 0; goto out; - } else if (cmdmatch(buf, "getsubscribe")) { - send_subscribe(proxi, &sockd); - } else if (cmdmatch(buf, "getnotify")) { - send_notify(proxi, &sockd); - } else if (cmdmatch(buf, "getdiff")) { - send_diff(proxi, &sockd); } else if (cmdmatch(buf, "reconnect")) { goto reconnect; } else if (cmdmatch(buf, "submitblock:")) { + char blockmsg[80]; + bool ret; + LOGNOTICE("Submitting likely block solve share to upstream pool"); + ret = submit_block(cs, buf + 12 + 64 + 1); + memset(buf + 12 + 64, 0, 1); + sprintf(blockmsg, "%sblock:%s", ret ? "" : "no", buf + 12); + send_proc(ckp->stratifier, blockmsg); } else if (cmdmatch(buf, "loglevel")) { sscanf(buf, "loglevel=%d", &ckp->loglevel); } else if (cmdmatch(buf, "ping")) { LOGDEBUG("Proxy received ping request"); - send_unix_msg(sockd, "pong"); - } else if (ckp->passthrough) { - /* Anything remaining should be stratum messages */ - passthrough_add_send(proxi, buf); + send_unix_msg(umsg->sockd, "pong"); + } else if (cmdmatch(buf, "recruit")) { + recruit_subproxy(gdata, buf); + } else if (cmdmatch(buf, "dropproxy")) { + drop_proxy(gdata, buf); } else { - /* Anything remaining should be share submissions */ - json_t *val = json_loads(buf, 0, NULL); - - if (!val) - LOGWARNING("Received unrecognised message: %s", buf); - else - submit_share(proxi, val); + LOGWARNING("Generator received unrecognised message: %s", buf); } - Close(sockd); goto retry; out: - kill_proxy(ckp, proxi); - Close(sockd); return ret; } @@ -1718,14 +2884,14 @@ static void *server_watchdog(void *arg) return NULL; } -static int server_mode(ckpool_t *ckp, proc_instance_t *pi) +static void setup_servers(ckpool_t *ckp) { pthread_t pth_watchdog; - server_instance_t *si; - int i, ret; + int i; ckp->servers = ckalloc(sizeof(server_instance_t *) * ckp->btcds); for (i = 0; i < ckp->btcds; i++) { + server_instance_t *si; connsock_t *cs; ckp->servers[i] = ckzalloc(sizeof(server_instance_t)); @@ -1741,10 +2907,19 @@ static int server_mode(ckpool_t *ckp, proc_instance_t *pi) } create_pthread(&pth_watchdog, server_watchdog, ckp); +} + +static int server_mode(ckpool_t *ckp, proc_instance_t *pi) +{ + int i, ret; + + setup_servers(ckp); + ret = gen_loop(pi); for (i = 0; i < ckp->btcds; i++) { - si = ckp->servers[i]; + server_instance_t *si = ckp->servers[i]; + kill_server(si); dealloc(si); } @@ -1752,110 +2927,60 @@ static int server_mode(ckpool_t *ckp, proc_instance_t *pi) return ret; } +static proxy_instance_t *__add_proxy(ckpool_t *ckp, gdata_t *gdata, const int id) +{ + proxy_instance_t *proxy; + + gdata->proxies_generated++; + proxy = ckzalloc(sizeof(proxy_instance_t)); + proxy->id = id; + proxy->url = strdup(ckp->proxyurl[id]); + proxy->auth = strdup(ckp->proxyauth[id]); + if (proxy->pass) + proxy->pass = strdup(ckp->proxypass[id]); + else + proxy->pass = strdup(""); + proxy->ckp = proxy->cs.ckp = ckp; + HASH_ADD_INT(gdata->proxies, id, proxy); + proxy->global = true; + cksem_init(&proxy->cs.sem); + cksem_post(&proxy->cs.sem); + return proxy; +} + static int proxy_mode(ckpool_t *ckp, proc_instance_t *pi) { gdata_t *gdata = ckp->data; - proxy_instance_t *proxi; - server_instance_t *si; + proxy_instance_t *proxy; int i, ret; mutex_init(&gdata->lock); + mutex_init(&gdata->notify_lock); + mutex_init(&gdata->share_lock); + + if (ckp->node) + setup_servers(ckp); /* Create all our proxy structures and pointers */ - ckp->servers = ckalloc(sizeof(server_instance_t *) * ckp->proxies); for (i = 0; i < ckp->proxies; i++) { - ckp->servers[i] = ckzalloc(sizeof(server_instance_t)); - si = ckp->servers[i]; - si->id = i; - si->url = strdup(ckp->proxyurl[i]); - si->auth = strdup(ckp->proxyauth[i]); - si->pass = strdup(ckp->proxypass[i]); - proxi = ckzalloc(sizeof(proxy_instance_t)); - si->data = proxi; - proxi->auth = si->auth; - proxi->pass = si->pass; - proxi->si = si; - proxi->ckp = ckp; - proxi->cs = &si->cs; - mutex_init(&proxi->notify_lock); - mutex_init(&proxi->share_lock); - } - - LOGWARNING("%s generator ready", ckp->name); + proxy = __add_proxy(ckp, gdata, i); + if (ckp->passthrough) { + create_pthread(&proxy->pth_precv, passthrough_recv, proxy); + proxy->passsends = create_ckmsgq(ckp, "passsend", &passthrough_send); + } else { + prepare_proxy(proxy); + create_pthread(&gdata->pth_uprecv, userproxy_recv, ckp); + mutex_init(&gdata->psend_lock); + cond_init(&gdata->psend_cond); + create_pthread(&gdata->pth_psend, proxy_send, ckp); + } + } ret = proxy_loop(pi); - mutex_lock(&gdata->lock); - for (i = 0; i < ckp->proxies; i++) { - si = ckp->servers[i]; - Close(si->cs.fd); - proxi = si->data; - free(proxi->enonce1); - free(proxi->enonce1bin); - free(proxi->sessionid); - pthread_cancel(proxi->pth_psend); - pthread_cancel(proxi->pth_precv); - join_pthread(proxi->pth_psend); - join_pthread(proxi->pth_precv); - dealloc(si->data); - dealloc(si->url); - dealloc(si->auth); - dealloc(si->pass); - dealloc(si); - } - mutex_unlock(&gdata->lock); - - dealloc(ckp->servers); return ret; } -static void proxy_watchdog(ckpool_t *ckp, server_instance_t *cursi) -{ - gdata_t *gdata = ckp->data; - static time_t last_t = 0; - bool alive = false; - time_t now_t; - int i, srvs; - - /* Rate limit to checking only once every 5 seconds */ - now_t = time(NULL); - if (now_t <= last_t + 5) - return; - - last_t = now_t; - - /* Is this the highest priority server already? */ - if (!cursi->id) - return; - - mutex_lock(&gdata->lock); - srvs = ckp->proxies; - mutex_unlock(&gdata->lock); - - for (i = 0; i < srvs; i++) { - proxy_instance_t *proxi; - server_instance_t *si; - connsock_t *cs; - - mutex_lock(&gdata->lock); - si = ckp->servers[i]; - proxi = si->data; - mutex_unlock(&gdata->lock); - - /* Have we reached the current server? */ - if (si == cursi) - return; - - cs = proxi->cs; - alive = proxy_alive(ckp, si, proxi, cs, true); - if (alive) - break; - } - if (alive) - send_proc(ckp->generator, "reconnect"); -} - - int generator(proc_instance_t *pi) { ckpool_t *ckp = pi->ckp; @@ -1865,12 +2990,26 @@ int generator(proc_instance_t *pi) LOGWARNING("%s generator starting", ckp->name); gdata = ckzalloc(sizeof(gdata_t)); ckp->data = gdata; + gdata->ckp = ckp; + create_unix_receiver(pi); + if (ckp->proxy) { - gdata->srvchk = create_ckmsgq(ckp, "prxchk", &proxy_watchdog); + char *buf = NULL; + + /* Wait for the stratifier to be ready for us */ + do { + if (!ping_main(ckp)) { + ret = 1; + goto out; + } + cksleep_ms(10); + buf = send_recv_proc(ckp->stratifier, "ping"); + } while (!buf); + dealloc(buf); ret = proxy_mode(ckp, pi); } else ret = server_mode(ckp, pi); - +out: dealloc(ckp->data); return process_exit(ckp, pi, ret); } diff --git a/src/libckpool.c b/src/libckpool.c index 092b93bd..2e78d330 100644 --- a/src/libckpool.c +++ b/src/libckpool.c @@ -747,12 +747,12 @@ int write_socket(int fd, const void *buf, size_t nbyte) if (!ret) LOGNOTICE("Select timed out in write_socket"); else - LOGERR("Select failed in write_socket"); + LOGNOTICE("Select failed in write_socket"); goto out; } ret = write_length(fd, buf, nbyte); if (ret < 0) - LOGWARNING("Failed to write in write_socket"); + LOGNOTICE("Failed to write in write_socket"); out: return ret; } @@ -1389,6 +1389,18 @@ void *_ckzalloc(size_t len, const char *file, const char *func, const int line) return ptr; } +/* Round up to the nearest page size for efficient malloc */ +size_t round_up_page(size_t len) +{ + int rem = len % PAGESIZE; + + if (rem) + len += PAGESIZE - rem; + return len; +} + + + /* Adequate size s==len*2 + 1 must be alloced to use this variant */ void __bin2hex(void *vs, const void *vp, size_t len) { @@ -1976,7 +1988,10 @@ double diff_from_nbits(char *nbits) if (powdiff < 8) powdiff = 8; diff32 = be32toh(*((uint32_t *)nbits)) & 0x00FFFFFF; - numerator = 0xFFFFULL << powdiff; + if (likely(powdiff > 0)) + numerator = 0xFFFFULL << powdiff; + else + numerator = 0xFFFFULL >> -powdiff; return numerator / (double)diff32; } diff --git a/src/libckpool.h b/src/libckpool.h index 9d8ba341..10c18248 100644 --- a/src/libckpool.h +++ b/src/libckpool.h @@ -531,6 +531,8 @@ void trail_slash(char **buf); void *_ckalloc(size_t len, const char *file, const char *func, const int line); void *json_ckalloc(size_t size); void *_ckzalloc(size_t len, const char *file, const char *func, const int line); +size_t round_up_page(size_t len); + extern const int hex2bin_tbl[]; void __bin2hex(void *vs, const void *vp, size_t len); void *bin2hex(const void *vp, size_t len); diff --git a/src/stratifier.c b/src/stratifier.c index 26e931ab..011cefc9 100644 --- a/src/stratifier.c +++ b/src/stratifier.c @@ -135,7 +135,7 @@ struct workbase { char *logdir; ckpool_t *ckp; - bool proxy; + bool proxy; /* This workbase is proxied work */ }; typedef struct workbase workbase_t; @@ -168,7 +168,7 @@ typedef struct stratum_instance stratum_instance_t; struct user_instance { UT_hash_handle hh; char username[128]; - int64_t id; + int id; char *secondaryuserid; bool btcaddress; @@ -225,6 +225,10 @@ struct worker_instance { bool notified_idle; }; +typedef struct stratifier_data sdata_t; + +typedef struct proxy_base proxy_t; + /* Per client stratum instance == workers */ struct stratum_instance { UT_hash_handle hh; @@ -260,6 +264,7 @@ struct stratum_instance { time_t start_time; char address[INET6_ADDRSTRLEN]; + bool node; /* Is this a mining node */ bool subscribed; bool authorising; /* In progress, protected by instance_lock */ bool authorised; @@ -269,12 +274,16 @@ struct stratum_instance { * or other problem and should be dropped lazily if * this is set to 2 */ + bool reconnect; /* This client really needs to reconnect */ + time_t reconnect_request; /* The time we sent a reconnect message */ + user_instance_t *user_instance; worker_instance_t *worker_instance; char *useragent; char *workername; - int64_t user_id; + char *password; + int user_id; int server; /* Which server is this instance bound to */ ckpool_t *ckp; @@ -284,6 +293,11 @@ struct stratum_instance { int64_t suggest_diff; /* Stratum client suggested diff */ double best_diff; /* Best share found by this instance */ + + sdata_t *sdata; /* Which sdata this client is bound to */ + proxy_t *proxy; /* Proxy this is bound to in proxy mode */ + int proxyid; /* Which proxy id */ + int subproxyid; /* Which subproxy */ }; struct share { @@ -294,6 +308,50 @@ struct share { typedef struct share share_t; +struct proxy_base { + UT_hash_handle hh; + UT_hash_handle sh; /* For subproxy hashlist */ + proxy_t *next; /* For retired subproxies */ + proxy_t *prev; + int id; + int subid; + + /* Priority has the user id encoded in the high bits if it's not a + * global proxy. */ + int64_t priority; + + bool global; /* Is this a global proxy */ + int userid; /* Userid for non global proxies */ + + double diff; + + char url[128]; + char auth[128]; + char pass[128]; + char enonce1[32]; + uchar enonce1bin[16]; + int enonce1constlen; + int enonce1varlen; + + int nonce2len; + int enonce2varlen; + + bool subscribed; + bool notified; + + int64_t clients; /* Incrementing client count */ + int64_t max_clients; /* Maximum number of clients per subproxy */ + int64_t bound_clients; /* Currently actively bound clients */ + int64_t combined_clients; /* Total clients of all subproxies of a parent proxy */ + int64_t headroom; /* Temporary variable when calculating how many more clients can bind */ + + int subproxy_count; /* Number of subproxies */ + proxy_t *parent; /* Parent proxy of each subproxy */ + proxy_t *subproxies; /* Hashlist of subproxies sorted by subid */ + sdata_t *sdata; /* Unique stratifer data for each subproxy */ + bool dead; +}; + typedef struct session session_t; struct session { @@ -301,7 +359,9 @@ struct session { int session_id; uint64_t enonce1_64; int64_t client_id; + int userid; time_t added; + char address[INET6_ADDRSTRLEN]; }; #define ID_AUTH 0 @@ -344,6 +404,8 @@ static const char *ckdb_seq_names[] = { #define ID_COUNT (sizeof(ckdb_ids)/sizeof(char *)) struct stratifier_data { + ckpool_t *ckp; + char pubkeytxnbin[25]; int pubkeytxnlen; char donkeytxnbin[25]; @@ -363,14 +425,9 @@ struct stratifier_data { uint64_t ckdb_seq_ids[ID_COUNT]; bool ckdb_offline; + bool verbose; - /* Variable length enonce1 always refers back to a u64 */ - union { - uint64_t u64; - uint32_t u32; - uint16_t u16; - uint8_t u8; - } enonce1u; + uint64_t enonce1_64; /* For protecting the hashtable data */ cklock_t workbase_lock; @@ -398,10 +455,11 @@ struct stratifier_data { ckmsgq_t *sauthq; // Stratum authorisations ckmsgq_t *stxnq; // Transaction requests - int64_t user_instance_id; + int user_instance_id; stratum_instance_t *stratum_instances; stratum_instance_t *recycled_instances; + stratum_instance_t *node_instances; int stratum_generated; int disconnected_generated; @@ -426,23 +484,13 @@ struct stratifier_data { /* Generator message priority */ int gen_priority; - struct { - double diff; - - char enonce1[32]; - uchar enonce1bin[16]; - int enonce1constlen; - int enonce1varlen; - - int nonce2len; - int enonce2varlen; - - bool subscribed; - } proxy_base; + int proxy_count; /* Total proxies generated (not necessarily still alive) */ + proxy_t *proxy; /* Current proxy in use */ + proxy_t *proxies; /* Hashlist of all proxies */ + mutex_t proxy_lock; /* Protects all proxy data */ + proxy_t *subproxy; /* Which subproxy this sdata belongs to in proxy mode */ }; -typedef struct stratifier_data sdata_t; - typedef struct json_entry json_entry_t; struct json_entry { @@ -479,6 +527,18 @@ static void notice_msg_entries(char_entry_t **entries) } } +static void info_msg_entries(char_entry_t **entries) +{ + char_entry_t *entry, *tmpentry; + + DL_FOREACH_SAFE(*entries, entry, tmpentry) { + DL_DELETE(*entries, entry); + LOGINFO("%s", entry->buf); + free(entry->buf); + free(entry); + } +} + static void generate_coinbase(const ckpool_t *ckp, workbase_t *wb) { uint64_t *u64, g64, d64 = 0; @@ -594,7 +654,7 @@ static void generate_coinbase(const ckpool_t *ckp, workbase_t *wb) hex2bin(wb->headerbin, header, 112); } -static void stratum_broadcast_update(sdata_t *sdata, bool clean); +static void stratum_broadcast_update(sdata_t *sdata, const workbase_t *wb, bool clean); static void clear_workbase(workbase_t *wb) { @@ -725,7 +785,73 @@ static void _ckdbq_add(ckpool_t *ckp, const int idtype, json_t *val, const char #define ckdbq_add(ckp, idtype, val) _ckdbq_add(ckp, idtype, val, __FILE__, __func__, __LINE__) -static void send_workinfo(ckpool_t *ckp, const workbase_t *wb) +static void stratum_add_send(sdata_t *sdata, json_t *val, const int64_t client_id, + const int msg_type); + +static void send_node_workinfo(sdata_t *sdata, const workbase_t *wb) +{ + stratum_instance_t *client; + ckmsg_t *bulk_send = NULL; + + ck_rlock(&sdata->instance_lock); + if (sdata->node_instances) { + json_t *wb_val = json_object(); + + json_set_int(wb_val, "jobid", wb->id); + json_set_string(wb_val, "target", wb->target); + json_set_double(wb_val, "diff", wb->diff); + json_set_int(wb_val, "version", wb->version); + json_set_int(wb_val, "curtime", wb->curtime); + json_set_string(wb_val, "prevhash", wb->prevhash); + json_set_string(wb_val, "ntime", wb->ntime); + json_set_string(wb_val, "bbversion", wb->bbversion); + json_set_string(wb_val, "nbit", wb->nbit); + json_set_int(wb_val, "coinbasevalue", wb->coinbasevalue); + json_set_int(wb_val, "height", wb->height); + json_set_string(wb_val, "flags", wb->flags); + json_set_int(wb_val, "transactions", wb->transactions); + if (likely(wb->transactions)) + json_set_string(wb_val, "txn_data", wb->txn_data); + /* We don't need txn_hashes */ + json_set_int(wb_val, "merkles", wb->merkles); + json_object_set_new_nocheck(wb_val, "merklehash", json_deep_copy(wb->merkle_array)); + json_set_string(wb_val, "coinb1", wb->coinb1); + json_set_int(wb_val, "enonce1varlen", wb->enonce1varlen); + json_set_int(wb_val, "enonce2varlen", wb->enonce2varlen); + json_set_int(wb_val, "coinb1len", wb->coinb1len); + json_set_int(wb_val, "coinb2len", wb->coinb2len); + json_set_string(wb_val, "coinb2", wb->coinb2); + + DL_FOREACH(sdata->node_instances, client) { + ckmsg_t *client_msg; + smsg_t *msg; + json_t *json_msg = json_deep_copy(wb_val); + + json_set_string(json_msg, "node.method", stratum_msgs[SM_WORKINFO]); + client_msg = ckalloc(sizeof(ckmsg_t)); + msg = ckzalloc(sizeof(smsg_t)); + msg->json_msg = json_msg; + msg->client_id = client->id; + client_msg->data = msg; + DL_APPEND(bulk_send, client_msg); + } + json_decref(wb_val); + } + ck_runlock(&sdata->instance_lock); + + if (bulk_send) { + ckmsgq_t *ssends = sdata->ssends; + + LOGINFO("Sending workinfo to mining nodes"); + + mutex_lock(ssends->lock); + DL_CONCAT(ssends->msgs, bulk_send); + pthread_cond_signal(ssends->cond); + mutex_unlock(ssends->lock); + } +} + +static void send_workinfo(ckpool_t *ckp, sdata_t *sdata, const workbase_t *wb) { char cdfield[64]; json_t *val; @@ -749,6 +875,7 @@ static void send_workinfo(ckpool_t *ckp, const workbase_t *wb) "createcode", __func__, "createinet", ckp->serverurl[0]); ckdbq_add(ckp, ID_WORKINFO, val); + send_node_workinfo(sdata, wb); } static void send_ageworkinfo(ckpool_t *ckp, const int64_t id) @@ -770,10 +897,12 @@ static void send_ageworkinfo(ckpool_t *ckp, const int64_t id) ckdbq_add(ckp, ID_AGEWORKINFO, val); } -static void add_base(ckpool_t *ckp, workbase_t *wb, bool *new_block) +/* Add a new workbase to the table of workbases. Sdata is the global data in + * pool mode but unique to each subproxy in proxy mode */ +static void add_base(ckpool_t *ckp, sdata_t *sdata, workbase_t *wb, bool *new_block) { workbase_t *tmp, *tmpa, *aged = NULL; - sdata_t *sdata = ckp->data; + sdata_t *ckp_sdata = ckp->data; int len, ret; ts_realtime(&wb->gentime); @@ -786,7 +915,7 @@ static void add_base(ckpool_t *ckp, workbase_t *wb, bool *new_block) * we set workbase_id from it. In server mode the stratifier is * setting the workbase_id */ ck_wlock(&sdata->workbase_lock); - sdata->workbases_generated++; + ckp_sdata->workbases_generated++; if (!ckp->proxy) wb->id = sdata->workbase_id++; else @@ -811,6 +940,9 @@ static void add_base(ckpool_t *ckp, workbase_t *wb, bool *new_block) if (ckp->logshares) sprintf(wb->logdir, "%s%08x/%s", ckp->logdir, wb->height, wb->idstring); + /* Is this long enough to ensure we don't dereference a workbase + * immediately? Should be unless clock changes 10 minutes so we use + * ts_realtime */ HASH_ITER(hh, sdata->workbases, tmp, tmpa) { if (HASH_COUNT(sdata->workbases) < 3) break; @@ -827,12 +959,11 @@ static void add_base(ckpool_t *ckp, workbase_t *wb, bool *new_block) sdata->current_workbase = wb; ck_wunlock(&sdata->workbase_lock); - if (*new_block) { - LOGNOTICE("Block hash changed to %s", sdata->lastswaphash); + if (*new_block) purge_share_hashtable(sdata, wb->id); - } - send_workinfo(ckp, wb); + if (!ckp->passthrough) + send_workinfo(ckp, sdata, wb); /* Send the aged work message to ckdb once we have dropped the workbase lock * to prevent taking recursive locks */ @@ -879,7 +1010,7 @@ static char *send_recv_generator(ckpool_t *ckp, const char *msg, const int prio) return buf; } -static void send_generator(ckpool_t *ckp, const char *msg, const int prio) +static void send_generator(const ckpool_t *ckp, const char *msg, const int prio) { sdata_t *sdata = ckp->data; bool set; @@ -978,7 +1109,7 @@ retry: json_decref(val); generate_coinbase(ckp, wb); - add_base(ckp, wb, &new_block); + add_base(ckp, sdata, wb, &new_block); /* Reset the update time to avoid stacked low priority notifies. Bring * forward the next notify in case of a new block. */ now_t = time(NULL); @@ -986,7 +1117,9 @@ retry: now_t -= ckp->update_interval / 2; sdata->update_time = now_t; - stratum_broadcast_update(sdata, new_block); + if (new_block) + LOGNOTICE("Block hash changed to %s", sdata->lastswaphash); + stratum_broadcast_update(sdata, wb, new_block); ret = true; LOGINFO("Broadcast updated stratum base"); out: @@ -1004,6 +1137,63 @@ out: return NULL; } +static void add_node_base(ckpool_t *ckp, json_t *val) +{ + workbase_t *wb = ckzalloc(sizeof(workbase_t)); + sdata_t *sdata = ckp->data; + bool new_block = false; + char header[228]; + int i; + + wb->ckp = ckp; + json_int64cpy(&wb->id, val, "jobid"); + json_strcpy(wb->target, val, "target"); + json_dblcpy(&wb->diff, val, "diff"); + json_uintcpy(&wb->version, val, "version"); + json_uintcpy(&wb->curtime, val, "curtime"); + json_strcpy(wb->prevhash, val, "prevhash"); + json_strcpy(wb->ntime, val, "ntime"); + sscanf(wb->ntime, "%x", &wb->ntime32); + json_strcpy(wb->bbversion, val, "bbversion"); + json_strcpy(wb->nbit, val, "nbit"); + json_uint64cpy(&wb->coinbasevalue, val, "coinbasevalue"); + json_intcpy(&wb->height, val, "height"); + json_strdup(&wb->flags, val, "flags"); + json_intcpy(&wb->transactions, val, "transactions"); + if (wb->transactions) + json_strdup(&wb->txn_data, val, "txn_data"); + json_intcpy(&wb->merkles, val, "merkles"); + wb->merkle_array = json_object_dup(val, "merklehash"); + for (i = 0; i < wb->merkles; i++) { + strcpy(&wb->merklehash[i][0], json_string_value(json_array_get(wb->merkle_array, i))); + hex2bin(&wb->merklebin[i][0], &wb->merklehash[i][0], 32); + } + json_strdup(&wb->coinb1, val, "coinb1"); + json_intcpy(&wb->coinb1len, val, "coinb1len"); + wb->coinb1bin = ckzalloc(wb->coinb1len); + hex2bin(wb->coinb1bin, wb->coinb1, wb->coinb1len); + json_strdup(&wb->coinb2, val, "coinb2"); + json_intcpy(&wb->coinb2len, val, "coinb2len"); + wb->coinb2bin = ckzalloc(wb->coinb2len); + hex2bin(wb->coinb2bin, wb->coinb2, wb->coinb2len); + json_intcpy(&wb->enonce1varlen, val, "enonce1varlen"); + json_intcpy(&wb->enonce2varlen, val, "enonce2varlen"); + ts_realtime(&wb->gentime); + + snprintf(header, 225, "%s%s%s%s%s%s%s", + wb->bbversion, wb->prevhash, + "0000000000000000000000000000000000000000000000000000000000000000", + wb->ntime, wb->nbit, + "00000000", /* nonce */ + workpadding); + LOGDEBUG("Header: %s", header); + hex2bin(wb->headerbin, header, 112); + + add_base(ckp, sdata, wb, &new_block); + if (new_block) + LOGNOTICE("Block hash changed to %s", sdata->lastswaphash); +} + static void update_base(ckpool_t *ckp, const int prio) { struct update_req *ur = ckalloc(sizeof(struct update_req)); @@ -1019,7 +1209,12 @@ static void update_base(ckpool_t *ckp, const int prio) * clients allowing us to reuse it instead of callocing a new one */ static void __kill_instance(sdata_t *sdata, stratum_instance_t *client) { + if (client->proxy) { + client->proxy->bound_clients--; + client->proxy->parent->combined_clients--; + } free(client->workername); + free(client->password); free(client->useragent); memset(client, 0, sizeof(stratum_instance_t)); DL_APPEND(sdata->recycled_instances, client); @@ -1064,7 +1259,9 @@ static void __disconnect_session(sdata_t *sdata, const stratum_instance_t *clien session->enonce1_64 = client->enonce1_64; session->session_id = client->session_id; session->client_id = client->id; + session->userid = client->user_id; session->added = now_t; + strcpy(session->address, client->address); HASH_ADD_INT(sdata->disconnected_sessions, session_id, session); sdata->stats.disconnected++; sdata->disconnected_generated++; @@ -1083,12 +1280,20 @@ static void __del_client(sdata_t *sdata, stratum_instance_t *client) } } +static void connector_drop_client(ckpool_t *ckp, const int64_t id) +{ + char buf[256]; + + LOGDEBUG("Stratifier requesting connector drop client %"PRId64, id); + snprintf(buf, 255, "dropclient=%"PRId64, id); + send_proc(ckp->connector, buf); +} + static void drop_allclients(ckpool_t *ckp) { stratum_instance_t *client, *tmp; sdata_t *sdata = ckp->data; int kills = 0; - char buf[128]; ck_wlock(&sdata->instance_lock); HASH_ITER(hh, sdata->stratum_instances, client, tmp) { @@ -1100,8 +1305,7 @@ static void drop_allclients(ckpool_t *ckp) } else client->dropped = true; kills++; - sprintf(buf, "dropclient=%"PRId64, client_id); - send_proc(ckp->connector, buf); + connector_drop_client(ckp, client_id); } sdata->stats.users = sdata->stats.workers = 0; ck_wunlock(&sdata->instance_lock); @@ -1110,793 +1314,1973 @@ static void drop_allclients(ckpool_t *ckp) LOGNOTICE("Dropped %d instances for dropall request", kills); } -static void update_subscribe(ckpool_t *ckp) +/* Copy only the relevant parts of the master sdata for each subproxy */ +static sdata_t *duplicate_sdata(const sdata_t *sdata) { - sdata_t *sdata = ckp->data; - json_t *val; - char *buf; + sdata_t *dsdata = ckzalloc(sizeof(sdata_t)); + + dsdata->ckp = sdata->ckp; + + /* Copy the transaction binaries for workbase creation */ + memcpy(dsdata->pubkeytxnbin, sdata->pubkeytxnbin, 25); + memcpy(dsdata->donkeytxnbin, sdata->donkeytxnbin, 25); + + /* Use the same work queues for all subproxies */ + dsdata->ssends = sdata->ssends; + dsdata->srecvs = sdata->srecvs; + dsdata->ckdbq = sdata->ckdbq; + dsdata->sshareq = sdata->sshareq; + dsdata->sauthq = sdata->sauthq; + dsdata->stxnq = sdata->stxnq; + + /* Give the sbuproxy its own workbase list and lock */ + cklock_init(&dsdata->workbase_lock); + cksem_init(&dsdata->update_sem); + cksem_post(&dsdata->update_sem); + return dsdata; +} - buf = send_recv_proc(ckp->generator, "getsubscribe"); - if (unlikely(!buf)) { - LOGWARNING("Failed to get subscribe from generator in update_notify"); - drop_allclients(ckp); - return; +static int64_t prio_sort(proxy_t *a, proxy_t *b) +{ + return (a->priority - b->priority); +} + +/* Priority values can be sparse, they do not need to be sequential */ +static void __set_proxy_prio(sdata_t *sdata, proxy_t *proxy, int64_t priority) +{ + proxy_t *tmpa, *tmpb, *exists = NULL; + int64_t next_prio = 0; + + /* Encode the userid as the high bits in priority */ + if (!proxy->global) { + int64_t high_bits = proxy->userid; + + high_bits <<= 32; + priority |= high_bits; } - LOGDEBUG("Update subscribe: %s", buf); - val = json_loads(buf, 0, NULL); - free(buf); - ck_wlock(&sdata->workbase_lock); - sdata->proxy_base.subscribed = true; - sdata->proxy_base.diff = ckp->startdiff; - /* Length is checked by generator */ - strcpy(sdata->proxy_base.enonce1, json_string_value(json_object_get(val, "enonce1"))); - sdata->proxy_base.enonce1constlen = strlen(sdata->proxy_base.enonce1) / 2; - hex2bin(sdata->proxy_base.enonce1bin, sdata->proxy_base.enonce1, sdata->proxy_base.enonce1constlen); - sdata->proxy_base.nonce2len = json_integer_value(json_object_get(val, "nonce2len")); - if (ckp->clientsvspeed) { - if (sdata->proxy_base.nonce2len > 5) - sdata->proxy_base.enonce1varlen = 4; - else if (sdata->proxy_base.nonce2len > 3) - sdata->proxy_base.enonce1varlen = 2; - else - sdata->proxy_base.enonce1varlen = 1; - } else { - if (sdata->proxy_base.nonce2len > 7) - sdata->proxy_base.enonce1varlen = 4; - else if (sdata->proxy_base.nonce2len > 5) - sdata->proxy_base.enonce1varlen = 2; - else - sdata->proxy_base.enonce1varlen = 1; + /* See if the priority is already in use */ + HASH_ITER(hh, sdata->proxies, tmpa, tmpb) { + if (tmpa->priority > priority) + break; + if (tmpa->priority == priority) { + exists = tmpa; + next_prio = exists->priority + 1; + break; + } } - sdata->proxy_base.enonce2varlen = sdata->proxy_base.nonce2len - sdata->proxy_base.enonce1varlen; - ck_wunlock(&sdata->workbase_lock); + /* See if we need to push the priority of everything after exists up */ + HASH_ITER(hh, exists, tmpa, tmpb) { + if (tmpa->priority > next_prio) + break; + tmpa->priority++; + next_prio++; + } + proxy->priority = priority; + HASH_SORT(sdata->proxies, prio_sort); +} - LOGNOTICE("Upstream pool extranonce2 length %d, max proxy clients %lld", - sdata->proxy_base.nonce2len, 1ll << (sdata->proxy_base.enonce1varlen * 8)); +static proxy_t *__generate_proxy(sdata_t *sdata, const int id) +{ + proxy_t *proxy = ckzalloc(sizeof(proxy_t)); + + proxy->parent = proxy; + proxy->id = id; + proxy->sdata = duplicate_sdata(sdata); + proxy->sdata->subproxy = proxy; + proxy->sdata->verbose = true; + /* subid == 0 on parent proxy */ + HASH_ADD(sh, proxy->subproxies, subid, sizeof(int), proxy); + proxy->subproxy_count++; + HASH_ADD_INT(sdata->proxies, id, proxy); + /* Set the new proxy priority to its id */ + __set_proxy_prio(sdata, proxy, id); + sdata->proxy_count++; + return proxy; +} - json_decref(val); - drop_allclients(ckp); +static proxy_t *__generate_subproxy(sdata_t *sdata, proxy_t *proxy, const int subid) +{ + proxy_t *subproxy = ckzalloc(sizeof(proxy_t)); + + subproxy->parent = proxy; + subproxy->id = proxy->id; + subproxy->subid = subid; + HASH_ADD(sh, proxy->subproxies, subid, sizeof(int), subproxy); + proxy->subproxy_count++; + subproxy->sdata = duplicate_sdata(sdata); + subproxy->sdata->subproxy = subproxy; + return subproxy; } -static void update_diff(ckpool_t *ckp); +static proxy_t *__existing_proxy(const sdata_t *sdata, const int id) +{ + proxy_t *proxy; + + HASH_FIND_INT(sdata->proxies, &id, proxy); + return proxy; +} -static void update_notify(ckpool_t *ckp) +static proxy_t *existing_proxy(sdata_t *sdata, const int id) { - bool new_block = false, clean; - sdata_t *sdata = ckp->data; - char header[228]; - workbase_t *wb; - json_t *val; - char *buf; - int i; + proxy_t *proxy; - buf = send_recv_proc(ckp->generator, "getnotify"); - if (unlikely(!buf)) { - LOGWARNING("Failed to get notify from generator in update_notify"); - return; - } + mutex_lock(&sdata->proxy_lock); + proxy = __existing_proxy(sdata, id); + mutex_unlock(&sdata->proxy_lock); - if (unlikely(!sdata->proxy_base.subscribed)) { - LOGINFO("No valid proxy subscription to update notify yet"); - return; - } + return proxy; +} - LOGDEBUG("Update notify: %s", buf); - wb = ckzalloc(sizeof(workbase_t)); - val = json_loads(buf, 0, NULL); - dealloc(buf); - wb->ckp = ckp; - wb->proxy = true; +/* Find proxy by id number, generate one if none exist yet by that id */ +static proxy_t *__proxy_by_id(sdata_t *sdata, const int id) +{ + proxy_t *proxy = __existing_proxy(sdata, id); - json_int64cpy(&wb->id, val, "jobid"); - json_strcpy(wb->prevhash, val, "prevhash"); - json_intcpy(&wb->coinb1len, val, "coinb1len"); - wb->coinb1bin = ckalloc(wb->coinb1len); - wb->coinb1 = ckalloc(wb->coinb1len * 2 + 1); - json_strcpy(wb->coinb1, val, "coinbase1"); - hex2bin(wb->coinb1bin, wb->coinb1, wb->coinb1len); - wb->height = get_sernumber(wb->coinb1bin + 42); - json_strdup(&wb->coinb2, val, "coinbase2"); - wb->coinb2len = strlen(wb->coinb2) / 2; - wb->coinb2bin = ckalloc(wb->coinb2len); - hex2bin(wb->coinb2bin, wb->coinb2, wb->coinb2len); - wb->merkle_array = json_object_dup(val, "merklehash"); - wb->merkles = json_array_size(wb->merkle_array); - for (i = 0; i < wb->merkles; i++) { - strcpy(&wb->merklehash[i][0], json_string_value(json_array_get(wb->merkle_array, i))); - hex2bin(&wb->merklebin[i][0], &wb->merklehash[i][0], 32); + if (unlikely(!proxy)) { + proxy = __generate_proxy(sdata, id); + LOGNOTICE("Stratifier added new proxy %d", id); } - json_strcpy(wb->bbversion, val, "bbversion"); - json_strcpy(wb->nbit, val, "nbit"); - json_strcpy(wb->ntime, val, "ntime"); - sscanf(wb->ntime, "%x", &wb->ntime32); - clean = json_is_true(json_object_get(val, "clean")); - json_decref(val); - ts_realtime(&wb->gentime); - snprintf(header, 225, "%s%s%s%s%s%s%s", - wb->bbversion, wb->prevhash, - "0000000000000000000000000000000000000000000000000000000000000000", - wb->ntime, wb->nbit, - "00000000", /* nonce */ - workpadding); - LOGDEBUG("Header: %s", header); - hex2bin(wb->headerbin, header, 112); - wb->txn_hashes = ckzalloc(1); - - /* Check diff on each notify */ - update_diff(ckp); - ck_rlock(&sdata->workbase_lock); - strcpy(wb->enonce1const, sdata->proxy_base.enonce1); - wb->enonce1constlen = sdata->proxy_base.enonce1constlen; - memcpy(wb->enonce1constbin, sdata->proxy_base.enonce1bin, wb->enonce1constlen); - wb->enonce1varlen = sdata->proxy_base.enonce1varlen; - wb->enonce2varlen = sdata->proxy_base.enonce2varlen; - wb->diff = sdata->proxy_base.diff; - ck_runlock(&sdata->workbase_lock); + return proxy; +} - add_base(ckp, wb, &new_block); +static proxy_t *__existing_subproxy(proxy_t *proxy, const int subid) +{ + proxy_t *subproxy; - stratum_broadcast_update(sdata, new_block | clean); + HASH_FIND(sh, proxy->subproxies, &subid, sizeof(int), subproxy); + return subproxy; } -static void stratum_send_diff(sdata_t *sdata, const stratum_instance_t *client); - -static void update_diff(ckpool_t *ckp) +static proxy_t *__subproxy_by_id(sdata_t *sdata, proxy_t *proxy, const int subid) { - stratum_instance_t *client, *tmp; - sdata_t *sdata = ckp->data; - double old_diff, diff; - json_t *val; - char *buf; + proxy_t *subproxy = __existing_subproxy(proxy, subid); - if (unlikely(!sdata->current_workbase)) { - LOGINFO("No current workbase to update diff yet"); - return; + if (!subproxy) { + subproxy = __generate_subproxy(sdata, proxy, subid); + LOGINFO("Stratifier added new subproxy %d:%d", proxy->id, subid); } + return subproxy; +} - buf = send_recv_proc(ckp->generator, "getdiff"); - if (unlikely(!buf)) { - LOGWARNING("Failed to get diff from generator in update_diff"); - return; - } +static proxy_t *subproxy_by_id(sdata_t *sdata, const int id, const int subid) +{ + proxy_t *proxy, *subproxy; - LOGDEBUG("Update diff: %s", buf); - val = json_loads(buf, 0, NULL); - dealloc(buf); - json_dblcpy(&diff, val, "diff"); - json_decref(val); + mutex_lock(&sdata->proxy_lock); + proxy = __proxy_by_id(sdata, id); + subproxy = __subproxy_by_id(sdata, proxy, subid); + mutex_unlock(&sdata->proxy_lock); - /* We only really care about integer diffs so clamp the lower limit to - * 1 or it will round down to zero. */ - if (unlikely(diff < 1)) - diff = 1; + return subproxy; +} - ck_wlock(&sdata->workbase_lock); - old_diff = sdata->proxy_base.diff; - sdata->current_workbase->diff = sdata->proxy_base.diff = diff; - ck_wunlock(&sdata->workbase_lock); +static proxy_t *existing_subproxy(sdata_t *sdata, const int id, const int subid) +{ + proxy_t *proxy, *subproxy = NULL; - if (old_diff < diff) - return; + mutex_lock(&sdata->proxy_lock); + proxy = __existing_proxy(sdata, id); + if (proxy) + subproxy = __existing_subproxy(proxy, subid); + mutex_unlock(&sdata->proxy_lock); - /* If the diff has dropped, iterate over all the clients and check - * they're at or below the new diff, and update it if not. */ - ck_rlock(&sdata->instance_lock); - HASH_ITER(hh, sdata->stratum_instances, client, tmp) { - if (client->diff > diff) { - client->diff = diff; - stratum_send_diff(sdata, client); - } - } - ck_runlock(&sdata->instance_lock); + return subproxy; } -/* Enter with instance_lock held */ -static stratum_instance_t *__instance_by_id(sdata_t *sdata, const int64_t id) +static void set_proxy_prio(sdata_t *sdata, proxy_t *proxy, const int priority) { - stratum_instance_t *client; - - HASH_FIND_I64(sdata->stratum_instances, &id, client); - return client; + mutex_lock(&sdata->proxy_lock); + __set_proxy_prio(sdata, proxy, priority); + mutex_unlock(&sdata->proxy_lock); } -/* Increase the reference count of instance */ -static void __inc_instance_ref(stratum_instance_t *client) +/* Set proxy to the current proxy and calculate how much headroom it has */ +static int64_t current_headroom(sdata_t *sdata, proxy_t **proxy) { - client->ref++; -} + proxy_t *subproxy, *tmp; + int64_t headroom = 0; -/* Find an __instance_by_id and increase its reference count allowing us to - * use this instance outside of instance_lock without fear of it being - * dereferenced. Does not return dropped clients still on the list. */ -static stratum_instance_t *ref_instance_by_id(sdata_t *sdata, const int64_t id) -{ - stratum_instance_t *client; - - ck_wlock(&sdata->instance_lock); - client = __instance_by_id(sdata, id); - if (client) { - if (unlikely(client->dropped)) - client = NULL; - else - __inc_instance_ref(client); + mutex_lock(&sdata->proxy_lock); + *proxy = sdata->proxy; + if (!*proxy) + goto out_unlock; + HASH_ITER(sh, (*proxy)->subproxies, subproxy, tmp) { + if (subproxy->dead) + continue; + headroom += subproxy->max_clients - subproxy->clients; } - ck_wunlock(&sdata->instance_lock); +out_unlock: + mutex_unlock(&sdata->proxy_lock); - return client; + return headroom; } -static void __drop_client(sdata_t *sdata, stratum_instance_t *client, bool lazily, char **msg) +static int64_t proxy_headroom(sdata_t *sdata, const int userid) { - user_instance_t *user = client->user_instance; + proxy_t *proxy, *subproxy, *tmp, *subtmp; + int64_t headroom = 0; - if (client->workername) { - if (user) { - ASPRINTF(msg, "Dropped client %"PRId64" %s %suser %s worker %s %s", - client->id, client->address, user->throttled ? "throttled " : "", - user->username, client->workername, lazily ? "lazily" : ""); - } else { - ASPRINTF(msg, "Dropped client %"PRId64" %s no user worker %s %s", - client->id, client->address, client->workername, - lazily ? "lazily" : ""); + 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; + headroom += subproxy->max_clients - subproxy->clients; } - } else { - ASPRINTF(msg, "Dropped workerless client %"PRId64" %s %s", - client->id, client->address, lazily ? "lazily" : ""); } - __del_client(sdata, client); - __kill_instance(sdata, client); + mutex_unlock(&sdata->proxy_lock); + + return headroom; } -/* Decrease the reference count of instance. */ -static void _dec_instance_ref(sdata_t *sdata, stratum_instance_t *client, const char *file, - const char *func, const int line) +static void reconnect_client(sdata_t *sdata, stratum_instance_t *client); + +static void generator_recruit(const ckpool_t *ckp, const int proxyid, const int recruits) { - char_entry_t *entries = NULL; - char *msg; - int ref; + char buf[256]; - ck_wlock(&sdata->instance_lock); - ref = --client->ref; - /* See if there are any instances that were dropped that could not be - * moved due to holding a reference and drop them now. */ - if (unlikely(client->dropped && !ref)) { - __drop_client(sdata, client, true, &msg); - add_msg_entry(&entries, &msg); + sprintf(buf, "recruit=%d:%d", proxyid, recruits); + LOGINFO("Stratifer requesting %d more subproxies of proxy %d from generator", + recruits, proxyid); + send_generator(ckp, buf, GEN_PRIORITY); +} + +/* Find how much headroom we have and connect up to that many clients that are + * not currently on this pool, recruiting more slots to switch more clients + * later on lazily. Only reconnect clients bound to global proxies. */ +static void reconnect_clients(sdata_t *sdata) +{ + stratum_instance_t *client, *tmpclient; + int reconnects = 0; + int64_t headroom; + proxy_t *proxy; + + headroom = current_headroom(sdata, &proxy); + if (!proxy) + return; + + ck_rlock(&sdata->instance_lock); + HASH_ITER(hh, sdata->stratum_instances, client, tmpclient) { + if (client->dropped) + continue; + if (!client->authorised) + continue; + /* Is this client bound to a dead proxy? */ + if (!client->reconnect) { + /* This client is bound to a user proxy */ + if (client->proxy->userid) + continue; + if (client->proxyid == proxy->id) + continue; + } + if (headroom-- < 1) + continue; + reconnects++; + reconnect_client(sdata, client); } - ck_wunlock(&sdata->instance_lock); + ck_runlock(&sdata->instance_lock); - notice_msg_entries(&entries); - /* This should never happen */ - if (unlikely(ref < 0)) - LOGERR("Instance ref count dropped below zero from %s %s:%d", file, func, line); + if (reconnects) { + LOGINFO("%d clients flagged for reconnect to global proxy %d", + reconnects, proxy->id); + } + if (headroom < 0) + generator_recruit(sdata->ckp, proxy->id, -headroom); } -#define dec_instance_ref(sdata, instance) _dec_instance_ref(sdata, instance, __FILE__, __func__, __LINE__) - -/* If we have a no longer used stratum instance in the recycled linked list, - * use that, otherwise calloc a fresh one. */ -static stratum_instance_t *__recruit_stratum_instance(sdata_t *sdata) +static bool __subproxies_alive(proxy_t *proxy) { - stratum_instance_t *client = sdata->recycled_instances; + proxy_t *subproxy, *tmp; + bool alive = false; - if (client) - DL_DELETE(sdata->recycled_instances, client); - else { - client = ckzalloc(sizeof(stratum_instance_t)); - sdata->stratum_generated++; + HASH_ITER(sh, proxy->subproxies, subproxy, tmp) { + if (!subproxy->dead) { + alive = true; + break; + } } - return client; + return alive; } -/* Enter with write instance_lock held */ -static stratum_instance_t *__stratum_add_instance(ckpool_t *ckp, const int64_t id, - const char *address, int server) +/* Iterate over the current global proxy list and see if the current one is + * the highest priority alive one. Proxies are sorted by priority so the first + * available will be highest priority. Uses ckp sdata */ +static void check_bestproxy(sdata_t *sdata) { - stratum_instance_t *client; - sdata_t *sdata = ckp->data; + proxy_t *proxy, *tmp; + int changed_id = -1; + + mutex_lock(&sdata->proxy_lock); + if (sdata->proxy && !__subproxies_alive(sdata->proxy)) + sdata->proxy = NULL; + HASH_ITER(hh, sdata->proxies, proxy, tmp) { + if (!__subproxies_alive(proxy)) + continue; + if (!proxy->global) + break; + if (proxy != sdata->proxy) { + sdata->proxy = proxy; + changed_id = proxy->id; + } + break; + } + mutex_unlock(&sdata->proxy_lock); - client = __recruit_stratum_instance(sdata); - client->id = id; - client->session_id = ++sdata->session_id; - strcpy(client->address, address); - /* Sanity check to not overflow lookup in ckp->serverurl[] */ - if (server >= ckp->serverurls) - server = 0; - client->server = server; - client->diff = client->old_diff = ckp->startdiff; - client->ckp = ckp; - tv_time(&client->ldc); - HASH_ADD_I64(sdata->stratum_instances, id, client); - return client; + if (changed_id != -1) + LOGNOTICE("Stratifier setting active proxy to %d", changed_id); } -/* passthrough subclients have client_ids in the high bits */ -static inline bool passthrough_subclient(const int64_t client_id) +static void dead_proxyid(sdata_t *sdata, const int id, const int subid, const bool replaced) { - return (client_id > 0xffffffffll); + stratum_instance_t *client, *tmp; + int reconnects = 0, proxyid = 0; + int64_t headroom; + proxy_t *proxy; + + proxy = existing_subproxy(sdata, id, subid); + if (proxy) { + proxy->dead = true; + if (!replaced && proxy->global) + check_bestproxy(sdata); + } + LOGINFO("Stratifier dropping clients from proxy %d:%d", id, subid); + headroom = current_headroom(sdata, &proxy); + if (proxy) + proxyid = proxy->id; + + ck_rlock(&sdata->instance_lock); + HASH_ITER(hh, sdata->stratum_instances, client, tmp) { + if (client->proxyid != id || client->subproxyid != subid) + continue; + /* Clients could remain connected to a dead connection here + * but should be picked up when we recruit enough slots after + * another notify. */ + if (headroom-- < 1) { + client->reconnect = true; + continue; + } + reconnects++; + reconnect_client(sdata, client); + } + ck_runlock(&sdata->instance_lock); + + if (reconnects) { + LOGINFO("%d clients flagged to reconnect from dead proxy %d:%d", reconnects, + id, subid); + } + /* When a proxy dies, recruit more of the global proxies for them to + * fail over to in case user proxies are unavailable. */ + if (headroom < 0) + generator_recruit(sdata->ckp, proxyid, -headroom); } -static uint64_t disconnected_sessionid_exists(sdata_t *sdata, const char *sessionid, - int *session_id, const int64_t id) +static void update_subscribe(ckpool_t *ckp, const char *cmd) { - session_t *session; - int64_t old_id = 0; - uint64_t ret = 0; - int slen; + sdata_t *sdata = ckp->data, *dsdata; + int id = 0, subid = 0, userid = 0; + proxy_t *proxy, *old = NULL; + const char *buf; + bool global; + json_t *val; - /* Don't allow passthrough subclients to resume */ - if (passthrough_subclient(id)) - goto out; + if (unlikely(strlen(cmd) < 11)) { + LOGWARNING("Received zero length string for subscribe in update_subscribe"); + return; + } + buf = cmd + 10; + LOGDEBUG("Update subscribe: %s", buf); + val = json_loads(buf, 0, NULL); + if (unlikely(!val)) { + LOGWARNING("Failed to json decode subscribe response in update_subscribe %s", buf); + return; + } + if (unlikely(!json_get_int(&id, val, "proxy"))) { + LOGWARNING("Failed to json decode proxy value in update_subscribe %s", buf); + return; + } + if (unlikely(!json_get_int(&subid, val, "subproxy"))) { + LOGWARNING("Failed to json decode subproxy value in update_subscribe %s", buf); + return; + } + if (unlikely(!json_get_bool(&global, val, "global"))) { + LOGWARNING("Failed to json decode global value in update_subscribe %s", buf); + return; + } + if (!global) { + if (unlikely(!json_get_int(&userid, val, "userid"))) { + LOGWARNING("Failed to json decode userid value in update_subscribe %s", buf); + return; + } + } - if (!sessionid) - goto out; - slen = strlen(sessionid) / 2; - if (slen < 1 || slen > 4) - goto out; + if (!subid) + LOGNOTICE("Got updated subscribe for proxy %d", id); + else + LOGINFO("Got updated subscribe for proxy %d:%d", id, subid); + + /* Is this a replacement for an existing proxy id? */ + old = existing_subproxy(sdata, id, subid); + if (old) { + dead_proxyid(sdata, id, subid, true); + proxy = old; + proxy->dead = false; + } else + proxy = subproxy_by_id(sdata, id, subid); + proxy->global = global; + proxy->userid = userid; + proxy->subscribed = true; + proxy->diff = ckp->startdiff; + memset(proxy->url, 0, 128); + memset(proxy->auth, 0, 128); + memset(proxy->pass, 0, 128); + strncpy(proxy->url, json_string_value(json_object_get(val, "url")), 127); + strncpy(proxy->auth, json_string_value(json_object_get(val, "auth")), 127); + strncpy(proxy->pass, json_string_value(json_object_get(val, "pass")), 127); + + dsdata = proxy->sdata; + + ck_wlock(&dsdata->workbase_lock); + /* Length is checked by generator */ + strcpy(proxy->enonce1, json_string_value(json_object_get(val, "enonce1"))); + proxy->enonce1constlen = strlen(proxy->enonce1) / 2; + hex2bin(proxy->enonce1bin, proxy->enonce1, proxy->enonce1constlen); + proxy->nonce2len = json_integer_value(json_object_get(val, "nonce2len")); + if (proxy->nonce2len > 7) + proxy->enonce1varlen = 4; + else if (proxy->nonce2len > 5) + proxy->enonce1varlen = 2; + else if (proxy->nonce2len > 3) + proxy->enonce1varlen = 1; + else + proxy->enonce1varlen = 0; + proxy->enonce2varlen = proxy->nonce2len - proxy->enonce1varlen; + proxy->max_clients = 1ll << (proxy->enonce1varlen * 8); + proxy->clients = 0; + ck_wunlock(&dsdata->workbase_lock); + + if (subid) { + LOGINFO("Upstream pool %s %d:%d extranonce2 length %d, max proxy clients %"PRId64, + proxy->url, id, subid, proxy->nonce2len, proxy->max_clients); + } else { + LOGNOTICE("Upstream pool %s %d extranonce2 length %d, max proxy clients %"PRId64, + proxy->url, id, proxy->nonce2len, proxy->max_clients); + } + json_decref(val); +} - if (!validhex(sessionid)) - goto out; +/* 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, *best = NULL; - sscanf(sessionid, "%x", session_id); - LOGDEBUG("Testing for sessionid %s %x", sessionid, *session_id); + 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; + best = proxy; + } + } + mutex_unlock(&sdata->proxy_lock); - ck_wlock(&sdata->instance_lock); - HASH_FIND_INT(sdata->disconnected_sessions, session_id, session); - if (!session) - goto out_unlock; - HASH_DEL(sdata->disconnected_sessions, session); - sdata->stats.disconnected--; - ret = session->enonce1_64; - old_id = session->client_id; - dealloc(session); -out_unlock: - ck_wunlock(&sdata->instance_lock); -out: - if (ret) - LOGNOTICE("Reconnecting old instance %"PRId64" to instance %"PRId64, old_id, id); - return ret; + if (best) + generator_recruit(sdata->ckp, best->id, recruits); } -static inline bool client_active(stratum_instance_t *client) +/* 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, const int userid) { - return (client->authorised && !client->dropped); + int64_t headroom = proxy_headroom(sdata, userid); + stratum_instance_t *client, *tmpclient; + int reconnects = 0; + + ck_rlock(&sdata->instance_lock); + HASH_ITER(hh, sdata->stratum_instances, client, tmpclient) { + if (client->dropped) + continue; + if (!client->authorised) + continue; + if (client->user_id != userid) + continue; + /* Is this client bound to a dead proxy? */ + if (!client->reconnect && client->proxy->userid == userid) + continue; + if (headroom-- < 1) + continue; + reconnects++; + reconnect_client(sdata, client); + } + ck_runlock(&sdata->instance_lock); + + if (reconnects) { + LOGINFO("%d clients flagged for reconnect to user %d proxies", + reconnects, userid); + } + if (headroom < 0) + recruit_best_userproxy(sdata, userid, -headroom); } -/* For creating a list of sends without locking that can then be concatenated - * to the stratum_sends list. Minimises locking and avoids taking recursive - * locks. */ -static void stratum_broadcast(sdata_t *sdata, json_t *val) +static proxy_t *best_proxy(sdata_t *sdata) { - stratum_instance_t *client, *tmp; - ckmsg_t *bulk_send = NULL; - ckmsgq_t *ssends; + proxy_t *proxy; + + mutex_lock(&sdata->proxy_lock); + proxy = sdata->proxy; + mutex_unlock(&sdata->proxy_lock); + + return proxy; +} + +static void update_notify(ckpool_t *ckp, const char *cmd) +{ + sdata_t *sdata = ckp->data, *dsdata; + bool new_block = false, clean; + int i, id = 0, subid = 0; + char header[228]; + const char *buf; + proxy_t *proxy; + workbase_t *wb; + json_t *val; + + if (unlikely(strlen(cmd) < 8)) { + LOGWARNING("Zero length string passed to update_notify"); + return; + } + buf = cmd + 7; /* "notify=" */ + LOGDEBUG("Update notify: %s", buf); + val = json_loads(buf, 0, NULL); if (unlikely(!val)) { - LOGERR("Sent null json to stratum_broadcast"); + LOGWARNING("Failed to json decode in update_notify"); return; } + json_get_int(&id, val, "proxy"); + json_get_int(&subid, val, "subproxy"); + proxy = existing_subproxy(sdata, id, subid); + if (unlikely(!proxy || !proxy->subscribed)) { + LOGINFO("No valid proxy %d:%d subscription to update notify yet", id, subid); + goto out; + } + LOGINFO("Got updated notify for proxy %d:%d", id, subid); - ck_rlock(&sdata->instance_lock); - HASH_ITER(hh, sdata->stratum_instances, client, tmp) { - ckmsg_t *client_msg; - smsg_t *msg; + wb = ckzalloc(sizeof(workbase_t)); + wb->ckp = ckp; + wb->proxy = true; - if (!client_active(client)) - continue; - client_msg = ckalloc(sizeof(ckmsg_t)); - msg = ckzalloc(sizeof(smsg_t)); - msg->json_msg = json_deep_copy(val); - msg->client_id = client->id; - client_msg->data = msg; - DL_APPEND(bulk_send, client_msg); + json_get_int64(&wb->id, val, "jobid"); + json_strcpy(wb->prevhash, val, "prevhash"); + json_intcpy(&wb->coinb1len, val, "coinb1len"); + wb->coinb1bin = ckalloc(wb->coinb1len); + wb->coinb1 = ckalloc(wb->coinb1len * 2 + 1); + json_strcpy(wb->coinb1, val, "coinbase1"); + hex2bin(wb->coinb1bin, wb->coinb1, wb->coinb1len); + wb->height = get_sernumber(wb->coinb1bin + 42); + json_strdup(&wb->coinb2, val, "coinbase2"); + wb->coinb2len = strlen(wb->coinb2) / 2; + wb->coinb2bin = ckalloc(wb->coinb2len); + hex2bin(wb->coinb2bin, wb->coinb2, wb->coinb2len); + wb->merkle_array = json_object_dup(val, "merklehash"); + wb->merkles = json_array_size(wb->merkle_array); + for (i = 0; i < wb->merkles; i++) { + strcpy(&wb->merklehash[i][0], json_string_value(json_array_get(wb->merkle_array, i))); + hex2bin(&wb->merklebin[i][0], &wb->merklehash[i][0], 32); + } + json_strcpy(wb->bbversion, val, "bbversion"); + json_strcpy(wb->nbit, val, "nbit"); + json_strcpy(wb->ntime, val, "ntime"); + sscanf(wb->ntime, "%x", &wb->ntime32); + clean = json_is_true(json_object_get(val, "clean")); + ts_realtime(&wb->gentime); + snprintf(header, 225, "%s%s%s%s%s%s%s", + wb->bbversion, wb->prevhash, + "0000000000000000000000000000000000000000000000000000000000000000", + wb->ntime, wb->nbit, + "00000000", /* nonce */ + workpadding); + LOGDEBUG("Header: %s", header); + hex2bin(wb->headerbin, header, 112); + wb->txn_hashes = ckzalloc(1); + + dsdata = proxy->sdata; + + ck_rlock(&dsdata->workbase_lock); + strcpy(wb->enonce1const, proxy->enonce1); + wb->enonce1constlen = proxy->enonce1constlen; + memcpy(wb->enonce1constbin, proxy->enonce1bin, wb->enonce1constlen); + wb->enonce1varlen = proxy->enonce1varlen; + wb->enonce2varlen = proxy->enonce2varlen; + wb->diff = proxy->diff; + ck_runlock(&dsdata->workbase_lock); + + add_base(ckp, dsdata, wb, &new_block); + if (new_block) { + if (subid) + LOGINFO("Block hash on proxy %d:%d changed to %s", id, subid, dsdata->lastswaphash); + else + LOGNOTICE("Block hash on proxy %d changed to %s", id, dsdata->lastswaphash); + } + + if (proxy->global) { + check_bestproxy(sdata); + if (proxy->parent == best_proxy(sdata)->parent) + reconnect_clients(sdata); + } else + check_userproxies(sdata, proxy->userid); + clean |= new_block; + LOGINFO("Proxy %d:%d broadcast updated stratum notify with%s clean", id, + subid, clean ? "" : "out"); + stratum_broadcast_update(dsdata, wb, clean); +out: + json_decref(val); +} + +static void stratum_send_diff(sdata_t *sdata, const stratum_instance_t *client); + +static void update_diff(ckpool_t *ckp, const char *cmd) +{ + sdata_t *sdata = ckp->data, *dsdata; + stratum_instance_t *client, *tmp; + double old_diff, diff; + int id = 0, subid = 0; + const char *buf; + proxy_t *proxy; + json_t *val; + + if (unlikely(strlen(cmd) < 6)) { + LOGWARNING("Zero length string passed to update_diff"); + return; + } + buf = cmd + 5; /* "diff=" */ + LOGDEBUG("Update diff: %s", buf); + + val = json_loads(buf, 0, NULL); + if (unlikely(!val)) { + LOGWARNING("Failed to json decode in update_diff"); + return; + } + json_get_int(&id, val, "proxy"); + json_get_int(&subid, val, "subproxy"); + json_dblcpy(&diff, val, "diff"); + json_decref(val); + + LOGINFO("Got updated diff for proxy %d:%d", id, subid); + proxy = existing_subproxy(sdata, id, subid); + if (!proxy) { + LOGINFO("No existing subproxy %d:%d to update diff", id, subid); + return; + } + + /* We only really care about integer diffs so clamp the lower limit to + * 1 or it will round down to zero. */ + if (unlikely(diff < 1)) + diff = 1; + + dsdata = proxy->sdata; + + if (unlikely(!dsdata->current_workbase)) { + LOGINFO("No current workbase to update diff yet"); + return; + } + + ck_wlock(&dsdata->workbase_lock); + old_diff = proxy->diff; + dsdata->current_workbase->diff = proxy->diff = diff; + ck_wunlock(&dsdata->workbase_lock); + + if (old_diff < diff) + return; + + /* If the diff has dropped, iterate over all the clients and check + * they're at or below the new diff, and update it if not. */ + ck_rlock(&sdata->instance_lock); + HASH_ITER(hh, sdata->stratum_instances, client, tmp) { + if (client->proxyid != id) + continue; + if (client->subproxyid != subid) + continue; + if (client->diff > diff) { + client->diff = diff; + stratum_send_diff(sdata, client); + } + } + ck_runlock(&sdata->instance_lock); +} + +#if 0 +static void generator_drop_proxy(ckpool_t *ckp, const int64_t id, const int subid) +{ + char msg[256]; + + sprintf(msg, "dropproxy=%ld:%d", id, subid); + send_generator(ckp, msg, GEN_LAX); +} +#endif + +static void free_proxy(proxy_t *proxy) +{ + free(proxy->sdata); + free(proxy); +} + +/* Remove subproxies that are flagged dead. Then see if there + * are any retired proxies that no longer have any other subproxies and reap + * those. */ +static void reap_proxies(ckpool_t *ckp, sdata_t *sdata) +{ + proxy_t *proxy, *proxytmp, *subproxy, *subtmp; + int dead = 0; + + if (!ckp->proxy) + return; + + mutex_lock(&sdata->proxy_lock); + HASH_ITER(hh, sdata->proxies, proxy, proxytmp) { + HASH_ITER(sh, proxy->subproxies, subproxy, subtmp) { + if (!subproxy->bound_clients && !subproxy->dead) { + /* Reset the counter to reuse this proxy */ + subproxy->clients = 0; + continue; + } + if (proxy == subproxy) + continue; + if (subproxy->bound_clients) + continue; + if (!subproxy->dead) + continue; + if (unlikely(!subproxy->subid)) { + LOGWARNING("Unexepectedly found proxy %d:%d as subproxy of %d:%d", + subproxy->id, subproxy->subid, proxy->id, proxy->subid); + continue; + } + if (unlikely(subproxy == sdata->proxy)) { + LOGWARNING("Unexepectedly found proxy %d:%d as current", + subproxy->id, subproxy->subid); + continue; + } + dead++; + HASH_DELETE(sh, proxy->subproxies, subproxy); + proxy->subproxy_count--; + free_proxy(subproxy); + } + } + mutex_unlock(&sdata->proxy_lock); + + if (dead) + LOGINFO("Stratifier discarded %d dead proxies", dead); +} + +/* Enter with instance_lock held */ +static stratum_instance_t *__instance_by_id(sdata_t *sdata, const int64_t id) +{ + stratum_instance_t *client; + + HASH_FIND_I64(sdata->stratum_instances, &id, client); + return client; +} + +/* Increase the reference count of instance */ +static void __inc_instance_ref(stratum_instance_t *client) +{ + client->ref++; +} + +/* Find an __instance_by_id and increase its reference count allowing us to + * use this instance outside of instance_lock without fear of it being + * dereferenced. Does not return dropped clients still on the list. */ +static stratum_instance_t *ref_instance_by_id(sdata_t *sdata, const int64_t id) +{ + stratum_instance_t *client; + + ck_wlock(&sdata->instance_lock); + client = __instance_by_id(sdata, id); + if (client) { + if (unlikely(client->dropped)) + client = NULL; + else + __inc_instance_ref(client); + } + ck_wunlock(&sdata->instance_lock); + + return client; +} + +static void __drop_client(sdata_t *sdata, stratum_instance_t *client, bool lazily, char **msg) +{ + user_instance_t *user = client->user_instance; + + if (unlikely(client->node)) + DL_DELETE(sdata->node_instances, client); + if (client->workername) { + if (user) { + ASPRINTF(msg, "Dropped client %"PRId64" %s %suser %s worker %s %s", + client->id, client->address, user->throttled ? "throttled " : "", + user->username, client->workername, lazily ? "lazily" : ""); + } else { + ASPRINTF(msg, "Dropped client %"PRId64" %s no user worker %s %s", + client->id, client->address, client->workername, + lazily ? "lazily" : ""); + } + } else { + ASPRINTF(msg, "Dropped workerless client %"PRId64" %s %s", + client->id, client->address, lazily ? "lazily" : ""); + } + __del_client(sdata, client); + __kill_instance(sdata, client); +} + +/* Decrease the reference count of instance. */ +static void _dec_instance_ref(sdata_t *sdata, stratum_instance_t *client, const char *file, + const char *func, const int line) +{ + char_entry_t *entries = NULL; + bool dropped = false; + char *msg; + int ref; + + ck_wlock(&sdata->instance_lock); + ref = --client->ref; + /* See if there are any instances that were dropped that could not be + * moved due to holding a reference and drop them now. */ + if (unlikely(client->dropped && !ref)) { + dropped = true; + __drop_client(sdata, client, true, &msg); + add_msg_entry(&entries, &msg); + } + ck_wunlock(&sdata->instance_lock); + + notice_msg_entries(&entries); + /* This should never happen */ + if (unlikely(ref < 0)) + LOGERR("Instance ref count dropped below zero from %s %s:%d", file, func, line); + + if (dropped) + reap_proxies(sdata->ckp, sdata); +} + +#define dec_instance_ref(sdata, instance) _dec_instance_ref(sdata, instance, __FILE__, __func__, __LINE__) + +/* If we have a no longer used stratum instance in the recycled linked list, + * use that, otherwise calloc a fresh one. */ +static stratum_instance_t *__recruit_stratum_instance(sdata_t *sdata) +{ + stratum_instance_t *client = sdata->recycled_instances; + + if (client) + DL_DELETE(sdata->recycled_instances, client); + else { + client = ckzalloc(sizeof(stratum_instance_t)); + sdata->stratum_generated++; + } + return client; +} + +/* Enter with write instance_lock held */ +static stratum_instance_t *__stratum_add_instance(ckpool_t *ckp, const int64_t id, + const char *address, int server) +{ + stratum_instance_t *client; + sdata_t *sdata = ckp->data; + + client = __recruit_stratum_instance(sdata); + client->start_time = time(NULL); + client->id = id; + client->session_id = ++sdata->session_id; + strcpy(client->address, address); + /* Sanity check to not overflow lookup in ckp->serverurl[] */ + if (server >= ckp->serverurls) + server = 0; + client->server = server; + client->diff = client->old_diff = ckp->startdiff; + client->ckp = ckp; + tv_time(&client->ldc); + HASH_ADD_I64(sdata->stratum_instances, id, client); + /* Points to ckp sdata in ckpool mode, but is changed later in proxy + * mode . */ + client->sdata = sdata; + return client; +} + +static uint64_t disconnected_sessionid_exists(sdata_t *sdata, const int session_id, + const int64_t id) +{ + session_t *session; + int64_t old_id = 0; + uint64_t ret = 0; + + ck_wlock(&sdata->instance_lock); + HASH_FIND_INT(sdata->disconnected_sessions, &session_id, session); + if (!session) + goto out_unlock; + HASH_DEL(sdata->disconnected_sessions, session); + sdata->stats.disconnected--; + ret = session->enonce1_64; + old_id = session->client_id; + dealloc(session); +out_unlock: + ck_wunlock(&sdata->instance_lock); + + if (ret) + LOGNOTICE("Reconnecting old instance %"PRId64" to instance %"PRId64, old_id, id); + return ret; +} + +static inline bool client_active(stratum_instance_t *client) +{ + return (client->authorised && !client->dropped); +} + +/* Ask the connector asynchronously to send us dropclient commands if this + * client no longer exists. */ +static void connector_test_client(ckpool_t *ckp, const int64_t id) +{ + char buf[256]; + + LOGDEBUG("Stratifier requesting connector test client %"PRId64, id); + snprintf(buf, 255, "testclient=%"PRId64, id); + send_proc(ckp->connector, buf); +} + +/* passthrough subclients have client_ids in the high bits */ +static inline bool passthrough_subclient(const int64_t client_id) +{ + return (client_id > 0xffffffffll); +} + +/* For creating a list of sends without locking that can then be concatenated + * to the stratum_sends list. Minimises locking and avoids taking recursive + * locks. Sends only to sdata bound clients (everyone in ckpool) */ +static void stratum_broadcast(sdata_t *sdata, json_t *val, const int msg_type) +{ + ckpool_t *ckp = sdata->ckp; + sdata_t *ckp_sdata = ckp->data; + stratum_instance_t *client, *tmp; + ckmsg_t *bulk_send = NULL; + time_t now_t = time(NULL); + ckmsgq_t *ssends; + + if (unlikely(!val)) { + LOGERR("Sent null json to stratum_broadcast"); + return; + } + + if (ckp->node) { + json_decref(val); + return; + } + + /* Use this locking as an opportunity to test other clients. */ + ck_rlock(&ckp_sdata->instance_lock); + HASH_ITER(hh, ckp_sdata->stratum_instances, client, tmp) { + ckmsg_t *client_msg; + smsg_t *msg; + + if (sdata != ckp_sdata && client->sdata != sdata) + continue; + + /* Look for clients that may have been dropped which the stratifer has + * not been informed about and ask the connector of they still exist */ + if (client->dropped) { + connector_test_client(ckp, client->id); + continue; + } + + if (client->node) + continue; + + /* Test for clients that haven't authed in over a minute and drop them */ + if (!client->authorised) { + if (now_t > client->start_time + 60) { + client->dropped = true; + connector_drop_client(ckp, client->id); + } + continue; + } + + if (!client_active(client)) + continue; + client_msg = ckalloc(sizeof(ckmsg_t)); + msg = ckzalloc(sizeof(smsg_t)); + if (passthrough_subclient(client->id)) + json_set_string(val, "node.method", stratum_msgs[msg_type]); + msg->json_msg = json_deep_copy(val); + msg->client_id = client->id; + client_msg->data = msg; + DL_APPEND(bulk_send, client_msg); + } + ck_runlock(&ckp_sdata->instance_lock); + + json_decref(val); + + if (!bulk_send) + return; + + ssends = sdata->ssends; + + mutex_lock(ssends->lock); + if (ssends->msgs) + DL_CONCAT(ssends->msgs, bulk_send); + else + ssends->msgs = bulk_send; + pthread_cond_signal(ssends->cond); + mutex_unlock(ssends->lock); +} + +static void stratum_add_send(sdata_t *sdata, json_t *val, const int64_t client_id, + const int msg_type) +{ + smsg_t *msg; + ckpool_t *ckp = sdata->ckp; + + if (ckp->node) { + /* Node shouldn't be sending any messages as it only uses the + * stratifier for monitoring activity. */ + json_decref(val); + return; + } + + if (passthrough_subclient(client_id)) + json_set_string(val, "node.method", stratum_msgs[msg_type]); + LOGDEBUG("Sending stratum message %s", stratum_msgs[msg_type]); + msg = ckzalloc(sizeof(smsg_t)); + msg->json_msg = val; + msg->client_id = client_id; + ckmsgq_add(sdata->ssends, msg); +} + +static void drop_client(ckpool_t *ckp, sdata_t *sdata, const int64_t id) +{ + char_entry_t *entries = NULL; + stratum_instance_t *client; + char *msg; + + LOGINFO("Stratifier asked to drop client %"PRId64, id); + + ck_wlock(&sdata->instance_lock); + client = __instance_by_id(sdata, id); + if (client && !client->dropped) { + __disconnect_session(sdata, client); + /* If the client is still holding a reference, don't drop them + * now but wait till the reference is dropped */ + if (!client->ref) { + __drop_client(sdata, client, false, &msg); + add_msg_entry(&entries, &msg); + } else + client->dropped = true; + } + ck_wunlock(&sdata->instance_lock); + + notice_msg_entries(&entries); + reap_proxies(ckp, sdata); +} + +static void stratum_broadcast_message(sdata_t *sdata, const char *msg) +{ + json_t *json_msg; + + JSON_CPACK(json_msg, "{sosss[s]}", "id", json_null(), "method", "client.show_message", + "params", msg); + stratum_broadcast(sdata, json_msg, SM_MSG); +} + +/* Send a generic reconnect to all clients without parameters to make them + * reconnect to the same server. */ +static void request_reconnect(sdata_t *sdata, const char *cmd) +{ + char *port = strdupa(cmd), *url = NULL; + stratum_instance_t *client, *tmp; + json_t *json_msg; + + strsep(&port, ":"); + if (port) + url = strsep(&port, ","); + if (url && port) { + JSON_CPACK(json_msg, "{sosss[ssi]}", "id", json_null(), "method", "client.reconnect", + "params", url, port, 0); + } else + JSON_CPACK(json_msg, "{sosss[]}", "id", json_null(), "method", "client.reconnect", + "params"); + stratum_broadcast(sdata, json_msg, SM_RECONNECT); + + /* Tag all existing clients as dropped now so they can be removed + * lazily */ + ck_wlock(&sdata->instance_lock); + HASH_ITER(hh, sdata->stratum_instances, client, tmp) { + client->dropped = true; + } + ck_wunlock(&sdata->instance_lock); +} + +static void reset_bestshares(sdata_t *sdata) +{ + user_instance_t *user, *tmpuser; + stratum_instance_t *client, *tmp; + + ck_rlock(&sdata->instance_lock); + HASH_ITER(hh, sdata->stratum_instances, client, tmp) { + client->best_diff = 0; + } + HASH_ITER(hh, sdata->user_instances, user, tmpuser) { + worker_instance_t *worker; + + user->best_diff = 0; + DL_FOREACH(user->worker_instances, worker) { + worker->best_diff = 0; + } + } + ck_runlock(&sdata->instance_lock); +} + +static user_instance_t *get_user(sdata_t *sdata, const char *username); + +static user_instance_t *user_by_workername(sdata_t *sdata, const char *workername) +{ + char *username = strdupa(workername), *ignore; + user_instance_t *user; + + ignore = username; + strsep(&ignore, "._"); + + /* Find the user first */ + user = get_user(sdata, username); + return user; +} + +static worker_instance_t *get_worker(sdata_t *sdata, user_instance_t *user, const char *workername); + +static json_t *worker_stats(const worker_instance_t *worker) +{ + char suffix1[16], suffix5[16], suffix60[16], suffix1440[16], suffix10080[16]; + json_t *val; + double ghs; + + ghs = worker->dsps1 * nonces; + suffix_string(ghs, suffix1, 16, 0); + + ghs = worker->dsps5 * nonces; + suffix_string(ghs, suffix5, 16, 0); + + ghs = worker->dsps60 * nonces; + suffix_string(ghs, suffix60, 16, 0); + + ghs = worker->dsps1440 * nonces; + suffix_string(ghs, suffix1440, 16, 0); + + ghs = worker->dsps10080 * nonces; + suffix_string(ghs, suffix10080, 16, 0); + + JSON_CPACK(val, "{ss,ss,ss,ss,ss}", + "hashrate1m", suffix1, + "hashrate5m", suffix5, + "hashrate1hr", suffix60, + "hashrate1d", suffix1440, + "hashrate7d", suffix10080); + return val; +} + +static json_t *user_stats(const user_instance_t *user) +{ + char suffix1[16], suffix5[16], suffix60[16], suffix1440[16], suffix10080[16]; + json_t *val; + double ghs; + + ghs = user->dsps1 * nonces; + suffix_string(ghs, suffix1, 16, 0); + + ghs = user->dsps5 * nonces; + suffix_string(ghs, suffix5, 16, 0); + + ghs = user->dsps60 * nonces; + suffix_string(ghs, suffix60, 16, 0); + + ghs = user->dsps1440 * nonces; + suffix_string(ghs, suffix1440, 16, 0); + + ghs = user->dsps10080 * nonces; + suffix_string(ghs, suffix10080, 16, 0); + + JSON_CPACK(val, "{ss,ss,ss,ss,ss}", + "hashrate1m", suffix1, + "hashrate5m", suffix5, + "hashrate1hr", suffix60, + "hashrate1d", suffix1440, + "hashrate7d", suffix10080); + return val; +} + +static void block_solve(ckpool_t *ckp, const char *blockhash) +{ + ckmsg_t *block, *tmp, *found = NULL; + char *msg, *workername = NULL; + sdata_t *sdata = ckp->data; + char cdfield[64]; + int height = 0; + ts_t ts_now; + json_t *val; + + if (!ckp->node) + update_base(ckp, GEN_PRIORITY); + + ts_realtime(&ts_now); + sprintf(cdfield, "%lu,%lu", ts_now.tv_sec, ts_now.tv_nsec); + + mutex_lock(&sdata->block_lock); + DL_FOREACH_SAFE(sdata->block_solves, block, tmp) { + val = block->data; + char *solvehash; + + json_get_string(&solvehash, val, "blockhash"); + if (unlikely(!solvehash)) { + LOGERR("Failed to find blockhash in block_solve json!"); + continue; + } + if (!strcmp(solvehash, blockhash)) { + dealloc(solvehash); + json_get_string(&workername, val, "workername"); + found = block; + DL_DELETE(sdata->block_solves, block); + break; + } + dealloc(solvehash); + } + mutex_unlock(&sdata->block_lock); + + if (unlikely(!found)) { + LOGERR("Failed to find blockhash %s in block_solve!", blockhash); + return; + } + + val = found->data; + json_set_string(val, "confirmed", "1"); + json_set_string(val, "createdate", cdfield); + json_set_string(val, "createcode", __func__); + json_get_int(&height, val, "height"); + ckdbq_add(ckp, ID_BLOCK, val); + free(found); + + if (unlikely(!workername)) { + /* This should be impossible! */ + ASPRINTF(&msg, "Block %d solved by %s!", height, ckp->name); + LOGWARNING("Solved and confirmed block %d", height); + } else { + json_t *user_val, *worker_val; + worker_instance_t *worker; + user_instance_t *user; + char *s; + + ASPRINTF(&msg, "Block %d solved by %s @ %s!", height, workername, ckp->name); + LOGWARNING("Solved and confirmed block %d by %s", height, workername); + user = user_by_workername(sdata, workername); + worker = get_worker(sdata, user, workername); + + ck_rlock(&sdata->instance_lock); + user_val = user_stats(user); + worker_val = worker_stats(worker); + ck_runlock(&sdata->instance_lock); + + s = json_dumps(user_val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER); + json_decref(user_val); + LOGWARNING("User %s:%s", user->username, s); + dealloc(s); + s = json_dumps(worker_val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER); + json_decref(worker_val); + LOGWARNING("Worker %s:%s", workername, s); + dealloc(s); + } + stratum_broadcast_message(sdata, msg); + free(msg); + + free(workername); + + reset_bestshares(sdata); +} + +static void block_reject(sdata_t *sdata, const char *blockhash) +{ + ckmsg_t *block, *tmp, *found = NULL; + int height = 0; + json_t *val; + + mutex_lock(&sdata->block_lock); + DL_FOREACH_SAFE(sdata->block_solves, block, tmp) { + val = block->data; + char *solvehash; + + json_get_string(&solvehash, val, "blockhash"); + if (unlikely(!solvehash)) { + LOGERR("Failed to find blockhash in block_reject json!"); + continue; + } + if (!strcmp(solvehash, blockhash)) { + dealloc(solvehash); + found = block; + DL_DELETE(sdata->block_solves, block); + break; + } + dealloc(solvehash); + } + mutex_unlock(&sdata->block_lock); + + if (unlikely(!found)) { + LOGERR("Failed to find blockhash %s in block_reject!", blockhash); + return; + } + val = found->data; + json_get_int(&height, val, "height"); + json_decref(val); + free(found); + + LOGWARNING("Submitted, but rejected block %d", height); +} + +/* Some upstream pools (like p2pool) don't update stratum often enough and + * miners disconnect if they don't receive regular communication so send them + * a ping at regular intervals */ +static void broadcast_ping(sdata_t *sdata) +{ + json_t *json_msg; + + JSON_CPACK(json_msg, "{s:[],s:i,s:s}", + "params", + "id", 42, + "method", "mining.ping"); + + stratum_broadcast(sdata, json_msg, SM_PING); +} + +static void ckmsgq_stats(ckmsgq_t *ckmsgq, const int size, json_t **val) +{ + int objects, generated; + int64_t memsize; + ckmsg_t *msg; + + mutex_lock(ckmsgq->lock); + DL_COUNT(ckmsgq->msgs, msg, objects); + generated = ckmsgq->messages; + mutex_unlock(ckmsgq->lock); + + memsize = (sizeof(ckmsg_t) + size) * objects; + JSON_CPACK(*val, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); +} + +static char *stratifier_stats(ckpool_t *ckp, sdata_t *sdata) +{ + json_t *val = json_object(), *subval; + int objects, generated; + int64_t memsize; + char *buf; + + ck_rlock(&sdata->workbase_lock); + objects = HASH_COUNT(sdata->workbases); + memsize = SAFE_HASH_OVERHEAD(sdata->workbases) + sizeof(workbase_t) * objects; + generated = sdata->workbases_generated; + ck_runlock(&sdata->workbase_lock); + + JSON_CPACK(subval, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); + json_set_object(val, "workbases", subval); + + ck_rlock(&sdata->instance_lock); + objects = HASH_COUNT(sdata->user_instances); + memsize = SAFE_HASH_OVERHEAD(sdata->user_instances) + sizeof(stratum_instance_t) * objects; + JSON_CPACK(subval, "{si,si}", "count", objects, "memory", memsize); + json_set_object(val, "users", subval); + + objects = HASH_COUNT(sdata->stratum_instances); + memsize = SAFE_HASH_OVERHEAD(sdata->stratum_instances); + generated = sdata->stratum_generated; + JSON_CPACK(subval, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); + json_set_object(val, "clients", subval); + + objects = sdata->stats.disconnected; + generated = sdata->disconnected_generated; + memsize = SAFE_HASH_OVERHEAD(sdata->disconnected_sessions); + memsize += sizeof(session_t) * sdata->stats.disconnected; + JSON_CPACK(subval, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); + json_set_object(val, "disconnected", subval); + ck_runlock(&sdata->instance_lock); + + mutex_lock(&sdata->share_lock); + generated = sdata->shares_generated; + objects = HASH_COUNT(sdata->shares); + memsize = SAFE_HASH_OVERHEAD(sdata->shares) + sizeof(share_t) * objects; + mutex_unlock(&sdata->share_lock); + + JSON_CPACK(subval, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); + json_set_object(val, "shares", subval); + + ckmsgq_stats(sdata->ssends, sizeof(smsg_t), &subval); + json_set_object(val, "ssends", subval); + /* Don't know exactly how big the string is so just count the pointer for now */ + ckmsgq_stats(sdata->srecvs, sizeof(char *), &subval); + json_set_object(val, "srecvs", subval); + if (!CKP_STANDALONE(ckp)) { + ckmsgq_stats(sdata->ckdbq, sizeof(char *), &subval); + json_set_object(val, "ckdbq", subval); } - ck_runlock(&sdata->instance_lock); + ckmsgq_stats(sdata->stxnq, sizeof(json_params_t), &subval); + json_set_object(val, "stxnq", subval); + buf = json_dumps(val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER); json_decref(val); + LOGNOTICE("Stratifier stats: %s", buf); + return buf; +} - if (!bulk_send) - return; - - ssends = sdata->ssends; +/* Send a single client a reconnect request, setting the time we sent the + * request so we can drop the client lazily if it hasn't reconnected on its + * own more than one minute later if we call reconnect again */ +static void reconnect_client(sdata_t *sdata, stratum_instance_t *client) +{ + json_t *json_msg; - mutex_lock(sdata->ssends->lock); - if (ssends->msgs) - DL_CONCAT(ssends->msgs, bulk_send); - else - ssends->msgs = bulk_send; - pthread_cond_signal(ssends->cond); - mutex_unlock(ssends->lock); + /* Already requested? */ + if (client->reconnect_request) { + if (time(NULL) - client->reconnect_request >= 60) + connector_drop_client(sdata->ckp, client->id); + return; + } + client->reconnect_request = time(NULL); + JSON_CPACK(json_msg, "{sosss[]}", "id", json_null(), "method", "client.reconnect", + "params"); + stratum_add_send(sdata, json_msg, client->id, SM_RECONNECT); } -static void stratum_add_send(sdata_t *sdata, json_t *val, const int64_t client_id) +static void dead_proxy(sdata_t *sdata, const char *buf) { - smsg_t *msg; + int id = 0, subid = 0; - msg = ckzalloc(sizeof(smsg_t)); - msg->json_msg = val; - msg->client_id = client_id; - ckmsgq_add(sdata->ssends, msg); + sscanf(buf, "deadproxy=%d:%d", &id, &subid); + dead_proxyid(sdata, id, subid, false); } -static void drop_client(sdata_t *sdata, const int64_t id) +static void reconnect_client_id(sdata_t *sdata, const int64_t client_id) { - char_entry_t *entries = NULL; stratum_instance_t *client; - char *msg; - - LOGINFO("Stratifier asked to drop client %"PRId64, id); - ck_wlock(&sdata->instance_lock); - client = __instance_by_id(sdata, id); - if (client && !client->dropped) { - __disconnect_session(sdata, client); - /* If the client is still holding a reference, don't drop them - * now but wait till the reference is dropped */ - if (!client->ref) { - __drop_client(sdata, client, false, &msg); - add_msg_entry(&entries, &msg); - } else - client->dropped = true; + client = ref_instance_by_id(sdata, client_id); + if (!client) { + LOGINFO("reconnect_client_id failed to find client %"PRId64, client_id); + return; } - ck_wunlock(&sdata->instance_lock); - - notice_msg_entries(&entries); + client->reconnect = true; + reconnect_client(sdata, client); + dec_instance_ref(sdata, client); } -static void stratum_broadcast_message(sdata_t *sdata, const char *msg) +/* API commands */ + +static user_instance_t *get_user(sdata_t *sdata, const char *username); + +static json_t *userinfo(const user_instance_t *user) { - json_t *json_msg; + json_t *val; - JSON_CPACK(json_msg, "{sosss[s]}", "id", json_null(), "method", "client.show_message", - "params", msg); - stratum_broadcast(sdata, json_msg); + JSON_CPACK(val, "{ss,si,si,sf,sf,sf,sf,sf,sf,si}", + "user", user->username, "id", user->id, "workers", user->workers, + "bestdiff", user->best_diff, "dsps1", user->dsps1, "dsps5", user->dsps5, + "dsps60", user->dsps60, "dsps1440", user->dsps1440, "dsps10080", user->dsps10080, + "lastshare", user->last_share.tv_sec); + return val; } -/* Send a generic reconnect to all clients without parameters to make them - * reconnect to the same server. */ -static void reconnect_clients(sdata_t *sdata, const char *cmd) +static void getuser(sdata_t *sdata, const char *buf, int *sockd) { - char *port = strdupa(cmd), *url = NULL; - stratum_instance_t *client, *tmp; - json_t *json_msg; - - strsep(&port, ":"); - if (port) - url = strsep(&port, ","); - if (url && port) { - JSON_CPACK(json_msg, "{sosss[ssi]}", "id", json_null(), "method", "client.reconnect", - "params", url, port, 0); - } else - JSON_CPACK(json_msg, "{sosss[]}", "id", json_null(), "method", "client.reconnect", - "params"); - stratum_broadcast(sdata, json_msg); + char *username = NULL; + user_instance_t *user; + json_error_t err_val; + json_t *val = NULL; - /* Tag all existing clients as dropped now so they can be removed - * lazily */ - ck_wlock(&sdata->instance_lock); - HASH_ITER(hh, sdata->stratum_instances, client, tmp) { - client->dropped = true; + val = json_loads(buf, 0, &err_val); + if (unlikely(!val)) { + val = json_encode_errormsg(&err_val); + goto out; } - ck_wunlock(&sdata->instance_lock); + if (!json_get_string(&username, val, "user")) { + val = json_errormsg("Failed to find user key"); + goto out; + } + if (!strlen(username)) { + val = json_errormsg("Zero length user key"); + goto out; + } + user = get_user(sdata, username); + val = userinfo(user); +out: + free(username); + send_api_response(val, *sockd); + _Close(sockd); } -static void reset_bestshares(sdata_t *sdata) +static void userclients(sdata_t *sdata, const char *buf, int *sockd) { - user_instance_t *user, *tmpuser; - stratum_instance_t *client, *tmp; + json_t *val = NULL, *client_arr; + stratum_instance_t *client; + char *username = NULL; + user_instance_t *user; + json_error_t err_val; - ck_rlock(&sdata->instance_lock); - HASH_ITER(hh, sdata->stratum_instances, client, tmp) { - client->best_diff = 0; + val = json_loads(buf, 0, &err_val); + if (unlikely(!val)) { + val = json_encode_errormsg(&err_val); + goto out; } - HASH_ITER(hh, sdata->user_instances, user, tmpuser) { - worker_instance_t *worker; + if (!json_get_string(&username, val, "user")) { + val = json_errormsg("Failed to find user key"); + goto out; + } + if (!strlen(username)) { + val = json_errormsg("Zero length user key"); + goto out; + } + user = get_user(sdata, username); + client_arr = json_array(); - user->best_diff = 0; - DL_FOREACH(user->worker_instances, worker) { - worker->best_diff = 0; - } + ck_rlock(&sdata->instance_lock); + DL_FOREACH(user->clients, client) { + json_array_append_new(client_arr, json_integer(client->id)); } ck_runlock(&sdata->instance_lock); -} -static user_instance_t *get_user(sdata_t *sdata, const char *username); + JSON_CPACK(val, "{ss,so}", "user", username, "clients", client_arr); +out: + free(username); + send_api_response(val, *sockd); + _Close(sockd); +} -static user_instance_t *user_by_workername(sdata_t *sdata, const char *workername) +static void workerclients(sdata_t *sdata, const char *buf, int *sockd) { - char *username = strdupa(workername), *ignore; + char *tmp, *username, *workername = NULL; + json_t *val = NULL, *client_arr; + stratum_instance_t *client; user_instance_t *user; + json_error_t err_val; - ignore = username; - strsep(&ignore, "._"); - - /* Find the user first */ + val = json_loads(buf, 0, &err_val); + if (unlikely(!val)) { + val = json_encode_errormsg(&err_val); + goto out; + } + if (!json_get_string(&workername, val, "worker")) { + val = json_errormsg("Failed to find worker key"); + goto out; + } + if (!strlen(workername)) { + val = json_errormsg("Zero length worker key"); + goto out; + } + tmp = strdupa(workername); + username = strsep(&tmp, "._"); user = get_user(sdata, username); - return user; + client_arr = json_array(); + + ck_rlock(&sdata->instance_lock); + DL_FOREACH(user->clients, client) { + if (strcmp(client->workername, workername)) + continue; + json_array_append_new(client_arr, json_integer(client->id)); + } + ck_runlock(&sdata->instance_lock); + + JSON_CPACK(val, "{ss,so}", "worker", workername, "clients", client_arr); +out: + free(workername); + send_api_response(val, *sockd); + _Close(sockd); } static worker_instance_t *get_worker(sdata_t *sdata, user_instance_t *user, const char *workername); -static json_t *worker_stats(const worker_instance_t *worker) +static json_t *workerinfo(const user_instance_t *user, const worker_instance_t *worker) { - char suffix1[16], suffix5[16], suffix60[16], suffix1440[16], suffix10080[16]; json_t *val; - double ghs; - ghs = worker->dsps1 * nonces; - suffix_string(ghs, suffix1, 16, 0); + JSON_CPACK(val, "{ss,ss,si,sf,sf,sf,sf,si,sf,si,sb}", + "user", user->username, "worker", worker->workername, "id", user->id, + "dsps1", worker->dsps1, "dsps5", worker->dsps5, "dsps60", worker->dsps60, + "dsps1440", worker->dsps1440, "lastshare", worker->last_share.tv_sec, + "bestdiff", worker->best_diff, "mindiff", worker->mindiff, "idle", worker->idle); + return val; +} - ghs = worker->dsps5 * nonces; - suffix_string(ghs, suffix5, 16, 0); +static void getworker(sdata_t *sdata, const char *buf, int *sockd) +{ + char *tmp, *username, *workername = NULL; + worker_instance_t *worker; + user_instance_t *user; + json_error_t err_val; + json_t *val = NULL; - ghs = worker->dsps60 * nonces; - suffix_string(ghs, suffix60, 16, 0); + val = json_loads(buf, 0, &err_val); + if (unlikely(!val)) { + val = json_encode_errormsg(&err_val); + goto out; + } + if (!json_get_string(&workername, val, "worker")) { + val = json_errormsg("Failed to find worker key"); + goto out; + } + if (!strlen(workername)) { + val = json_errormsg("Zero length worker key"); + goto out; + } + tmp = strdupa(workername); + username = strsep(&tmp, "._"); + user = get_user(sdata, username); + worker = get_worker(sdata, user, workername); + val = workerinfo(user, worker); +out: + free(workername); + send_api_response(val, *sockd); + _Close(sockd); +} - ghs = worker->dsps1440 * nonces; - suffix_string(ghs, suffix1440, 16, 0); +static void getworkers(sdata_t *sdata, int *sockd) +{ + json_t *val = NULL, *worker_arr; + worker_instance_t *worker; + user_instance_t *user; - ghs = worker->dsps10080 * nonces; - suffix_string(ghs, suffix10080, 16, 0); + worker_arr = json_array(); - JSON_CPACK(val, "{ss,ss,ss,ss,ss}", - "hashrate1m", suffix1, - "hashrate5m", suffix5, - "hashrate1hr", suffix60, - "hashrate1d", suffix1440, - "hashrate7d", suffix10080); - return val; + ck_rlock(&sdata->instance_lock); + for (user = sdata->user_instances; user; user = user->hh.next) { + DL_FOREACH(user->worker_instances, worker) { + json_array_append_new(worker_arr, workerinfo(user, worker)); + } + } + ck_runlock(&sdata->instance_lock); + + JSON_CPACK(val, "{so}", "workers", worker_arr); + send_api_response(val, *sockd); + _Close(sockd); } -static json_t *user_stats(const user_instance_t *user) +static void getusers(sdata_t *sdata, int *sockd) { - char suffix1[16], suffix5[16], suffix60[16], suffix1440[16], suffix10080[16]; - json_t *val; - double ghs; - - ghs = user->dsps1 * nonces; - suffix_string(ghs, suffix1, 16, 0); + json_t *val = NULL, *user_array; + user_instance_t *user; - ghs = user->dsps5 * nonces; - suffix_string(ghs, suffix5, 16, 0); + user_array = json_array(); - ghs = user->dsps60 * nonces; - suffix_string(ghs, suffix60, 16, 0); + ck_rlock(&sdata->instance_lock); + for (user = sdata->user_instances; user; user = user->hh.next) { + json_array_append_new(user_array, userinfo(user)); + } + ck_runlock(&sdata->instance_lock); - ghs = user->dsps1440 * nonces; - suffix_string(ghs, suffix1440, 16, 0); + JSON_CPACK(val, "{so}", "users", user_array); + send_api_response(val, *sockd); + _Close(sockd); +} - ghs = user->dsps10080 * nonces; - suffix_string(ghs, suffix10080, 16, 0); +static json_t *clientinfo(const stratum_instance_t *client) +{ + json_t *val = json_object(); - JSON_CPACK(val, "{ss,ss,ss,ss,ss}", - "hashrate1m", suffix1, - "hashrate5m", suffix5, - "hashrate1hr", suffix60, - "hashrate1d", suffix1440, - "hashrate7d", suffix10080); + /* Too many fields for a pack object, do each discretely to keep track */ + json_set_int(val, "id", client->id); + json_set_string(val, "enonce1", client->enonce1); + json_set_string(val, "enonce1var", client->enonce1var); + json_set_int(val, "enonce1_64", client->enonce1_64); + json_set_double(val, "diff", client->diff); + json_set_double(val, "dsps1", client->dsps1); + json_set_double(val, "dsps5", client->dsps5); + json_set_double(val, "dsps60", client->dsps60); + json_set_double(val, "dsps1440", client->dsps1440); + json_set_double(val, "dsps10080", client->dsps10080); + json_set_int(val, "lastshare", client->last_share.tv_sec); + json_set_int(val, "starttime", client->start_time); + json_set_string(val, "address", client->address); + json_set_bool(val, "subscribed", client->subscribed); + json_set_bool(val, "authorised", client->authorised); + json_set_bool(val, "idle", client->idle); + json_set_string(val, "useragent", client->useragent ? client->useragent : ""); + json_set_string(val, "workername", client->workername ? client->workername : ""); + json_set_int(val, "userid", client->user_id); + json_set_int(val, "server", client->server); + json_set_double(val, "bestdiff", client->best_diff); + json_set_int(val, "proxyid", client->proxyid); + json_set_int(val, "subproxyid", client->subproxyid); return val; } -static void block_solve(ckpool_t *ckp, const char *blockhash) +static void getclient(sdata_t *sdata, const char *buf, int *sockd) { - ckmsg_t *block, *tmp, *found = NULL; - char *msg, *workername = NULL; - sdata_t *sdata = ckp->data; - char cdfield[64]; - int height = 0; - ts_t ts_now; - json_t *val; - - update_base(ckp, GEN_PRIORITY); - - ts_realtime(&ts_now); - sprintf(cdfield, "%lu,%lu", ts_now.tv_sec, ts_now.tv_nsec); - - mutex_lock(&sdata->block_lock); - DL_FOREACH_SAFE(sdata->block_solves, block, tmp) { - val = block->data; - char *solvehash; - - json_get_string(&solvehash, val, "blockhash"); - if (unlikely(!solvehash)) { - LOGERR("Failed to find blockhash in block_solve json!"); - continue; - } - if (!strcmp(solvehash, blockhash)) { - dealloc(solvehash); - json_get_string(&workername, val, "workername"); - found = block; - DL_DELETE(sdata->block_solves, block); - break; - } - dealloc(solvehash); - } - mutex_unlock(&sdata->block_lock); + stratum_instance_t *client; + json_error_t err_val; + json_t *val = NULL; + int64_t client_id; - if (unlikely(!found)) { - LOGERR("Failed to find blockhash %s in block_solve!", blockhash); - return; + val = json_loads(buf, 0, &err_val); + if (unlikely(!val)) { + val = json_encode_errormsg(&err_val); + goto out; } + if (!json_get_int64(&client_id, val, "id")) { + val = json_errormsg("Failed to find id key"); + goto out; + } + client = ref_instance_by_id(sdata, client_id); + if (!client) { + val = json_errormsg("Failed to find client %"PRId64, client_id); + goto out; + } + val = clientinfo(client); - val = found->data; - json_set_string(val, "confirmed", "1"); - json_set_string(val, "createdate", cdfield); - json_set_string(val, "createcode", __func__); - json_get_int(&height, val, "height"); - ckdbq_add(ckp, ID_BLOCK, val); - free(found); - - if (unlikely(!workername)) { - /* This should be impossible! */ - ASPRINTF(&msg, "Block %d solved by %s!", height, ckp->name); - LOGWARNING("Solved and confirmed block %d", height); - } else { - json_t *user_val, *worker_val; - worker_instance_t *worker; - user_instance_t *user; - char *s; + dec_instance_ref(sdata, client); +out: + send_api_response(val, *sockd); + _Close(sockd); +} - ASPRINTF(&msg, "Block %d solved by %s @ %s!", height, workername, ckp->name); - LOGWARNING("Solved and confirmed block %d by %s", height, workername); - user = user_by_workername(sdata, workername); - worker = get_worker(sdata, user, workername); +static void getclients(sdata_t *sdata, int *sockd) +{ + json_t *val = NULL, *client_arr; + stratum_instance_t *client; - ck_rlock(&sdata->instance_lock); - user_val = user_stats(user); - worker_val = worker_stats(worker); - ck_runlock(&sdata->instance_lock); + client_arr = json_array(); - s = json_dumps(user_val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER); - json_decref(user_val); - LOGWARNING("User %s:%s", user->username, s); - dealloc(s); - s = json_dumps(worker_val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER); - json_decref(worker_val); - LOGWARNING("Worker %s:%s", workername, s); - dealloc(s); + ck_rlock(&sdata->instance_lock); + for (client = sdata->stratum_instances; client; client = client->hh.next) { + json_array_append_new(client_arr, clientinfo(client)); } - stratum_broadcast_message(sdata, msg); - free(msg); - - free(workername); + ck_runlock(&sdata->instance_lock); - reset_bestshares(sdata); + JSON_CPACK(val, "{so}", "clients", client_arr); + send_api_response(val, *sockd); + _Close(sockd); } -static void block_reject(sdata_t *sdata, const char *blockhash) +static void user_clientinfo(sdata_t *sdata, const char *buf, int *sockd) { - ckmsg_t *block, *tmp, *found = NULL; - int height = 0; - json_t *val; - - mutex_lock(&sdata->block_lock); - DL_FOREACH_SAFE(sdata->block_solves, block, tmp) { - val = block->data; - char *solvehash; + json_t *val = NULL, *client_arr; + stratum_instance_t *client; + char *username = NULL; + user_instance_t *user; + json_error_t err_val; - json_get_string(&solvehash, val, "blockhash"); - if (unlikely(!solvehash)) { - LOGERR("Failed to find blockhash in block_reject json!"); - continue; - } - if (!strcmp(solvehash, blockhash)) { - dealloc(solvehash); - found = block; - DL_DELETE(sdata->block_solves, block); - break; - } - dealloc(solvehash); + val = json_loads(buf, 0, &err_val); + if (unlikely(!val)) { + val = json_encode_errormsg(&err_val); + goto out; } - mutex_unlock(&sdata->block_lock); + if (!json_get_string(&username, val, "user")) { + val = json_errormsg("Failed to find user key"); + goto out; + } + if (!strlen(username)) { + val = json_errormsg("Zero length user key"); + goto out; + } + user = get_user(sdata, username); + client_arr = json_array(); - if (unlikely(!found)) { - LOGERR("Failed to find blockhash %s in block_reject!", blockhash); - return; + ck_rlock(&sdata->instance_lock); + DL_FOREACH(user->clients, client) { + json_array_append_new(client_arr, clientinfo(client)); } - val = found->data; - json_get_int(&height, val, "height"); - json_decref(val); - free(found); + ck_runlock(&sdata->instance_lock); - LOGWARNING("Submitted, but rejected block %d", height); + JSON_CPACK(val, "{ss,so}", "user", username, "clients", client_arr); +out: + free(username); + send_api_response(val, *sockd); + _Close(sockd); } -/* Some upstream pools (like p2pool) don't update stratum often enough and - * miners disconnect if they don't receive regular communication so send them - * a ping at regular intervals */ -static void broadcast_ping(sdata_t *sdata) +static void worker_clientinfo(sdata_t *sdata, const char *buf, int *sockd) { - json_t *json_msg; + char *tmp, *username, *workername = NULL; + json_t *val = NULL, *client_arr; + stratum_instance_t *client; + user_instance_t *user; + json_error_t err_val; - JSON_CPACK(json_msg, "{s:[],s:i,s:s}", - "params", - "id", 42, - "method", "mining.ping"); + val = json_loads(buf, 0, &err_val); + if (unlikely(!val)) { + val = json_encode_errormsg(&err_val); + goto out; + } + if (!json_get_string(&workername, val, "worker")) { + val = json_errormsg("Failed to find worker key"); + goto out; + } + if (!strlen(workername)) { + val = json_errormsg("Zero length worker key"); + goto out; + } + tmp = strdupa(workername); + username = strsep(&tmp, "._"); + user = get_user(sdata, username); + client_arr = json_array(); + + ck_rlock(&sdata->instance_lock); + DL_FOREACH(user->clients, client) { + if (strcmp(client->workername, workername)) + continue; + json_array_append_new(client_arr, clientinfo(client)); + } + ck_runlock(&sdata->instance_lock); - stratum_broadcast(sdata, json_msg); + JSON_CPACK(val, "{ss,so}", "worker", workername, "clients", client_arr); +out: + free(workername); + send_api_response(val, *sockd); + _Close(sockd); } -static void ckmsgq_stats(ckmsgq_t *ckmsgq, const int size, json_t **val) +/* Return the user masked priority value of the proxy */ +static int proxy_prio(const proxy_t *proxy) { - int objects, generated; - int64_t memsize; - ckmsg_t *msg; - - mutex_lock(ckmsgq->lock); - DL_COUNT(ckmsgq->msgs, msg, objects); - generated = ckmsgq->messages; - mutex_unlock(ckmsgq->lock); + int prio = proxy->priority & 0x00000000ffffffff; - memsize = (sizeof(ckmsg_t) + size) * objects; - JSON_CPACK(*val, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); + return prio; } -static char *stratifier_stats(ckpool_t *ckp, sdata_t *sdata) +static json_t *json_proxyinfo(const proxy_t *proxy) { - json_t *val = json_object(), *subval; - int objects, generated; - int64_t memsize; - char *buf; + const proxy_t *parent = proxy->parent; + json_t *val; - ck_rlock(&sdata->workbase_lock); - objects = HASH_COUNT(sdata->workbases); - memsize = SAFE_HASH_OVERHEAD(sdata->workbases) + sizeof(workbase_t) * objects; - generated = sdata->workbases_generated; - ck_runlock(&sdata->workbase_lock); + JSON_CPACK(val, "{si,si,si,sf,ss,ss,ss,ss,si,si,si,si,sb,sb,sI,sI,sI,sI,si,si,sb,sb,si}", + "id", proxy->id, "subid", proxy->subid, "priority", proxy_prio(parent), + "diff", proxy->diff, "url", proxy->url, "auth", proxy->auth, "pass", proxy->pass, + "enonce1", proxy->enonce1, "enonce1constlen", proxy->enonce1constlen, + "enonce1varlen", proxy->enonce1varlen, "nonce2len", proxy->nonce2len, + "enonce2varlen", proxy->enonce2varlen, "subscribed", proxy->subscribed, + "notified", proxy->notified, "clients", proxy->clients, "maxclients", proxy->max_clients, + "bound_clients", proxy->bound_clients, "combined_clients", parent->combined_clients, + "headroom", proxy->headroom, "subproxy_count", parent->subproxy_count, + "dead", proxy->dead, "global", proxy->global, "userid", proxy->userid); + return val; +} - JSON_CPACK(subval, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); - json_set_object(val, "workbases", subval); +static void getproxy(sdata_t *sdata, const char *buf, int *sockd) +{ + json_error_t err_val; + json_t *val = NULL; + int id, subid = 0; + proxy_t *proxy; - ck_rlock(&sdata->instance_lock); - objects = HASH_COUNT(sdata->user_instances); - memsize = SAFE_HASH_OVERHEAD(sdata->user_instances) + sizeof(stratum_instance_t) * objects; - JSON_CPACK(subval, "{si,si}", "count", objects, "memory", memsize); - json_set_object(val, "users", subval); + val = json_loads(buf, 0, &err_val); + if (unlikely(!val)) { + val = json_encode_errormsg(&err_val); + goto out; + } + if (!json_get_int(&id, val, "id")) { + val = json_errormsg("Failed to find id key"); + goto out; + } + json_get_int(&subid, val, "subid"); + if (!subid) + proxy = existing_proxy(sdata, id); + else + proxy = existing_subproxy(sdata, id, subid); + if (!proxy) { + val = json_errormsg("Failed to find proxy %d:%d", id, subid); + goto out; + } + val = json_proxyinfo(proxy); +out: + send_api_response(val, *sockd); + _Close(sockd); +} - objects = HASH_COUNT(sdata->stratum_instances); - memsize = SAFE_HASH_OVERHEAD(sdata->stratum_instances); - generated = sdata->stratum_generated; - JSON_CPACK(subval, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); - json_set_object(val, "clients", subval); +static void proxyinfo(sdata_t *sdata, const char *buf, int *sockd) +{ + json_t *val = NULL, *arr_val = json_array(); + proxy_t *proxy, *subproxy; + bool all = true; + int userid = 0; - objects = sdata->stats.disconnected; - generated = sdata->disconnected_generated; - memsize = SAFE_HASH_OVERHEAD(sdata->disconnected_sessions); - memsize += sizeof(session_t) * sdata->stats.disconnected; - JSON_CPACK(subval, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); - json_set_object(val, "disconnected", subval); - ck_runlock(&sdata->instance_lock); + if (buf) { + /* See if there's a userid specified */ + val = json_loads(buf, 0, NULL); + if (json_get_int(&userid, val, "userid")) + all = false; + } - mutex_lock(&sdata->share_lock); - generated = sdata->shares_generated; - objects = HASH_COUNT(sdata->shares); - memsize = SAFE_HASH_OVERHEAD(sdata->shares) + sizeof(share_t) * objects; - mutex_unlock(&sdata->share_lock); + mutex_lock(&sdata->proxy_lock); + for (proxy = sdata->proxies; proxy; proxy = proxy->hh.next) { + if (!all && proxy->userid != userid) + continue; + for (subproxy = proxy->subproxies; subproxy; subproxy = subproxy->sh.next) + json_array_append_new(arr_val, json_proxyinfo(subproxy)); + } + mutex_unlock(&sdata->proxy_lock); - JSON_CPACK(subval, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); - json_set_object(val, "shares", subval); + JSON_CPACK(val, "{so}", "proxies", arr_val); + send_api_response(val, *sockd); + _Close(sockd); +} - ckmsgq_stats(sdata->ssends, sizeof(smsg_t), &subval); - json_set_object(val, "ssends", subval); - /* Don't know exactly how big the string is so just count the pointer for now */ - ckmsgq_stats(sdata->srecvs, sizeof(char *), &subval); - json_set_object(val, "srecvs", subval); - if (!CKP_STANDALONE(ckp)) { - ckmsgq_stats(sdata->ckdbq, sizeof(char *), &subval); - json_set_object(val, "ckdbq", subval); +static void setproxy(sdata_t *sdata, const char *buf, int *sockd) +{ + json_error_t err_val; + json_t *val = NULL; + int id, priority; + proxy_t *proxy; + + val = json_loads(buf, 0, &err_val); + if (unlikely(!val)) { + val = json_encode_errormsg(&err_val); + goto out; } - ckmsgq_stats(sdata->stxnq, sizeof(json_params_t), &subval); - json_set_object(val, "stxnq", subval); + if (!json_get_int(&id, val, "id")) { + val = json_errormsg("Failed to find id key"); + goto out; + } + if (!json_get_int(&priority, val, "priority")) { + val = json_errormsg("Failed to find priority key"); + goto out; + } + proxy = existing_proxy(sdata, id); + if (!proxy) { + val = json_errormsg("Failed to find proxy %d", id); + goto out; + } + if (priority != proxy_prio(proxy)) + set_proxy_prio(sdata, proxy, priority); + val = json_proxyinfo(proxy); +out: + send_api_response(val, *sockd); + _Close(sockd); +} - buf = json_dumps(val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER); - json_decref(val); - LOGNOTICE("Stratifier stats: %s", buf); - return buf; +static void get_poolstats(sdata_t *sdata, int *sockd) +{ + pool_stats_t *stats = &sdata->stats; + json_t *val; + + mutex_lock(&sdata->stats_lock); + JSON_CPACK(val, "{si,si,si,si,si,sI,sf,sf,sf,sf,sI,sI,sf,sf,sf,sf,sf,sf,sf}", + "start", stats->start_time.tv_sec, "update", stats->last_update.tv_sec, + "workers", stats->workers, "users", stats->users, "disconnected", stats->disconnected, + "shares", stats->accounted_shares, "sps1", stats->sps1, "sps5", stats->sps5, + "sps15", stats->sps15, "sps60", stats->sps60, "accepted", stats->accounted_diff_shares, + "rejected", stats->accounted_rejects, "dsps1", stats->dsps1, "dsps5", stats->dsps5, + "dsps15", stats->dsps15, "dsps60", stats->dsps60, "dsps360", stats->dsps360, + "dsps1440", stats->dsps1440, "dsps10080", stats->dsps10080); + mutex_unlock(&sdata->stats_lock); + + send_api_response(val, *sockd); + _Close(sockd); } +static void srecv_process(ckpool_t *ckp, char *buf); + static int stratum_loop(ckpool_t *ckp, proc_instance_t *pi) { sdata_t *sdata = ckp->data; @@ -1960,6 +3344,63 @@ retry: Close(umsg->sockd); goto retry; } + /* Parse API commands here to return a message to sockd */ + if (cmdmatch(buf, "clients")) { + getclients(sdata, &umsg->sockd); + goto retry; + } + if (cmdmatch(buf, "workers")) { + getworkers(sdata, &umsg->sockd); + goto retry; + } + if (cmdmatch(buf, "users")) { + getusers(sdata, &umsg->sockd); + goto retry; + } + if (cmdmatch(buf, "getclient")) { + getclient(sdata, buf + 10, &umsg->sockd); + goto retry; + } + if (cmdmatch(buf, "getuser")) { + getuser(sdata, buf + 8, &umsg->sockd); + goto retry; + } + if (cmdmatch(buf, "getworker")) { + getworker(sdata, buf + 10, &umsg->sockd); + goto retry; + } + if (cmdmatch(buf, "userclients")) { + userclients(sdata, buf + 12, &umsg->sockd); + goto retry; + } + if (cmdmatch(buf, "workerclients")) { + workerclients(sdata, buf + 14, &umsg->sockd); + goto retry; + } + if (cmdmatch(buf, "getproxy")) { + getproxy(sdata, buf + 9, &umsg->sockd); + goto retry; + } + if (cmdmatch(buf, "setproxy")) { + setproxy(sdata, buf + 9, &umsg->sockd); + goto retry; + } + if (cmdmatch(buf, "poolstats")) { + get_poolstats(sdata, &umsg->sockd); + goto retry; + } + if (cmdmatch(buf, "proxyinfo")) { + proxyinfo(sdata, buf + 10, &umsg->sockd); + goto retry; + } + if (cmdmatch(buf, "ucinfo")) { + user_clientinfo(sdata, buf + 7, &umsg->sockd); + goto retry; + } + if (cmdmatch(buf, "wcinfo")) { + worker_clientinfo(sdata, buf + 7, &umsg->sockd); + goto retry; + } Close(umsg->sockd); LOGDEBUG("Stratifier received request: %s", buf); @@ -1970,12 +3411,12 @@ retry: update_base(ckp, GEN_PRIORITY); } else if (cmdmatch(buf, "subscribe")) { /* Proxifier has a new subscription */ - update_subscribe(ckp); + update_subscribe(ckp, buf); } else if (cmdmatch(buf, "notify")) { /* Proxifier has a new notify ready */ - update_notify(ckp); + update_notify(ckp, buf); } else if (cmdmatch(buf, "diff")) { - update_diff(ckp); + update_diff(ckp, buf); } else if (cmdmatch(buf, "dropclient")) { int64_t client_id; @@ -1983,7 +3424,15 @@ retry: if (ret < 0) LOGDEBUG("Stratifier failed to parse dropclient command: %s", buf); else - drop_client(sdata, client_id); + drop_client(ckp, sdata, client_id); + } else if (cmdmatch(buf, "reconnclient")) { + int64_t client_id; + + ret = sscanf(buf, "reconnclient=%"PRId64, &client_id); + if (ret < 0) + LOGWARNING("Stratifier failed to parse reconnclient command: %s", buf); + else + reconnect_client_id(sdata, client_id); } else if (cmdmatch(buf, "dropall")) { drop_allclients(ckp); } else if (cmdmatch(buf, "block")) { @@ -1991,7 +3440,9 @@ retry: } else if (cmdmatch(buf, "noblock")) { block_reject(sdata, buf + 8); } else if (cmdmatch(buf, "reconnect")) { - reconnect_clients(sdata, buf); + request_reconnect(sdata, buf); + } else if (cmdmatch(buf, "deadproxy")) { + dead_proxy(sdata, buf); } else if (cmdmatch(buf, "loglevel")) { sscanf(buf, "loglevel=%d", &ckp->loglevel); } else @@ -2030,34 +3481,15 @@ static void *blockupdate(void *arg) return NULL; } -/* Enter holding instance_lock */ -static bool __enonce1_free(sdata_t *sdata, const uint64_t enonce1) -{ - stratum_instance_t *client, *tmp; - bool ret = true; - - if (unlikely(!enonce1)) { - ret = false; - goto out; - } - - HASH_ITER(hh, sdata->stratum_instances, client, tmp) { - if (client->enonce1_64 == enonce1) { - ret = false; - break; - } - } -out: - return ret; -} - /* Enter holding workbase_lock and client a ref count. */ static void __fill_enonce1data(const workbase_t *wb, stratum_instance_t *client) { if (wb->enonce1constlen) memcpy(client->enonce1bin, wb->enonce1constbin, wb->enonce1constlen); - memcpy(client->enonce1bin + wb->enonce1constlen, &client->enonce1_64, wb->enonce1varlen); - __bin2hex(client->enonce1var, &client->enonce1_64, wb->enonce1varlen); + if (wb->enonce1varlen) { + memcpy(client->enonce1bin + wb->enonce1constlen, &client->enonce1_64, wb->enonce1varlen); + __bin2hex(client->enonce1var, &client->enonce1_64, wb->enonce1varlen); + } __bin2hex(client->enonce1, client->enonce1bin, wb->enonce1constlen + wb->enonce1varlen); } @@ -2066,74 +3498,195 @@ static void __fill_enonce1data(const workbase_t *wb, stratum_instance_t *client) * When the proxy space is less than 32 bits to work with, we look for an * unused enonce1 value and reject clients instead if there is no space left. * Needs to be entered with client holding a ref count. */ -static bool new_enonce1(stratum_instance_t *client) +static bool new_enonce1(ckpool_t *ckp, sdata_t *ckp_sdata, sdata_t *sdata, stratum_instance_t *client) { - sdata_t *sdata = client->ckp->data; - int enonce1varlen, i; - bool ret = false; + proxy_t *proxy = NULL; + uint64_t enonce1; + + if (ckp->proxy) { + if (!ckp_sdata->proxy) + return false; + + mutex_lock(&ckp_sdata->proxy_lock); + proxy = sdata->subproxy; + client->proxyid = proxy->id; + client->subproxyid = proxy->subid; + mutex_unlock(&ckp_sdata->proxy_lock); + + if (proxy->clients >= proxy->max_clients) { + LOGWARNING("Proxy reached max clients %"PRId64, proxy->max_clients); + return false; + } + } + + /* instance_lock protects enonce1_64. Incrementing a little endian 64bit + * number ensures that no matter how many of the bits we take from the + * left depending on nonce2 length, we'll always get a changing value + * for every next client.*/ + ck_wlock(&ckp_sdata->instance_lock); + enonce1 = le64toh(ckp_sdata->enonce1_64); + enonce1++; + client->enonce1_64 = ckp_sdata->enonce1_64 = htole64(enonce1); + if (proxy) { + client->proxy = proxy; + proxy->clients++; + proxy->bound_clients++; + proxy->parent->combined_clients++; + } + ck_wunlock(&ckp_sdata->instance_lock); - /* Extract the enonce1varlen from the current workbase which may be - * a different workbase to when we __fill_enonce1data but the value - * will not change and this avoids grabbing recursive locks */ ck_rlock(&sdata->workbase_lock); - enonce1varlen = sdata->current_workbase->enonce1varlen; + __fill_enonce1data(sdata->current_workbase, client); ck_runlock(&sdata->workbase_lock); - /* instance_lock protects sdata->enonce1u */ - ck_wlock(&sdata->instance_lock); - switch(enonce1varlen) { - case 8: - sdata->enonce1u.u64++; - ret = true; - break; - case 7: - case 6: - case 5: - case 4: - sdata->enonce1u.u32++; - ret = true; + return true; +} + +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) +{ + proxy_t *subproxy, *best = NULL, *tmp; + int64_t max_headroom; + + proxy->headroom = max_headroom = 0; + HASH_ITER(sh, proxy->subproxies, subproxy, tmp) { + int64_t subproxy_headroom; + + if (subproxy->dead) + continue; + if (!subproxy->sdata->current_workbase) + continue; + subproxy_headroom = subproxy->max_clients - subproxy->clients; + + proxy->headroom += subproxy_headroom; + if (subproxy_headroom > max_headroom) { + best = subproxy; + max_headroom = subproxy_headroom; + } + if (best) break; - case 3: - case 2: - for (i = 0; i < 65536; i++) { - sdata->enonce1u.u16++; - ret = __enonce1_free(sdata, sdata->enonce1u.u64); - if (ret) - break; - } + } + return best; +} + +/* 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(const ckpool_t *ckp, sdata_t *ckp_sdata, const int userid) +{ + proxy_t *current, *proxy, *tmp, *best = NULL; + + if (!ckp->proxy || ckp->passthrough) + return ckp_sdata; + current = ckp_sdata->proxy; + if (!current) { + LOGWARNING("No proxy available yet to generate subscribes"); + return NULL; + } + + /* Proxies are ordered by priority so first available will be the best + * priority */ + mutex_lock(&ckp_sdata->proxy_lock); + HASH_ITER(hh, ckp_sdata->proxies, proxy, tmp) { + if (proxy->userid < userid) + continue; + if (proxy->userid > userid) break; - case 1: - for (i = 0; i < 256; i++) { - sdata->enonce1u.u8++; - ret = __enonce1_free(sdata, sdata->enonce1u.u64); - if (ret) - break; - } + best = __best_subproxy(proxy); + if (best) break; - default: - quit(0, "Invalid enonce1varlen %d", enonce1varlen); } - if (ret) - client->enonce1_64 = sdata->enonce1u.u64; - ck_wunlock(&sdata->instance_lock); + mutex_unlock(&ckp_sdata->proxy_lock); - ck_rlock(&sdata->workbase_lock); - __fill_enonce1data(sdata->current_workbase, client); - ck_runlock(&sdata->workbase_lock); + if (!best) { + if (!userid) + LOGWARNING("Temporarily insufficient subproxies to accept more clients"); + return NULL; + } + if (!userid) { + if (best->id != current->id || current_headroom(ckp_sdata, &proxy) < 2) + generator_recruit(ckp, current->id, 1); + } else { + if (proxy_headroom(ckp_sdata, userid) < 2) + generator_recruit(ckp, best->id, 1); + } + return best->sdata; +} + +static int int_from_sessionid(const char *sessionid) +{ + int ret = 0, slen; + + if (!sessionid) + goto out; + slen = strlen(sessionid) / 2; + if (slen < 1 || slen > 4) + goto out; - if (unlikely(!ret)) - LOGWARNING("Enonce1 space exhausted! Proxy rejecting clients"); + if (!validhex(sessionid)) + goto out; + sscanf(sessionid, "%x", &ret); +out: return ret; } -static void stratum_send_message(sdata_t *sdata, const stratum_instance_t *client, const char *msg); +static int userid_from_sessionid(sdata_t *sdata, const int session_id) +{ + session_t *session; + int ret = -1; + + ck_wlock(&sdata->instance_lock); + HASH_FIND_INT(sdata->disconnected_sessions, &session_id, session); + if (!session) + goto out_unlock; + HASH_DEL(sdata->disconnected_sessions, session); + sdata->stats.disconnected--; + ret = session->userid; + dealloc(session); +out_unlock: + ck_wunlock(&sdata->instance_lock); + + if (ret != -1) + LOGINFO("Found old session id %d for userid %d", session_id, ret); + return ret; +} + +static int userid_from_sessionip(sdata_t *sdata, const char *address) +{ + session_t *session, *tmp; + int ret = -1; + + ck_wlock(&sdata->instance_lock); + HASH_ITER(hh, sdata->disconnected_sessions, session, tmp) { + if (!strcmp(session->address, address)) { + ret = session->userid; + break; + } + } + if (ret == -1) + goto out_unlock; + HASH_DEL(sdata->disconnected_sessions, session); + sdata->stats.disconnected--; + dealloc(session); +out_unlock: + ck_wunlock(&sdata->instance_lock); + + if (ret != -1) + LOGINFO("Found old session address %s for userid %d", address, ret); + return ret; +} /* Extranonce1 must be set here. Needs to be entered with client holding a ref * count. */ static json_t *parse_subscribe(stratum_instance_t *client, const int64_t client_id, const json_t *params_val) { - sdata_t *sdata = client->ckp->data; + ckpool_t *ckp = client->ckp; + sdata_t *sdata, *ckp_sdata = ckp->data; + int session_id = 0, userid = -1; bool old_match = false; char sessionid[12]; int arr_size; @@ -2141,12 +3694,14 @@ static json_t *parse_subscribe(stratum_instance_t *client, const int64_t client_ int n2len; if (unlikely(!json_is_array(params_val))) { - stratum_send_message(sdata, client, "Invalid json: params not an array"); + stratum_send_message(ckp_sdata, client, "Invalid json: params not an array"); return json_string("params not an array"); } - if (unlikely(!sdata->current_workbase)) { - stratum_send_message(sdata, client, "Pool Initialising"); + sdata = select_sdata(ckp, ckp_sdata, 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"); return json_string("Initialising"); } @@ -2165,22 +3720,51 @@ static json_t *parse_subscribe(stratum_instance_t *client, const int64_t client_ /* This would be the session id for reconnect, it will * not work for clients on a proxied connection. */ buf = json_string_value(json_array_get(params_val, 1)); - LOGDEBUG("Found old session id %s", buf); - /* Add matching here */ - if ((client->enonce1_64 = disconnected_sessionid_exists(sdata, buf, &client->session_id, client_id))) { + session_id = int_from_sessionid(buf); + LOGDEBUG("Found old session id %d", session_id); + } + if (!ckp->proxy && session_id && !passthrough_subclient(client_id)) { + if ((client->enonce1_64 = disconnected_sessionid_exists(sdata, session_id, client_id))) { sprintf(client->enonce1, "%016lx", client->enonce1_64); old_match = true; - ck_rlock(&sdata->workbase_lock); + ck_rlock(&ckp_sdata->workbase_lock); __fill_enonce1data(sdata->current_workbase, client); - ck_runlock(&sdata->workbase_lock); + ck_runlock(&ckp_sdata->workbase_lock); } } } else client->useragent = ckzalloc(1); + + /* We got what we needed */ + if (ckp->node) + return NULL; + + if (ckp->proxy) { + /* Use the session_id to tell us which user this was. + * If it's not available, see if there's an IP address + * which matches a recently disconnected session. */ + if (session_id) + userid = userid_from_sessionid(ckp_sdata, session_id); + if (userid == -1) + userid = userid_from_sessionip(ckp_sdata, client->address); + if (userid != -1) { + sdata_t *user_sdata = select_sdata(ckp, ckp_sdata, userid); + + if (user_sdata) + sdata = user_sdata; + } + } + + client->sdata = sdata; + if (ckp->proxy) { + LOGINFO("Current %d, selecting proxy %d:%d for client %"PRId64, ckp_sdata->proxy->id, + sdata->subproxy->id, sdata->subproxy->subid, client->id); + } + if (!old_match) { /* Create a new extranonce1 based on a uint64_t pointer */ - if (!new_enonce1(client)) { + if (!new_enonce1(ckp, ckp_sdata, sdata, client)) { stratum_send_message(sdata, client, "Pool full of clients"); client->reject = 2; return json_string("proxy full"); @@ -2192,11 +3776,9 @@ static json_t *parse_subscribe(stratum_instance_t *client, const int64_t client_ client->id, client->enonce1_64, client->enonce1); } + /* Workbases will exist if sdata->current_workbase is not NULL */ ck_rlock(&sdata->workbase_lock); - if (likely(sdata->workbases)) - n2len = sdata->workbases->enonce2varlen; - else - n2len = 8; + n2len = sdata->workbases->enonce2varlen; sprintf(sessionid, "%08x", client->session_id); JSON_CPACK(ret, "[[[s,s]],s,i]", "mining.notify", sessionid, client->enonce1, n2len); @@ -2411,7 +3993,7 @@ static user_instance_t *__create_user(sdata_t *sdata, const char *username) user->auth_backoff = DEFAULT_AUTH_BACKOFF; strcpy(user->username, username); - user->id = sdata->user_instance_id++; + user->id = ++sdata->user_instance_id; HASH_ADD_STR(sdata->user_instances, username, user); return user; } @@ -2690,14 +4272,26 @@ static void queue_delayed_auth(stratum_instance_t *client) ckdbq_add(ckp, ID_AUTH, val); } +static void check_global_user(ckpool_t *ckp, user_instance_t *user, stratum_instance_t *client) +{ + sdata_t *sdata = ckp->data; + proxy_t *proxy = best_proxy(sdata); + int proxyid = proxy->id; + char buf[256]; + + sprintf(buf, "globaluser=%d:%d:%"PRId64":%s,%s", proxyid, user->id, client->id, + user->username, client->password); + send_generator(ckp, buf, GEN_LAX); +} + /* Needs to be entered with client holding a ref count. */ static json_t *parse_authorise(stratum_instance_t *client, const json_t *params_val, json_t **err_val, int *errnum) { user_instance_t *user; ckpool_t *ckp = client->ckp; + const char *buf, *pass; bool ret = false; - const char *buf; int arr_size; ts_t now; @@ -2731,6 +4325,7 @@ static json_t *parse_authorise(stratum_instance_t *client, const json_t *params_ *err_val = json_string("Invalid character in username"); goto out; } + pass = json_string_value(json_array_get(params_val, 1)); user = generate_user(ckp, client, buf); client->user_id = user->id; ts_realtime(&now); @@ -2738,6 +4333,10 @@ static json_t *parse_authorise(stratum_instance_t *client, const json_t *params_ /* NOTE workername is NULL prior to this so should not be used in code * till after this point */ client->workername = strdup(buf); + if (pass) + client->password = strndup(pass, 64); + else + client->password = strdup(""); if (user->failed_authtime) { time_t now_t = time(NULL); @@ -2777,8 +4376,15 @@ static json_t *parse_authorise(stratum_instance_t *client, const json_t *params_ if (ret) { client->authorised = ret; user->authorised = ret; - LOGNOTICE("Authorised client %"PRId64" %s worker %s as user %s", - client->id, client->address, buf, user->username); + if (ckp->proxy) { + LOGNOTICE("Authorised client %"PRId64" to proxy %d:%d, worker %s as user %s", + client->id, client->proxyid, client->subproxyid, buf, user->username); + if (ckp->userproxy) + check_global_user(ckp, user, client); + } else { + LOGNOTICE("Authorised client %"PRId64" worker %s as user %s", + client->id, buf, user->username); + } user->auth_backoff = DEFAULT_AUTH_BACKOFF; /* Reset auth backoff time */ user->throttled = false; } else { @@ -2803,7 +4409,7 @@ static void stratum_send_diff(sdata_t *sdata, const stratum_instance_t *client) JSON_CPACK(json_msg, "{s[I]soss}", "params", client->diff, "id", json_null(), "method", "mining.set_difficulty"); - stratum_add_send(sdata, json_msg, client->id); + stratum_add_send(sdata, json_msg, client->id, SM_DIFF); } /* Needs to be entered with client holding a ref count. */ @@ -2813,7 +4419,7 @@ static void stratum_send_message(sdata_t *sdata, const stratum_instance_t *clien JSON_CPACK(json_msg, "{sosss[s]}", "id", json_null(), "method", "client.show_message", "params", msg); - stratum_add_send(sdata, json_msg, client->id); + stratum_add_send(sdata, json_msg, client->id, SM_MSG); } static double time_bias(const double tdiff, const double period) @@ -2830,20 +4436,20 @@ static double time_bias(const double tdiff, const double period) static void add_submit(ckpool_t *ckp, stratum_instance_t *client, const int diff, const bool valid, const bool submit) { + sdata_t *ckp_sdata = ckp->data, *sdata = client->sdata; worker_instance_t *worker = client->worker_instance; double tdiff, bdiff, dsps, drr, network_diff, bias; user_instance_t *user = client->user_instance; int64_t next_blockid, optimal; - sdata_t *sdata = ckp->data; tv_t now_t; - mutex_lock(&sdata->stats_lock); + mutex_lock(&ckp_sdata->stats_lock); if (valid) { - sdata->stats.unaccounted_shares++; - sdata->stats.unaccounted_diff_shares += diff; + ckp_sdata->stats.unaccounted_shares++; + ckp_sdata->stats.unaccounted_diff_shares += diff; } else - sdata->stats.unaccounted_rejects += diff; - mutex_unlock(&sdata->stats_lock); + ckp_sdata->stats.unaccounted_rejects += diff; + mutex_unlock(&ckp_sdata->stats_lock); /* Count only accepted and stale rejects in diff calculation. */ if (valid) { @@ -2852,6 +4458,9 @@ static void add_submit(ckpool_t *ckp, stratum_instance_t *client, const int diff } else if (!submit) return; + if (ckp->node) + return; + tv_time(&now_t); ck_rlock(&sdata->workbase_lock); @@ -2954,10 +4563,10 @@ test_blocksolve(const stratum_instance_t *client, const workbase_t *wb, const uc { int transactions = wb->transactions + 1; char hexcoinbase[1024], blockhash[68]; + sdata_t *sdata = client->sdata; json_t *val = NULL, *val_copy; char *gbt_block, varint[12]; ckpool_t *ckp = wb->ckp; - sdata_t *sdata = ckp->data; ckmsg_t *block_ckmsg; char cdfield[64]; uchar swap[32]; @@ -2969,7 +4578,7 @@ test_blocksolve(const stratum_instance_t *client, const workbase_t *wb, const uc LOGWARNING("Possible block solve diff %f !", diff); /* Can't submit a block in proxy mode without the transactions */ - if (wb->proxy && wb->merkles) + if (!ckp->node && wb->proxy) return; ts_realtime(&ts_now); @@ -3116,12 +4725,12 @@ static bool new_share(sdata_t *sdata, const uchar *hash, const int64_t wb_id) return ret; } -static void update_client(sdata_t *sdata, const stratum_instance_t *client, const int64_t client_id); +static void update_client(const stratum_instance_t *client, const int64_t client_id); /* 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 int msg_id) + const char *ntime, const char *nonce) { ckpool_t *ckp = client->ckp; json_t *json_msg; @@ -3129,10 +4738,10 @@ static void submit_share(stratum_instance_t *client, const int64_t jobid, const char *msg; sprintf(enonce2, "%s%s", client->enonce1var, nonce2); - JSON_CPACK(json_msg, "{sisssssssIsi}", "jobid", jobid, "nonce2", enonce2, + JSON_CPACK(json_msg, "{sIsssssssIsIsi}", "jobid", jobid, "nonce2", enonce2, "ntime", ntime, "nonce", nonce, "client_id", client->id, - "msg_id", msg_id); - msg = json_dumps(json_msg, 0); + "proxy", client->proxyid, "subproxy", client->subproxyid); + msg = json_dumps(json_msg, JSON_COMPACT); json_decref(json_msg); send_generator(ckp, msg, GEN_LAX); free(msg); @@ -3150,9 +4759,9 @@ static json_t *parse_submit(stratum_instance_t *client, json_t *json_msg, char hexhash[68] = {}, sharehash[32], cdfield[64]; const char *workername, *job_id, *ntime, *nonce; char *fname = NULL, *s, *nonce2; + sdata_t *sdata = client->sdata; enum share_err err = SE_NONE; ckpool_t *ckp = client->ckp; - sdata_t *sdata = ckp->data; char idstring[20] = {}; workbase_t *wb = NULL; uint32_t ntime32; @@ -3221,6 +4830,11 @@ static json_t *parse_submit(stratum_instance_t *client, json_t *json_msg, ck_rlock(&sdata->workbase_lock); HASH_FIND_I64(sdata->workbases, &id, wb); if (unlikely(!wb)) { + if (!sdata->current_workbase) { + ck_runlock(&sdata->workbase_lock); + + return json_boolean(false); + } id = sdata->current_workbase->id; err = SE_INVALID_JOBID; json_set_string(json_msg, "reject-reason", SHARE_ERR(err)); @@ -3305,13 +4919,13 @@ out_unlock: submit = false; } } else - LOGINFO("Rejected client %"PRId64" invalid share", client->id); + LOGINFO("Rejected client %"PRId64" invalid share %s", client->id, SHARE_ERR(err)); /* Submit share to upstream pool in proxy mode. We submit valid and * 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, json_integer_value(json_object_get(json_msg, "id"))); + submit_share(client, id, nonce2, ntime, nonce); } add_submit(ckp, client, diff, result, submit); @@ -3366,7 +4980,7 @@ out: } else if (client->first_invalid && client->first_invalid < now_t - 60) { if (!client->reject) { LOGINFO("Client %"PRId64" rejecting for 60s, sending update", client->id); - update_client(sdata, client, client->id); + update_client(client, client->id); client->reject = 1; } } @@ -3418,27 +5032,36 @@ static json_t *__stratum_notify(const workbase_t *wb, const bool clean) return val; } -static void stratum_broadcast_update(sdata_t *sdata, const bool clean) +static void stratum_broadcast_update(sdata_t *sdata, const workbase_t *wb, const bool clean) { json_t *json_msg; ck_rlock(&sdata->workbase_lock); - json_msg = __stratum_notify(sdata->current_workbase, clean); + json_msg = __stratum_notify(wb, clean); ck_runlock(&sdata->workbase_lock); - stratum_broadcast(sdata, json_msg); + stratum_broadcast(sdata, json_msg, SM_UPDATE); } /* For sending a single stratum template update */ static void stratum_send_update(sdata_t *sdata, const int64_t client_id, const bool clean) { + ckpool_t *ckp = sdata->ckp; json_t *json_msg; + if (unlikely(!sdata->current_workbase)) { + if (!ckp->proxy) + LOGWARNING("No current workbase to send stratum update"); + else + LOGDEBUG("No current workbase to send stratum update for client %"PRId64, client_id); + return; + } + ck_rlock(&sdata->workbase_lock); json_msg = __stratum_notify(sdata->current_workbase, clean); ck_runlock(&sdata->workbase_lock); - stratum_add_send(sdata, json_msg, client_id); + stratum_add_send(sdata, json_msg, client_id, SM_UPDATE); } static void send_json_err(sdata_t *sdata, const int64_t client_id, json_t *id_val, const char *err_msg) @@ -3446,12 +5069,14 @@ static void send_json_err(sdata_t *sdata, const int64_t client_id, json_t *id_va json_t *val; JSON_CPACK(val, "{soss}", "id", json_deep_copy(id_val), "error", err_msg); - stratum_add_send(sdata, val, client_id); + stratum_add_send(sdata, val, client_id, SM_ERROR); } /* Needs to be entered with client holding a ref count. */ -static void update_client(sdata_t *sdata, const stratum_instance_t *client, const int64_t client_id) +static void update_client(const stratum_instance_t *client, const int64_t client_id) { + sdata_t *sdata = client->sdata; + stratum_send_update(sdata, client_id, true); stratum_send_diff(sdata, client); } @@ -3549,12 +5174,23 @@ static void init_client(sdata_t *sdata, const stratum_instance_t *client, const stratum_send_update(sdata, client_id, true); } +static void add_mining_node(sdata_t *sdata, stratum_instance_t *client) +{ + client->node = true; + + ck_wlock(&sdata->instance_lock); + DL_APPEND(sdata->node_instances, client); + ck_wunlock(&sdata->instance_lock); + + LOGWARNING("Added client %"PRId64" %s as mining node!", client->id, client->address); +} + /* Enter with client holding ref count */ -static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64_t client_id, - json_t *id_val, json_t *method_val, json_t *params_val) +static void parse_method(ckpool_t *ckp, sdata_t *sdata, stratum_instance_t *client, + const int64_t client_id, json_t *id_val, json_t *method_val, + json_t *params_val) { const char *method; - char buf[256]; /* Random broken clients send something not an integer as the id so we * copy the json item for id_val as is for the response. By far the @@ -3567,6 +5203,12 @@ static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64 return; } + if (cmdmatch(method, "mining.term")) { + LOGDEBUG("Mining terminate requested from %"PRId64" %s", client_id, client->address); + drop_client(ckp, sdata, client_id); + return; + } + if (cmdmatch(method, "mining.subscribe")) { json_t *val, *result_val; @@ -3585,29 +5227,41 @@ static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64 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); + stratum_add_send(sdata, val, client_id, SM_SUBSCRIBERESULT); if (likely(client->subscribed)) init_client(sdata, client, client_id); return; } + if (unlikely(cmdmatch(method, "mining.node"))) { + char buf[256]; + + /* Add this client as a passthrough in the connector and + * add it to the list of mining nodes in the stratifier */ + add_mining_node(sdata, client); + snprintf(buf, 255, "passthrough=%"PRId64, client_id); + send_proc(ckp->connector, buf); + return; + } + if (unlikely(cmdmatch(method, "mining.passthrough"))) { + char buf[256]; + /* We need to inform the connector process that this client * is a passthrough and to manage its messages accordingly. No * data from this client id should ever come back to this * stratifier after this so drop the client in the stratifier. */ LOGNOTICE("Adding passthrough client %"PRId64" %s", client_id, client->address); snprintf(buf, 255, "passthrough=%"PRId64, client_id); - send_proc(client->ckp->connector, buf); - drop_client(sdata, client_id); + send_proc(ckp->connector, buf); + drop_client(ckp, sdata, client_id); return; } /* We should only accept subscribed requests from here on */ if (!client->subscribed) { LOGINFO("Dropping unsubscribed client %"PRId64" %s", client_id, client->address); - snprintf(buf, 255, "dropclient=%"PRId64, client_id); - send_proc(client->ckp->connector, buf); + connector_drop_client(ckp, client_id); return; } @@ -3644,12 +5298,6 @@ static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64 return; } - if (cmdmatch(method, "mining.term")) { - LOGDEBUG("Mining terminate requested from %"PRId64" %s", client_id, client->address); - drop_client(sdata, client_id); - return; - } - /* Unhandled message here */ LOGINFO("Unhandled client %"PRId64" %s method %s", client_id, client->address, method); return; @@ -3661,18 +5309,134 @@ static void free_smsg(smsg_t *msg) free(msg); } +static void parse_diff(stratum_instance_t *client, json_t *val) +{ + double diff = json_number_value(json_array_get(val, 0)); + + LOGINFO("Set client %"PRId64" to diff %lf", client->id, diff); + client->diff = diff; +} + +static void parse_subscribe_result(stratum_instance_t *client, json_t *val) +{ + int len; + + strncpy(client->enonce1, json_string_value(json_array_get(val, 1)), 16); + len = strlen(client->enonce1) / 2; + hex2bin(client->enonce1bin, client->enonce1, len); + memcpy(&client->enonce1_64, client->enonce1bin, 8); + LOGINFO("Client %"PRId64" got enonce1 %lx string %s", client->id, client->enonce1_64, client->enonce1); +} + +static void parse_authorise_result(stratum_instance_t *client, json_t *val) +{ + client->authorised = json_is_true(val); + LOGDEBUG("Client %"PRId64" is %sauthorised", client->id, client->authorised ? "" : "not "); +} + +static int node_msg_type(json_t *val) +{ + int i, ret = -1; + char *method; + + if (!val) + goto out; + if (!json_get_string(&method, val, "node.method")) + goto out; + for (i = 0; i < SM_NONE; i++) { + if (!strcmp(method, stratum_msgs[i])) { + ret = i; + break; + } + } + json_object_del(val, "node.method"); +out: + if (ret < 0 && json_get_string(&method, val, "method")) { + if (!safecmp(method, "mining.submit")) + ret = SM_SHARE; + else if (!safecmp(method, "mining.notify")) + ret = SM_UPDATE; + else if (!safecmp(method, "mining.subscribe")) + ret = SM_SUBSCRIBE; + else if (cmdmatch(method, "mining.auth")) + ret = SM_AUTH; + else if (cmdmatch(method, "mining.get")) + ret = SM_TXNS; + } + return ret; +} + +/* Entered with client holding ref count */ +static void node_client_msg(ckpool_t *ckp, json_t *val, const char *buf, stratum_instance_t *client) +{ + json_t *params, *method, *res_val, *id_val, *err_val = NULL; + int msg_type = node_msg_type(val); + sdata_t *sdata = ckp->data; + json_params_t *jp; + int errnum; + + if (msg_type < 0) { + LOGERR("Missing client %"PRId64" node method from %s", client->id, buf); + return; + } + LOGDEBUG("Got client %"PRId64" node method %d:%s", client->id, msg_type, stratum_msgs[msg_type]); + id_val = json_object_get(val, "id"); + method = json_object_get(val, "method"); + params = json_object_get(val, "params"); + res_val = json_object_get(val, "result"); + switch (msg_type) { + case SM_SHARE: + jp = create_json_params(client->id, method, params, id_val); + ckmsgq_add(sdata->sshareq, jp); + break; + case SM_DIFF: + parse_diff(client, params); + break; + case SM_SUBSCRIBE: + parse_subscribe(client, client->id, params); + break; + case SM_SUBSCRIBERESULT: + parse_subscribe_result(client, res_val); + break; + case SM_AUTH: + parse_authorise(client, params, &err_val, &errnum); + break; + case SM_AUTHRESULT: + parse_authorise_result(client, res_val); + break; + default: + break; + } +} + +static void parse_node_msg(ckpool_t *ckp, json_t *val, const char *buf) +{ + int msg_type = node_msg_type(val); + + if (msg_type < 0) { + LOGERR("Missing node method from %s", buf); + return; + } + LOGDEBUG("Got node method %d:%s", msg_type, stratum_msgs[msg_type]); + switch (msg_type) { + case SM_WORKINFO: + add_node_base(ckp, val); + break; + default: + break; + } +} + /* Entered with client holding ref count */ -static void parse_instance_msg(sdata_t *sdata, smsg_t *msg, stratum_instance_t *client) +static void parse_instance_msg(ckpool_t *ckp, sdata_t *sdata, smsg_t *msg, stratum_instance_t *client) { json_t *val = msg->json_msg, *id_val, *method, *params; int64_t client_id = msg->client_id; - char buf[256]; - if (unlikely(client->reject == 2)) { + if (client->reject == 2) { LOGINFO("Dropping client %"PRId64" %s tagged for lazy invalidation", client_id, client->address); - snprintf(buf, 255, "dropclient=%"PRId64, client_id); - send_proc(client->ckp->connector, buf); + connector_drop_client(ckp, client_id); goto out; } @@ -3706,7 +5470,7 @@ static void parse_instance_msg(sdata_t *sdata, smsg_t *msg, stratum_instance_t * send_json_err(sdata, client_id, id_val, "-1:params not found"); goto out; } - parse_method(sdata, client, client_id, id_val, method, params); + parse_method(ckp, sdata, client, client_id, id_val, method, params); out: free_smsg(msg); } @@ -3730,7 +5494,10 @@ static void srecv_process(ckpool_t *ckp, char *buf) msg->json_msg = val; val = json_object_get(msg->json_msg, "client_id"); if (unlikely(!val)) { - LOGWARNING("Failed to extract client_id from connector json smsg %s", buf); + if (ckp->node) + parse_node_msg(ckp, msg->json_msg, buf); + else + LOGWARNING("Failed to extract client_id from connector json smsg %s", buf); json_decref(msg->json_msg); free(msg); goto out; @@ -3776,13 +5543,17 @@ static void srecv_process(ckpool_t *ckp, char *buf) /* Client may be NULL here */ LOGNOTICE("Stratifier skipped dropped instance %"PRId64" message from server %d", msg->client_id, server); + connector_drop_client(ckp, msg->client_id); free_smsg(msg); goto out; } if (unlikely(noid)) LOGINFO("Stratifier added instance %"PRId64" server %d", client->id, server); - parse_instance_msg(sdata, msg, client); + if (ckp->node) + node_client_msg(ckp, msg->json_msg, buf, client); + else + parse_instance_msg(ckp, sdata, msg, client); dec_instance_ref(sdata, client); out: free(buf); @@ -3801,7 +5572,7 @@ static void ssend_process(ckpool_t *ckp, smsg_t *msg) /* Add client_id to the json message and send it to the * connector process to be delivered */ json_object_set_new_nocheck(msg->json_msg, "client_id", json_integer(msg->client_id)); - s = json_dumps(msg->json_msg, 0); + s = json_dumps(msg->json_msg, JSON_COMPACT); send_proc(ckp->connector, s); free(s); free_smsg(msg); @@ -3846,7 +5617,7 @@ static void sshare_process(ckpool_t *ckp, json_params_t *jp) json_object_set_new_nocheck(json_msg, "result", result_val); json_object_set_new_nocheck(json_msg, "error", err_val ? err_val : json_null()); steal_json_id(json_msg, jp); - stratum_add_send(sdata, json_msg, client_id); + stratum_add_send(sdata, json_msg, client_id, SM_SHARERESULT); out_decref: dec_instance_ref(sdata, client); out: @@ -3908,7 +5679,7 @@ static void sauth_process(ckpool_t *ckp, json_params_t *jp) json_object_set_new_nocheck(json_msg, "result", result_val); json_object_set_new_nocheck(json_msg, "error", err_val ? err_val : json_null()); steal_json_id(json_msg, jp); - stratum_add_send(sdata, json_msg, client_id); + stratum_add_send(sdata, json_msg, client_id, SM_AUTHRESULT); if (!json_is_true(result_val) || !client->suggest_diff) goto out; @@ -4123,7 +5894,7 @@ static void send_transactions(ckpool_t *ckp, json_params_t *jp) } else json_set_string(val, "error", "Invalid job_id"); out_send: - stratum_add_send(sdata, val, jp->client_id); + stratum_add_send(sdata, val, jp->client_id, SM_TXNSRESULT); out: if (client) dec_instance_ref(sdata, client); @@ -4258,12 +6029,12 @@ static void *statsupdate(void *arg) double ghs, ghs1, ghs5, ghs15, ghs60, ghs360, ghs1440, ghs10080, per_tdiff; char suffix1[16], suffix5[16], suffix15[16], suffix60[16], cdfield[64]; char suffix360[16], suffix1440[16], suffix10080[16]; - char_entry_t *char_list = NULL, *char_t, *chartmp_t; stratum_instance_t *client, *tmp; log_entry_t *log_entries = NULL; user_instance_t *user, *tmpuser; + char_entry_t *char_list = NULL; int idle_workers = 0; - char *fname, *s; + char *fname, *s, *sp; tv_t now, diff; ts_t ts_now; json_t *val; @@ -4375,11 +6146,10 @@ static void *statsupdate(void *arg) s = json_dumps(val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER | JSON_EOL); add_log_entry(&log_entries, &fname, &s); if (!idle) { - char_t = ckalloc(sizeof(char_entry_t)); s = json_dumps(val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER); - ASPRINTF(&char_t->buf, "User %s:%s", user->username, s); + ASPRINTF(&sp, "User %s:%s", user->username, s); dealloc(s); - DL_APPEND(char_list, char_t); + add_msg_entry(&char_list, &sp); } json_decref(val); } @@ -4387,13 +6157,7 @@ static void *statsupdate(void *arg) /* Dump log entries out of instance_lock */ dump_log_entries(&log_entries); - - DL_FOREACH_SAFE(char_list, char_t, chartmp_t) { - LOGNOTICE("%s", char_t->buf); - DL_DELETE(char_list, char_t); - free(char_t->buf); - dealloc(char_t); - } + notice_msg_entries(&char_list); ghs1 = stats->dsps1 * nonces; suffix_string(ghs1, suffix1, 16, 0); @@ -4461,6 +6225,53 @@ static void *statsupdate(void *arg) dealloc(s); fclose(fp); + if (ckp->proxy && sdata->proxy) { + proxy_t *proxy, *proxytmp, *subproxy, *subtmp; + + mutex_lock(&sdata->proxy_lock); + JSON_CPACK(val, "{sI,si,si}", + "current", sdata->proxy->id, + "active", HASH_COUNT(sdata->proxies), + "total", sdata->proxy_count); + mutex_unlock(&sdata->proxy_lock); + + s = json_dumps(val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER); + json_decref(val); + LOGNOTICE("Proxy:%s", s); + dealloc(s); + + mutex_lock(&sdata->proxy_lock); + HASH_ITER(hh, sdata->proxies, proxy, proxytmp) { + JSON_CPACK(val, "{sI,si,sI,sb}", + "id", proxy->id, + "subproxies", proxy->subproxy_count, + "clients", proxy->combined_clients, + "alive", !proxy->dead); + s = json_dumps(val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER); + json_decref(val); + ASPRINTF(&sp, "Proxies:%s", s); + dealloc(s); + add_msg_entry(&char_list, &sp); + HASH_ITER(sh, proxy->subproxies, subproxy, subtmp) { + JSON_CPACK(val, "{sI,si,si,sI,sI,sf,sb}", + "id", subproxy->id, + "subid", subproxy->subid, + "nonce2len", subproxy->nonce2len, + "clients", subproxy->bound_clients, + "maxclients", subproxy->max_clients, + "diff", subproxy->diff, + "alive", !subproxy->dead); + s = json_dumps(val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER); + json_decref(val); + ASPRINTF(&sp, "Subproxies:%s", s); + dealloc(s); + add_msg_entry(&char_list, &sp); + } + } + mutex_unlock(&sdata->proxy_lock); + info_msg_entries(&char_list); + } + ts_realtime(&ts_now); sprintf(cdfield, "%lu,%lu", ts_now.tv_sec, ts_now.tv_nsec); JSON_CPACK(val, "{ss,si,si,si,sf,sf,sf,sf,ss,ss,ss,ss}", @@ -4654,6 +6465,8 @@ int stratifier(proc_instance_t *pi) LOGWARNING("%s stratifier starting", ckp->name); sdata = ckzalloc(sizeof(sdata_t)); ckp->data = sdata; + sdata->ckp = ckp; + sdata->verbose = true; /* Wait for the generator to have something for us */ do { @@ -4661,8 +6474,11 @@ int stratifier(proc_instance_t *pi) ret = 1; goto out; } + if (ckp->proxy) + break; buf = send_recv_proc(ckp->generator, "ping"); } while (!buf); + dealloc(buf); if (!ckp->proxy) { if (!test_address(ckp, ckp->btcaddress)) { @@ -4692,14 +6508,13 @@ int stratifier(proc_instance_t *pi) } } - randomiser = ((int64_t)time(NULL)) << 32; + randomiser = time(NULL); + sdata->enonce1_64 = htole64(randomiser); /* Set the initial id to time as high bits so as to not send the same * id on restarts */ + randomiser <<= 32; if (!ckp->proxy) sdata->blockchange_id = sdata->workbase_id = randomiser; - sdata->enonce1u.u64 = htobe64(randomiser); - - dealloc(buf); if (!ckp->serverurls) { ckp->serverurl[0] = "127.0.0.1"; @@ -4711,15 +6526,19 @@ int stratifier(proc_instance_t *pi) mutex_init(&sdata->ckdb_lock); mutex_init(&sdata->ckdb_msg_lock); - sdata->ssends = create_ckmsgq(ckp, "ssender", &ssend_process); /* Create half as many share processing threads as there are CPUs */ threads = sysconf(_SC_NPROCESSORS_ONLN) / 2 ? : 1; sdata->sshareq = create_ckmsgqs(ckp, "sprocessor", &sshare_process, threads); /* Create 1/4 as many stratum processing threads as there are CPUs */ - threads = threads / 2 ? : 1; + if (ckp->node) + threads = 1; + else { + sdata->ssends = create_ckmsgq(ckp, "ssender", &ssend_process); + threads = threads / 2 ? : 1; + sdata->sauthq = create_ckmsgq(ckp, "authoriser", &sauth_process); + sdata->stxnq = create_ckmsgq(ckp, "stxnq", &send_transactions); + } sdata->srecvs = create_ckmsgqs(ckp, "sreceiver", &srecv_process, threads); - sdata->sauthq = create_ckmsgq(ckp, "authoriser", &sauth_process); - sdata->stxnq = create_ckmsgq(ckp, "stxnq", &send_transactions); if (!CKP_STANDALONE(ckp)) { sdata->ckdbq = create_ckmsgq(ckp, "ckdbqueue", &ckdbq_process); create_pthread(&pth_heartbeat, ckdb_heartbeat, ckp); @@ -4729,9 +6548,12 @@ int stratifier(proc_instance_t *pi) cklock_init(&sdata->workbase_lock); if (!ckp->proxy) create_pthread(&pth_blockupdate, blockupdate, ckp); + else { + mutex_init(&sdata->proxy_lock); + } mutex_init(&sdata->stats_lock); - if (!ckp->passthrough) + if (!ckp->passthrough || ckp->node) create_pthread(&pth_statsupdate, statsupdate, ckp); mutex_init(&sdata->share_lock); @@ -4743,6 +6565,16 @@ int stratifier(proc_instance_t *pi) ret = stratum_loop(ckp, pi); out: + if (ckp->proxy) { + proxy_t *proxy, *tmpproxy; + + mutex_lock(&sdata->proxy_lock); + HASH_ITER(hh, sdata->proxies, proxy, tmpproxy) { + HASH_DEL(sdata->proxies, proxy); + dealloc(proxy); + } + mutex_unlock(&sdata->proxy_lock); + } dealloc(ckp->data); return process_exit(ckp, pi, ret); } diff --git a/src/uthash.h b/src/uthash.h index 10c9b6b7..31db3fa8 100644 --- a/src/uthash.h +++ b/src/uthash.h @@ -265,6 +265,8 @@ do { HASH_FIND(hh,head,findint,sizeof(int64_t),out) #define HASH_ADD_I64(head,intfield,add) \ HASH_ADD(hh,head,intfield,sizeof(int64_t),add) +#define HASH_REPLACE_I64(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int64_t),add,replaced) #define HASH_FIND_PTR(head,findptr,out) \ HASH_FIND(hh,head,findptr,sizeof(void *),out) #define HASH_ADD_PTR(head,ptrfield,add) \