From b5a2dd5f5bdbbe706c8ad6a4f21b7e0983bb996b Mon Sep 17 00:00:00 2001 From: Jethro Grassie Date: Fri, 5 Jul 2019 17:28:04 -0400 Subject: [PATCH] implement miner selected block templates --- Makefile | 188 +++++++++++----------- README.md | 6 + pool.conf | 1 + qr-small.png | Bin 0 -> 552 bytes src/pool.c | 375 ++++++++++++++++++++++++++++++++++--------- src/util.c | 13 +- src/util.h | 8 +- src/webui-embed.html | 3 + src/webui.c | 6 +- src/webui.h | 1 + src/xmr.cpp | 85 +++++++++- src/xmr.h | 15 +- stratum-ss.md | 174 ++++++++++++++++++++ 13 files changed, 689 insertions(+), 186 deletions(-) create mode 100644 qr-small.png create mode 100644 stratum-ss.md diff --git a/Makefile b/Makefile index bb934f4..dedfcc8 100644 --- a/Makefile +++ b/Makefile @@ -1,65 +1,60 @@ -define LICENSE - -Copyright (c) 2014-2019, The Monero Project - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Parts of the project are originally copyright (c) 2012-2013 The Cryptonote -developers. - -endef +# Copyright (c) 2019, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. TARGET = monero-pool TYPE = debug ifeq ($(MAKECMDGOALS),release) -TYPE = release + TYPE = release endif -MONERO_BUILD_ROOT = ${MONERO_ROOT}/build/$(shell echo `uname | \ - sed -e 's|[:/\\ \(\)]|_|g'`/`\ - git -C ${MONERO_ROOT} branch | grep '\* ' | cut -f2- -d' '| \ - sed -e 's|[:/\\ \(\)]|_|g'`)/release +MONERO_BUILD_ROOT = \ + ${MONERO_ROOT}/build/$(shell echo `uname | \ + sed -e 's|[:/\\ \(\)]|_|g'`/` \ + git -C ${MONERO_ROOT} branch | \ + grep '\* ' | cut -f2- -d' '| \ + sed -e 's|[:/\\ \(\)]|_|g'`)/release -MONERO_INC = ${MONERO_ROOT}/src \ - ${MONERO_ROOT}/external/easylogging++ \ - ${MONERO_ROOT}/contrib/epee/include +MONERO_INC = \ + ${MONERO_ROOT}/src \ + ${MONERO_ROOT}/external \ + ${MONERO_ROOT}/external/easylogging++ \ + ${MONERO_ROOT}/contrib/epee/include MONERO_LIBS = \ ${MONERO_BUILD_ROOT}/src/cryptonote_basic/libcryptonote_basic.a \ - ${MONERO_BUILD_ROOT}/src/cryptonote_core/libcryptonote_core.a \ ${MONERO_BUILD_ROOT}/src/crypto/libcncrypto.a \ ${MONERO_BUILD_ROOT}/src/common/libcommon.a \ - ${MONERO_BUILD_ROOT}/src/ringct/libringct.a \ ${MONERO_BUILD_ROOT}/src/ringct/libringct_basic.a \ ${MONERO_BUILD_ROOT}/src/device/libdevice.a \ - ${MONERO_BUILD_ROOT}/src/blockchain_db/libblockchain_db.a \ ${MONERO_BUILD_ROOT}/contrib/epee/src/libepee.a \ ${MONERO_BUILD_ROOT}/external/easylogging++/libeasylogging.a @@ -70,48 +65,52 @@ OS := $(shell uname -s) CPPDEFS = _GNU_SOURCE AUTO_INITIALIZE_EASYLOGGINGPP LOG_USE_COLOR CCPARAM = -Wall $(CFLAGS) -maes -fPIC -CXXFLAGS = -std=c++11 +CXXFLAGS = -std=c++11 -Wno-reorder ifeq ($(OS), Darwin) -CXXFLAGS += -stdlib=libc++ -CPPDEFS += HAVE_MEMSET_S + CXXFLAGS += -stdlib=libc++ + CPPDEFS += HAVE_MEMSET_S endif ifeq ($(OS),Darwin) -LDPARAM = + LDPARAM = else -LDPARAM = -Wl,-warn-unresolved-symbols -fPIC -pie + LDPARAM = -Wl,-warn-unresolved-symbols -fPIC -pie endif ifeq ($(TYPE),debug) -CCPARAM += -g -CPPDEFS += DEBUG + CCPARAM += -g + CPPDEFS += DEBUG endif ifeq ($(TYPE), release) -CCPARAM += -O3 -Wno-unused-variable -ifneq ($(OS), Darwin) -LDPARAM = -Wl,--unresolved-symbols=ignore-in-object-files -endif + CCPARAM += -O3 -Wno-unused-variable + ifneq ($(OS), Darwin) + LDPARAM = -Wl,--unresolved-symbols=ignore-in-object-files + endif endif LDPARAM += $(LDFLAGS) LIBS := lmdb pthread microhttpd unbound ifeq ($(OS), Darwin) -LIBS += c++ boost_system-mt boost_date_time-mt boost_chrono-mt \ - boost_filesystem-mt boost_thread-mt boost_regex-mt + LIBS += c++ \ + boost_system-mt boost_date_time-mt boost_chrono-mt \ + boost_filesystem-mt boost_thread-mt boost_regex-mt \ + boost_serialization-mt boost_program_options-mt else -LIBS += dl boost_system boost_date_time boost_chrono boost_filesystem \ - boost_thread boost_regex uuid + LIBS += dl uuid \ + boost_system boost_date_time boost_chrono \ + boost_filesystem boost_thread boost_regex \ + boost_serialization boost_program_options endif PKG_LIBS := $(shell pkg-config \ - "libevent >= 2.1" \ - json-c \ - openssl \ - libsodium \ - --libs) + "libevent >= 2.1" \ + json-c \ + openssl \ + libsodium \ + --libs) STATIC_LIBS = DLIBS = @@ -119,11 +118,11 @@ DLIBS = INCPATH := $(DIRS) ${MONERO_INC} /opt/local/include /usr/local/include PKG_INC := $(shell pkg-config \ - "libevent >= 2.1" \ - json-c \ - openssl \ - libsodium \ - --cflags) + "libevent >= 2.1" \ + json-c \ + openssl \ + libsodium \ + --cflags) LIBPATH := /opt/local/lib/ /usr/local/lib @@ -150,36 +149,42 @@ SDFILES := $(addprefix $(STORE)/,$(CSOURCE:.S=.d)) $(TARGET): preflight dirs $(OBJECTS) $(COBJECTS) $(SOBJECTS) $(HTMLOBJECTS) @echo Linking $(OBJECTS)... - $(C++) -o $(STORE)/$(TARGET) $(OBJECTS) $(COBJECTS) $(SOBJECTS) $(HTMLOBJECTS) \ - $(LDPARAM) $(MONERO_LIBS) \ - $(foreach LIBRARY, $(LIBS),-l$(LIBRARY)) \ - $(foreach LIB,$(LIBPATH),-L$(LIB)) \ - $(PKG_LIBS) $(STATIC_LIBS) + $(C++) -o $(STORE)/$(TARGET) \ + $(OBJECTS) $(COBJECTS) $(SOBJECTS) $(HTMLOBJECTS) \ + $(LDPARAM) $(MONERO_LIBS) \ + $(foreach LIBRARY, $(LIBS),-l$(LIBRARY)) \ + $(foreach LIB,$(LIBPATH),-L$(LIB)) \ + $(PKG_LIBS) $(STATIC_LIBS) @cp pool.conf $(STORE)/ @cp tools/* $(STORE)/ $(STORE)/%.o: %.cpp @echo Creating object file for $*... $(C++) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) $(CXXFLAGS) \ - $(foreach INC,$(INCPATH),-I$(INC)) \ - $(PKG_INC) \ - $(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) \ - -c $< -o $@ - @sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' $(STORE)/$*.dd > $(STORE)/$*.d + $(foreach INC,$(INCPATH),-I$(INC)) \ + $(PKG_INC) \ + $(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) \ + -c $< -o $@ + @sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' \ + $(STORE)/$*.dd > $(STORE)/$*.d @rm -f $(STORE)/$*.dd $(STORE)/%.o: %.c @echo Creating object file for $*... - $(CC) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) $(foreach INC,$(INCPATH),-I$(INC)) $(PKG_INC)\ - $(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) -c $< -o $@ - @sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' $(STORE)/$*.dd > $(STORE)/$*.d + $(CC) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) \ + $(foreach INC,$(INCPATH),-I$(INC)) $(PKG_INC) \ + $(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) -c $< -o $@ + @sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' \ + $(STORE)/$*.dd > $(STORE)/$*.d @rm -f $(STORE)/$*.dd $(STORE)/%.o: %.S @echo Creating object file for $*... - $(CC) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) $(foreach INC,$(INCPATH),-I$(INC)) $(PKG_INC)\ - $(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) -c $< -o $@ - @sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' $(STORE)/$*.dd > $(STORE)/$*.d + $(CC) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) \ + $(foreach INC,$(INCPATH),-I$(INC)) $(PKG_INC) \ + $(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) -c $< -o $@ + @sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' \ + $(STORE)/$*.dd > $(STORE)/$*.d @rm -f $(STORE)/$*.dd $(STORE)/%.o: %.html @@ -202,17 +207,18 @@ clean: dirs: @-if [ ! -e $(STORE) ]; then mkdir -p $(STORE); fi; @-$(foreach DIR,$(DIRS), \ - if [ ! -e $(STORE)/$(DIR) ]; then mkdir -p $(STORE)/$(DIR); fi; ) + if [ ! -e $(STORE)/$(DIR) ]; then mkdir -p $(STORE)/$(DIR); fi; ) preflight: ifeq ($(origin MONERO_ROOT), undefined) - $(error You need to set an environment variable MONERO_ROOT to your monero repository root) + $(error You need to set an environment variable MONERO_ROOT \ + to your monero repository root) endif ifndef PKG_LIBS - $(error Missing dependencies) + $(error Missing dependencies) endif ifndef XXD - $(error Command xxd not found) + $(error Command xxd not found) endif -include $(DFILES) diff --git a/README.md b/README.md index 59e55c3..8687095 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ The single payout mechanism is PPLNS, which favors loyal pool miners. I have no plans to add any other payout mechanisms or other coins. Work should stay focussed on performance, efficiency and stability. +The pool also now supports a new, experimental and optional, method of mining +for the pool miners, whereby miners can select their *own* block template to +mine on. Further information can be found in [stratum-ss.md](./stratum-ss.md). + ## Project status I have tested this quite a bit on the Monero testnet (if you plan @@ -134,6 +138,8 @@ pool software has), so if you use it and want to donate, XMR donations to: 451ytzQg1vUVkuAW73VsQ72G96FUjASi4WNQse3v8ALfjiR5vLzGQ2hMUdYhG38Fi15eJ5FJ1ZL4EV1SFVi228muGX4f3SV ``` +![QR code](./qr-small.png) + would be very much appreciated. ## License diff --git a/pool.conf b/pool.conf index 2212ec9..8b24690 100644 --- a/pool.conf +++ b/pool.conf @@ -11,3 +11,4 @@ pool-fee = 0.01 payment-threshold = 0.33 log-level = 5 webui-port=4243 +disable-self-select=0 diff --git a/qr-small.png b/qr-small.png new file mode 100644 index 0000000000000000000000000000000000000000..53a299d2cb07ee0113f072b23ed45a12a5270dff GIT binary patch literal 552 zcmV+@0@wYCP)4 zmNBmEAPhwvQP5>JfW$WF*b>=5GP8kXOLS}li47zj1&aUW=SlPQn|O++Cw)W_ox;Mt zzIKTJJ@lWDooM31Y**KatdJbsrb;8_iOs@d5>4>-NirqqgA6N(xn-uiOQMQ+Xw%*+zmWEF`_hqTRiERbI82xWZNKT5+`J)jtwKW{B`< zp8K0wNRCvFl&9Lr8}d@p?Me33_UVwam}vN#NYjw)`qeG>WOp<=Gy86z@ybF#j*3n$ zaL^VZy_9e@T|Ql2)ow{lBA+gkhE>}lCeUK3<uA>k!9EN#7SaxP~61uc;*>vqm&~y1s5cA#K1OBs0F_+!5>x*A+x@uK6P& qfA@ubbr+QNy+Lls&;2L-Ciw$xd)OwLIVEiX0000ix9< literal 0 HcmV?d00001 diff --git a/src/pool.c b/src/pool.c index 63cc2bd..4ba2dca 100644 --- a/src/pool.c +++ b/src/pool.c @@ -87,6 +87,7 @@ developers. #define ADDRESS_MAX 128 #define BLOCK_TIME 120 #define HR_BLOCK_COUNT 5 +#define TEMLATE_HEIGHT_VARIANCE 5 #define uint128_t unsigned __int128 @@ -117,6 +118,7 @@ developers. */ enum block_status { BLOCK_LOCKED=0, BLOCK_UNLOCKED=1, BLOCK_ORPHANED=2 }; +enum stratum_mode { MODE_NORMAL=0, MODE_SELF_SELECT=1 }; typedef struct config_t { @@ -135,6 +137,7 @@ typedef struct config_t uint32_t webui_port; char log_file[MAX_PATH]; bool block_notified; + bool disable_self_select; } config_t; typedef struct block_template_t @@ -156,6 +159,7 @@ typedef struct job_t uint64_t target; uint128_t *submissions; size_t submissions_count; + block_template_t *miner_template; } job_t; typedef struct client_t @@ -171,6 +175,7 @@ typedef struct client_t uint64_t hashes; time_t connected_since; bool is_proxy; + uint32_t mode; } client_t; typedef struct pool_clients_t @@ -235,6 +240,8 @@ static BIGNUM *base_diff; static pool_stats_t pool_stats; static pthread_mutex_t mutex_clients = PTHREAD_MUTEX_INITIALIZER; static FILE *fd_log; +static char sec_view[32]; +static char pub_spend[32]; #define JSON_GET_OR_ERROR(name, parent, type, client) \ json_object *name = NULL; \ @@ -783,6 +790,17 @@ template_recycle(void *item) } } +static void +retarget(client_t *client, job_t *job) +{ + double duration = difftime(time(NULL), client->connected_since); + uint8_t retarget_time = client->is_proxy ? 5 : 120; + uint64_t target = fmax((double)client->hashes / + duration * retarget_time, config.pool_start_diff); + job->target = target; + log_debug("Client %.32s target now %"PRIu64, client->client_id, target); +} + static void target_to_hex(uint64_t target, char *target_hex) { @@ -790,7 +808,6 @@ target_to_hex(uint64_t target, char *target_hex) { log_debug("High target requested: %"PRIu64, target); bin_to_hex((const char*)&target, 8, &target_hex[0], 16); - target_hex[16] = '\0'; return; } BIGNUM *diff = BN_new(); @@ -806,7 +823,6 @@ target_to_hex(uint64_t target, char *target_hex) BN_rshift(diff, diff, 224); uint32_t w = BN_get_word(diff); bin_to_hex((const char*)&w, 4, &target_hex[0], 8); - target_hex[8] = '\0'; BN_free(bnt); BN_free(diff); } @@ -818,10 +834,10 @@ stratum_get_proxy_job_body(char *body, const client_t *client, int json_id = client->json_id; const char *client_id = client->client_id; const job_t *job = &client->active_jobs[0]; - char job_id[33]; + char job_id[33] = {0}; bin_to_hex((const char*)job->id, sizeof(uuid_t), job_id, 32); uint64_t target = job->target; - char target_hex[17]; + char target_hex[17] = {0}; target_to_hex(target, &target_hex[0]); const block_template_t *bt = job->block_template; @@ -858,18 +874,58 @@ stratum_get_proxy_job_body(char *body, const client_t *client, } } +static void +stratum_get_job_body_ss(char *body, const client_t *client, bool response) +{ + /* job_id, target, pool_wallet, extra_nonce */ + int json_id = client->json_id; + const char *client_id = client->client_id; + const job_t *job = &client->active_jobs[0]; + char job_id[33] = {0}; + bin_to_hex((const char*)job->id, sizeof(uuid_t), job_id, 32); + uint64_t target = job->target; + char target_hex[17] = {0}; + target_to_hex(target, &target_hex[0]); + char extra_bin[8]; + memcpy(extra_bin, &job->extra_nonce, 4); + memcpy(extra_bin+4, &instance_id, 4); + char extra_hex[17] = {0}; + bin_to_hex(extra_bin, 8, extra_hex, 16); + + if (response) + { + snprintf(body, JOB_BODY_MAX, "{\"id\":%d,\"jsonrpc\":\"2.0\"," + "\"error\":null,\"result\"" + ":{\"id\":\"%.32s\",\"job\":{" + "\"job_id\":\"%.32s\",\"target\":\"%s\"," + "\"extra_nonce\":\"%s\", \"pool_wallet\":\"%s\"}," + "\"status\":\"OK\"}}\n", + json_id, client_id, job_id, target_hex, extra_hex, + config.pool_wallet); + } + else + { + snprintf(body, JOB_BODY_MAX, "{\"jsonrpc\":\"2.0\",\"method\":" + "\"job\",\"params\"" + ":{\"id\":\"%.32s\",\"job_id\":\"%.32s\"," + "\"target\":\"%s\"," + "\"extra_nonce\":\"%s\", \"pool_wallet\":\"%s\"}}\n", + client_id, job_id, target_hex, extra_hex, config.pool_wallet); + } +} + static void stratum_get_job_body(char *body, const client_t *client, bool response) { int json_id = client->json_id; const char *client_id = client->client_id; const job_t *job = &client->active_jobs[0]; - char job_id[33]; + char job_id[33] = {0}; bin_to_hex((const char*)job->id, sizeof(uuid_t), job_id, 32); const char *blob = job->blob; uint64_t target = job->target; uint64_t height = job->block_template->height; - char target_hex[17]; + char target_hex[17] = {0}; target_to_hex(target, &target_hex[0]); if (response) @@ -925,17 +981,28 @@ client_clear_jobs(client_t *client) for (size_t i=0; iactive_jobs[i]; - if (job->blob != NULL) + if (job->blob) { free(job->blob); job->blob = NULL; } - if (job->submissions != NULL) + if (job->submissions) { free(job->submissions); job->submissions = NULL; job->submissions_count = 0; } + if (job->miner_template) + { + block_template_t *bt = job->miner_template; + if (bt->blocktemplate_blob) + { + free(bt->blocktemplate_blob); + bt->blocktemplate_blob = NULL; + } + free(job->miner_template); + job->miner_template = NULL; + } } } @@ -943,7 +1010,7 @@ static job_t * client_find_job(client_t *client, const char *job_id) { uuid_t jid; - hex_to_bin(job_id, (char*)&jid, sizeof(uuid_t)); + hex_to_bin(job_id, strlen(job_id), (char*)&jid, sizeof(uuid_t)); for (size_t i=0; iactive_jobs[i]; @@ -958,17 +1025,28 @@ client_send_job(client_t *client, bool response) { /* First cycle jobs */ job_t *last = &client->active_jobs[CLIENT_JOBS_MAX-1]; - if (last->blob != NULL) + if (last->blob) { free(last->blob); last->blob = NULL; } - if (last->submissions != NULL) + if (last->submissions) { free(last->submissions); last->submissions = NULL; last->submissions_count = 0; } + if (last->miner_template) + { + block_template_t *bt = last->miner_template; + if (bt->blocktemplate_blob) + { + free(bt->blocktemplate_blob); + bt->blocktemplate_blob = NULL; + } + free(last->miner_template); + last->miner_template = NULL; + } for (size_t i=CLIENT_JOBS_MAX-1; i>0; i--) { job_t *current = &client->active_jobs[i]; @@ -978,6 +1056,20 @@ client_send_job(client_t *client, bool response) job_t *job = &client->active_jobs[0]; memset(job, 0, sizeof(job_t)); + if (client->mode == MODE_SELF_SELECT) + { + uuid_generate(job->id); + retarget(client, job); + ++extra_nonce; + job->extra_nonce = extra_nonce; + char body[JOB_BODY_MAX]; + stratum_get_job_body_ss(body, client, response); + log_trace("Client job: %s", body); + struct evbuffer *output = bufferevent_get_output(client->bev); + evbuffer_add(output, body, strlen(body)); + return; + } + /* Quick check we actually have a block template */ block_template_t *bt = bstack_peek(bst); if (!bt) @@ -996,7 +1088,7 @@ client_send_job(client_t *client, bool response) /* Convert template to blob */ size_t bin_size = strlen(bt->blocktemplate_blob) >> 1; char *block = calloc(bin_size, sizeof(char)); - hex_to_bin(bt->blocktemplate_blob, block, bin_size); + hex_to_bin(bt->blocktemplate_blob, bin_size << 1, block, bin_size); /* Set the extra nonce in our reserved space */ char *p = block; @@ -1027,16 +1119,11 @@ client_send_job(client_t *client, bool response) job->block_template = bt; /* Send */ - char job_id[33]; + char job_id[33] = {0}; bin_to_hex((const char*)job->id, sizeof(uuid_t), job_id, 32); /* Retarget */ - double duration = difftime(time(NULL), client->connected_since); - uint8_t retarget_time = client->is_proxy ? 5 : 120; - uint64_t target = fmax((double)client->hashes / - duration * retarget_time, config.pool_start_diff); - job->target = target; - log_debug("Client %.32s target now %"PRIu64, client->client_id, target); + retarget(client, job); char body[JOB_BODY_MAX]; if (!client->is_proxy) @@ -1259,7 +1346,7 @@ rpc_on_block_header_by_height(const char* data, rpc_callback_t *callback) const char *ss = json_object_get_string(status); json_object *error = NULL; json_object_object_get_ex(root, "error", &error); - if (error != NULL) + if (error) { JSON_GET_OR_WARN(code, error, json_type_object); JSON_GET_OR_WARN(message, error, json_type_string); @@ -1269,7 +1356,7 @@ rpc_on_block_header_by_height(const char* data, rpc_callback_t *callback) json_object_put(root); return; } - if (status == NULL || strcmp(ss, "OK") != 0) + if (!status || strcmp(ss, "OK") != 0) { log_error("Error getting block header by height: %s", ss); json_object_put(root); @@ -1291,7 +1378,7 @@ rpc_on_block_headers_range(const char* data, rpc_callback_t *callback) const char *ss = json_object_get_string(status); json_object *error = NULL; json_object_object_get_ex(root, "error", &error); - if (error != NULL) + if (error) { JSON_GET_OR_WARN(code, error, json_type_object); JSON_GET_OR_WARN(message, error, json_type_string); @@ -1301,7 +1388,7 @@ rpc_on_block_headers_range(const char* data, rpc_callback_t *callback) json_object_put(root); return; } - if (status == NULL || strcmp(ss, "OK") != 0) + if (!status || strcmp(ss, "OK") != 0) { log_warn("Error getting block headers by range: %s", ss); json_object_put(root); @@ -1331,7 +1418,7 @@ rpc_on_block_template(const char* data, rpc_callback_t *callback) const char *ss = json_object_get_string(status); json_object *error = NULL; json_object_object_get_ex(root, "error", &error); - if (error != NULL) + if (error) { JSON_GET_OR_WARN(code, error, json_type_object); JSON_GET_OR_WARN(message, error, json_type_string); @@ -1341,7 +1428,7 @@ rpc_on_block_template(const char* data, rpc_callback_t *callback) json_object_put(root); return; } - if (status == NULL || strcmp(ss, "OK") != 0) + if (!status || strcmp(ss, "OK") != 0) { log_error("Error getting block template: %s", ss); json_object_put(root); @@ -1419,6 +1506,32 @@ startup_pauout(uint64_t height) return 0; } +static void +rpc_on_view_key(const char* data, rpc_callback_t *callback) +{ + json_object *root = json_tokener_parse(data); + JSON_GET_OR_WARN(result, root, json_type_object); + JSON_GET_OR_WARN(key, result, json_type_string); + json_object *error = NULL; + json_object_object_get_ex(root, "error", &error); + if (error) + { + JSON_GET_OR_WARN(code, error, json_type_object); + JSON_GET_OR_WARN(message, error, json_type_string); + int ec = json_object_get_int(code); + const char *em = json_object_get_string(message); + log_error("Error (%d) getting key: %s", ec, em); + json_object_put(root); + return; + } + const char *vk = json_object_get_string(key); + hex_to_bin(vk, strlen(vk), &sec_view[0], 32); + json_object_put(root); + + uint64_t prefix; + parse_address(config.pool_wallet, &prefix, &pub_spend[0]); +} + static void rpc_on_last_block_header(const char* data, rpc_callback_t *callback) { @@ -1429,7 +1542,7 @@ rpc_on_last_block_header(const char* data, rpc_callback_t *callback) const char *ss = json_object_get_string(status); json_object *error = NULL; json_object_object_get_ex(root, "error", &error); - if (error != NULL) + if (error) { JSON_GET_OR_WARN(code, error, json_type_object); JSON_GET_OR_WARN(message, error, json_type_string); @@ -1439,7 +1552,7 @@ rpc_on_last_block_header(const char* data, rpc_callback_t *callback) json_object_put(root); return; } - if (status == NULL || strcmp(ss, "OK") != 0) + if (!status || strcmp(ss, "OK") != 0) { log_error("Error getting last block header: %s", ss); json_object_put(root); @@ -1451,13 +1564,13 @@ rpc_on_last_block_header(const char* data, rpc_callback_t *callback) uint64_t bh = json_object_get_int64(height); bool need_new_template = false; block_t *front = bstack_peek(bsh); - if (front != NULL && bh > front->height) + if (front && bh > front->height) { need_new_template = true; block_t *block = bstack_push(bsh, NULL); response_to_block(block_header, block); } - else if (front == NULL) + else if (!front) { block_t *block = bstack_push(bsh, NULL); response_to_block(block_header, block); @@ -1507,7 +1620,7 @@ rpc_on_block_submitted(const char* data, rpc_callback_t *callback) an alternative block. Thus, still store it. This doesn't matter as upon payout, blocks are checked whether they are orphaned or not. */ - if (error != NULL) + if (error) { JSON_GET_OR_WARN(code, error, json_type_object); JSON_GET_OR_WARN(message, error, json_type_string); @@ -1515,7 +1628,7 @@ rpc_on_block_submitted(const char* data, rpc_callback_t *callback) const char *em = json_object_get_string(message); log_warn("Error (%d) with block submission: %s", ec, em); } - if (status == NULL || strcmp(ss, "OK") != 0) + if (!status || strcmp(ss, "OK") != 0) { log_warn("Error submitting block: %s", ss); } @@ -1537,7 +1650,7 @@ rpc_on_wallet_transferred(const char* data, rpc_callback_t *callback) JSON_GET_OR_WARN(result, root, json_type_object); json_object *error = NULL; json_object_object_get_ex(root, "error", &error); - if (error != NULL) + if (error) { JSON_GET_OR_WARN(code, error, json_type_object); JSON_GET_OR_WARN(message, error, json_type_string); @@ -1741,6 +1854,15 @@ send_payments(void) return 0; } +static void +fetch_view_key(void) +{ + char body[RPC_BODY_MAX]; + rpc_get_request_body(body, "query_key", "ss", "key_type", "view_key"); + rpc_callback_t *cb = rpc_callback_new(rpc_on_view_key, NULL); + rpc_wallet_request(base, body, cb); +} + static void fetch_last_block_header(void) { @@ -1819,9 +1941,9 @@ client_find(struct bufferevent *bev, client_t **client) static void client_clear(struct bufferevent *bev) { - client_t *client; + client_t *client = NULL; client_find(bev, &client); - if (client == NULL) + if (!client) return; client_clear_jobs(client); memset(client, 0, sizeof(client_t)); @@ -1835,10 +1957,31 @@ client_on_login(json_object *message, client_t *client) JSON_GET_OR_ERROR(params, message, json_type_object, client); JSON_GET_OR_ERROR(login, params, json_type_string, client); JSON_GET_OR_ERROR(pass, params, json_type_string, client); + client->mode = MODE_NORMAL; + json_object *mode = NULL; + if (json_object_object_get_ex(params, "mode", &mode)) + { + if (!json_object_is_type(mode, json_type_string)) + log_warn("mode not a json_type_string"); + else + { + const char *modestr = json_object_get_string(mode); + if (strcmp(modestr, "self-select") == 0) + { + if (config.disable_self_select) + { + return send_validation_error(client, + "pool disabled self-select"); + } + client->mode = MODE_SELF_SELECT; + log_trace("Client login for mode: self-select"); + } + } + } const char *address = json_object_get_string(login); uint64_t prefix; - parse_address(address, &prefix); + parse_address(address, &prefix, NULL); if (prefix != MAINNET_ADDRESS_PREFIX && prefix != TESTNET_ADDRESS_PREFIX) return send_validation_error(client, "login only main wallet addresses are supported"); @@ -1857,16 +2000,74 @@ client_on_login(json_object *message, client_t *client) } } + if (client->is_proxy && client->mode == MODE_SELF_SELECT) + { + return send_validation_error(client, + "login mode self-select and proxy not yet supported"); + } + strncpy(client->address, address, sizeof(client->address)); strncpy(client->worker_id, worker_id, sizeof(client->worker_id)); uuid_t cid; uuid_generate(cid); bin_to_hex((const char*)cid, sizeof(uuid_t), client->client_id, 32); - char status[256]; - snprintf(status, 256, "Logged in: %s %s\n", worker_id, address); client_send_job(client, true); } +static void +client_on_block_template(json_object *message, client_t *client) +{ + struct evbuffer *output = bufferevent_get_output(client->bev); + + JSON_GET_OR_ERROR(params, message, json_type_object, client); + JSON_GET_OR_ERROR(id, params, json_type_string, client); + JSON_GET_OR_ERROR(job_id, params, json_type_string, client); + JSON_GET_OR_ERROR(blob, params, json_type_string, client); + JSON_GET_OR_ERROR(difficulty, params, json_type_int, client); + JSON_GET_OR_ERROR(height, params, json_type_int, client); + JSON_GET_OR_ERROR(prev_hash, params, json_type_string, client); + + const char *jid = json_object_get_string(job_id); + if (strlen(jid) != 32) + return send_validation_error(client, "job_id invalid length"); + + int64_t h = json_object_get_int64(height); + int64_t dh = llabs(h - (int64_t)pool_stats.network_height); + if (dh > TEMLATE_HEIGHT_VARIANCE) + { + char m[64]; + snprintf(m, 64, "Bad height. Differs to pool by %"PRIu64" blocks.", dh); + return send_validation_error(client, m); + } + + const char *btb = json_object_get_string(blob); + int rc = validate_block_from_blob(btb, &sec_view[0], &pub_spend[0]); + if (rc != 0) + { + log_warn("Bad template submitted: %d", rc); + return send_validation_error(client, "block template blob invalid"); + } + + job_t *job = client_find_job(client, jid); + if (!job) + return send_validation_error(client, "cannot find job with job_id"); + + if (job->miner_template) + return send_validation_error(client, "job already has block template"); + + job->miner_template = calloc(1, sizeof(block_template_t)); + job->miner_template->blocktemplate_blob = strdup(btb); + job->miner_template->difficulty = json_object_get_int64(difficulty); + job->miner_template->height = json_object_get_int64(height); + memcpy(job->miner_template->prev_hash, + json_object_get_string(prev_hash), 64); + + log_trace("Client set template: %s", btb); + char body[STATUS_BODY_MAX]; + stratum_get_status_body(body, client->json_id, "OK"); + evbuffer_add(output, body, strlen(body)); +} + static void client_on_submit(json_object *message, client_t *client) { @@ -1920,36 +2121,46 @@ client_on_submit(json_object *message, client_t *client) */ /* Convert template to blob */ - block_template_t *bt = job->block_template; + if (client->mode == MODE_SELF_SELECT && !job->miner_template) + return send_validation_error(client, "mode self-selct and no template"); + block_template_t *bt; + if (job->miner_template) + bt = job->miner_template; + else + bt = job->block_template; char *btb = bt->blocktemplate_blob; size_t bin_size = strlen(btb) >> 1; char *block = calloc(bin_size, sizeof(char)); - hex_to_bin(bt->blocktemplate_blob, block, bin_size); + hex_to_bin(bt->blocktemplate_blob, bin_size << 1, block, bin_size); - /* Set the extra nonce and instance_id in our reserved space */ char *p = block; - p += bt->reserved_offset; - memcpy(p, &job->extra_nonce, sizeof(extra_nonce)); - p += 4; - memcpy(p, &instance_id, sizeof(instance_id)); - uint32_t pool_nonce = 0; uint32_t worker_nonce = 0; - if (client->is_proxy) + + if (client->mode != MODE_SELF_SELECT) { - /* - A proxy supplies pool_nonce and worker_nonce - so add them in the resrved space too. - */ - JSON_GET_OR_WARN(poolNonce, params, json_type_int); - JSON_GET_OR_WARN(workerNonce, params, json_type_int); - pool_nonce = json_object_get_int(poolNonce); - worker_nonce = json_object_get_int(workerNonce); + /* Set the extra nonce and instance_id in our reserved space */ + p += bt->reserved_offset; + memcpy(p, &job->extra_nonce, sizeof(extra_nonce)); p += 4; - memcpy(p, &pool_nonce, sizeof(pool_nonce)); - p += 4; - memcpy(p, &worker_nonce, sizeof(worker_nonce)); + memcpy(p, &instance_id, sizeof(instance_id)); + if (client->is_proxy) + { + /* + A proxy supplies pool_nonce and worker_nonce + so add them in the resrved space too. + */ + JSON_GET_OR_WARN(poolNonce, params, json_type_int); + JSON_GET_OR_WARN(workerNonce, params, json_type_int); + pool_nonce = json_object_get_int(poolNonce); + worker_nonce = json_object_get_int(workerNonce); + p += 4; + memcpy(p, &pool_nonce, sizeof(pool_nonce)); + p += 4; + memcpy(p, &worker_nonce, sizeof(worker_nonce)); + } } + uint128_t sub = 0; uint32_t *psub = (uint32_t*) ⊂ *psub++ = result_nonce; @@ -2005,7 +2216,7 @@ client_on_submit(json_object *message, client_t *client) const int cn_variant = major_version >= 7 ? major_version - 6 : 0; get_hash(hashing_blob, hashing_blob_size, (char**)&result_hash, cn_variant, bt->height); - hex_to_bin(result_hex, submitted_hash, 32); + hex_to_bin(result_hex, 64, submitted_hash, 32); if (memcmp(submitted_hash, result_hash, 32) != 0) { @@ -2102,17 +2313,18 @@ client_on_read(struct bufferevent *bev, void *ctx) struct evbuffer *input, *output; char *line; size_t n; - client_t *client; + client_t *client = NULL; client_find(bev, &client); - if (client == NULL) + if (!client) return; input = bufferevent_get_input(bev); output = bufferevent_get_output(bev); size_t len = evbuffer_get_length(input); - if (len >= MAX_LINE) + if (len > MAX_LINE || + (client->mode == MODE_SELF_SELECT && len > MAX_LINE<<2)) { const char *too_long = "Message too long\n"; evbuffer_add(output, too_long, strlen(too_long)); @@ -2132,7 +2344,7 @@ client_on_read(struct bufferevent *bev, void *ctx) bool unknown = false; - if (method_name == NULL) + if (!method || !method_name) { unknown = true; } @@ -2140,6 +2352,10 @@ client_on_read(struct bufferevent *bev, void *ctx) { client_on_login(message, client); } + else if (strcmp(method_name, "block_template") == 0) + { + client_on_block_template(message, client); + } else if (strcmp(method_name, "submit") == 0) { client_on_submit(message, client); @@ -2238,8 +2454,9 @@ read_config(const char *config_file, const char *log_file, bool block_notified) config.log_level = 5; config.webui_port = 4243; config.block_notified = block_notified; + config.disable_self_select = false; - char path[MAX_PATH]; + char path[MAX_PATH] = {0}; if (config_file) { strncpy(path, config_file, MAX_PATH); @@ -2267,7 +2484,7 @@ read_config(const char *config_file, const char *log_file, bool block_notified) log_info("Reading config at: %s", path); FILE *fp = fopen(path, "r"); - if (fp == NULL) + if (!fp) { log_fatal("Cannot open config file. Aborting."); abort(); @@ -2276,13 +2493,13 @@ read_config(const char *config_file, const char *log_file, bool block_notified) char *key; char *val; const char *tok = " ="; - while (fgets(line, sizeof(line), fp) != NULL) + while (fgets(line, sizeof(line), fp)) { key = strtok(line, tok); - if (key == NULL) + if (!key) continue; val = strtok(NULL, tok); - if (val == NULL) + if (!val) continue; val[strcspn(val, "\r\n")] = 0; if (strcmp(key, "rpc-host") == 0) @@ -2345,10 +2562,14 @@ read_config(const char *config_file, const char *log_file, bool block_notified) { config.block_notified = atoi(val); } + else if (strcmp(key, "disable-self-select") == 0) + { + config.disable_self_select = atoi(val); + } } fclose(fp); - if (log_file != NULL) + if (log_file) strncpy(config.log_file, log_file, sizeof(config.log_file)); if (!config.pool_wallet[0]) @@ -2368,13 +2589,14 @@ read_config(const char *config_file, const char *log_file, bool block_notified) "pool_fee = %.3f\n payment_threshold = %.2f\n " "wallet_rpc_host = %s\n wallet_rpc_port = %u\n pool_port = %u\n " "log_level = %u\n webui_port=%u\n " - "log-file = %s\n block-notified = %u\n", + "log-file = %s\n block-notified = %u\n " + "disable-self-select = %u\n", config.rpc_host, config.rpc_port, config.rpc_timeout, config.pool_wallet, config.pool_start_diff, config.share_mul, config.pool_fee, config.payment_threshold, config.wallet_rpc_host, config.wallet_rpc_port, config.pool_port, config.log_level, config.webui_port, - config.log_file, config.block_notified); + config.log_file, config.block_notified, config.disable_self_select); } static void @@ -2448,6 +2670,8 @@ run(void) else fetch_last_block_header(); + fetch_view_key(); + timer_10m = evtimer_new(base, timer_on_10m, NULL); timer_on_10m(-1, EV_TIMEOUT, NULL); @@ -2476,7 +2700,7 @@ cleanup(void) BN_CTX_free(bn_ctx); pthread_mutex_destroy(&mutex_clients); log_info("Pool shutdown successfully"); - if (fd_log != NULL) + if (fd_log) fclose(fd_log); } @@ -2525,19 +2749,19 @@ int main(int argc, char **argv) read_config(config_file, log_file, block_notified); log_set_level(LOG_FATAL - config.log_level); - if (config.log_file[0] != '\0') + if (config.log_file[0]) { fd_log = fopen(config.log_file, "a"); - if (fd_log == NULL) + if (!fd_log) log_info("Failed to open log file: %s", config.log_file); else log_set_fp(fd_log); } - if (config_file != NULL) + if (config_file) free(config_file); - if (log_file != NULL) + if (log_file) free(log_file); int err = 0; @@ -2567,6 +2791,7 @@ int main(int argc, char **argv) uic.pool_stats = &pool_stats; uic.pool_fee = config.pool_fee; uic.pool_port = config.pool_port; + uic.allow_self_select = !config.disable_self_select; uic.payment_threshold = config.payment_threshold; start_web_ui(&uic); diff --git a/src/util.c b/src/util.c index 74d4733..bfb6a25 100644 --- a/src/util.c +++ b/src/util.c @@ -57,11 +57,11 @@ is_hex_string(const char *str) } void -hex_to_bin(const char *hex, char *bin, size_t bin_size) +hex_to_bin(const char *hex, const size_t hex_len, + char *bin, const size_t bin_size) { - size_t len = strlen(hex); - assert(len % 2 == 0); - assert(bin_size >= len / 2); + assert(hex_len % 2 == 0); + assert(bin_size >= hex_len >> 1); const char *ph = hex; char *end = bin + bin_size; while (*ph && bin < end) @@ -72,7 +72,8 @@ hex_to_bin(const char *hex, char *bin, size_t bin_size) } void -bin_to_hex(const char *bin, size_t bin_size, char *hex, size_t hex_size) +bin_to_hex(const char *bin, const size_t bin_size, + char *hex, const size_t hex_size) { assert(bin_size << 1 == hex_size); const char *hex_chars = "0123456789abcdef"; @@ -86,7 +87,7 @@ bin_to_hex(const char *bin, size_t bin_size, char *hex, size_t hex_size) } void -reverse_bin(char *bin, size_t len) +reverse_bin(char *bin, const size_t len) { size_t start = 0; size_t end = len-1; diff --git a/src/util.h b/src/util.h index b731efb..fcb379e 100644 --- a/src/util.h +++ b/src/util.h @@ -36,9 +36,11 @@ developers. #define UTIL_H int is_hex_string(const char *str); -void hex_to_bin(const char *hex, char *bin, size_t bin_size); -void bin_to_hex(const char *bin, size_t bin_size, char *hex, size_t hex_size); -void reverse_bin(char *bin, size_t len); +void hex_to_bin(const char *hex, const size_t hex_len, + char *bin, const size_t bin_size); +void bin_to_hex(const char *bin, size_t bin_size, char *hex, + const size_t hex_size); +void reverse_bin(char *bin, const size_t len); char *stecpy(char *dst, const char *src, const char *end); #endif diff --git a/src/webui-embed.html b/src/webui-embed.html index b49c456..b46a77b 100644 --- a/src/webui-embed.html +++ b/src/webui-embed.html @@ -39,6 +39,7 @@ Payment threshold: Pool fee: Pool port: + Allow self-select: Miners connected: Your HR: Balance due: @@ -113,6 +114,8 @@ el.innerHTML = format_hashrate(stats[e]); else if (e == "pool_fee") el.innerHTML = (stats[e]*100) + "%"; + else if (e == "allow_self_select") + el.innerHTML = stats[e] == 1 ? "Yes" : "No"; else el.innerHTML = stats[e]; } diff --git a/src/webui.c b/src/webui.c index 64fc8f5..f99817a 100644 --- a/src/webui.c +++ b/src/webui.c @@ -55,7 +55,7 @@ developers. #include "webui.h" #define TAG_MAX 17 -#define PAGE_MAX 4096 +#define PAGE_MAX 8192 #define JSON_MAX 512 extern unsigned char webui_html[]; @@ -76,6 +76,7 @@ send_json_stats (void *cls, struct MHD_Connection *connection) uint64_t ltf = context->pool_stats->last_template_fetched; uint64_t lbf = context->pool_stats->last_block_found; uint32_t pbf = context->pool_stats->pool_blocks_found; + unsigned ss = context->allow_self_select; uint64_t mh = 0; double mb = 0.0; const char *wa = MHD_lookup_connection_value(connection, @@ -96,12 +97,13 @@ send_json_stats (void *cls, struct MHD_Connection *connection) "\"payment_threshold\":%.2f," "\"pool_fee\":%.3f," "\"pool_port\":%d," + "\"allow_self_select\":%u," "\"connected_miners\":%d," "\"miner_hashrate\":%"PRIu64"," "\"miner_balance\":%.8f" "}", ph, nh, height, ltf, lbf, pbf, context->payment_threshold, context->pool_fee, - context->pool_port, context->pool_stats->connected_miners, + context->pool_port, ss, context->pool_stats->connected_miners, mh, mb); response = MHD_create_response_from_buffer(strlen(json), (void*) json, MHD_RESPMEM_MUST_COPY); diff --git a/src/webui.h b/src/webui.h index c1eb5c1..0d45b84 100644 --- a/src/webui.h +++ b/src/webui.h @@ -54,6 +54,7 @@ typedef struct wui_context_t double pool_fee; double payment_threshold; uint32_t pool_port; + unsigned allow_self_select; } wui_context_t; int start_web_ui(wui_context_t *context); diff --git a/src/xmr.cpp b/src/xmr.cpp index b3c1c10..498e28c 100644 --- a/src/xmr.cpp +++ b/src/xmr.cpp @@ -45,12 +45,16 @@ developers. #include "cryptonote_basic/difficulty.h" #include "crypto/crypto.h" #include "crypto/hash.h" -#include "common/base58.h" #include "serialization/binary_utils.h" +#include "ringct/rctSigs.h" +#include "common/base58.h" +#include "string_tools.h" #include "xmr.h" +using namespace epee::string_tools; using namespace cryptonote; +using namespace crypto; int get_hashing_blob(const char *input, const size_t in_size, char **output, size_t *out_size) @@ -59,18 +63,17 @@ int get_hashing_blob(const char *input, const size_t in_size, blobdata bd = std::string(input, in_size); if (!parse_and_validate_block_from_blob(bd, b)) { - printf("Failed to parse block\n"); - return -1; + return XMR_PARSE_ERROR; } blobdata blob = get_block_hashing_blob(b); *out_size = blob.length(); *output = (char*) malloc(*out_size); memcpy(*output, blob.data(), *out_size); - return 0; + return XMR_NO_ERROR; } -int parse_address(const char *input, uint64_t *prefix) +int parse_address(const char *input, uint64_t *prefix, char *pub_spend) { uint64_t tag; std::string decoded; @@ -78,14 +81,80 @@ int parse_address(const char *input, uint64_t *prefix) if (rv) { *prefix = tag; + if (pub_spend != NULL) + { + account_public_address address; + ::serialization::parse_binary(decoded, address); + public_key S = address.m_spend_public_key; + memcpy(pub_spend, &S, 32); + } } - return rv ? 0 : -1; + return rv ? XMR_NO_ERROR : XMR_PARSE_ERROR; } void get_hash(const char *input, const size_t in_size, char **output, int variant, uint64_t height) { - crypto::cn_slow_hash(input, in_size, - reinterpret_cast(*output), variant, height); + cn_slow_hash(input, in_size, + reinterpret_cast(*output), variant, height); +} + +int validate_block_from_blob(const char *blob_hex, const char *sec_view, + const char *pub_spend) +{ + /* + The only validation needed is that the data parses to a block and the + miner tx only pays out to the pool. + */ + block b = AUTO_VAL_INIT(b); + blobdata bd; + secret_key v; + public_key S; + memcpy(&unwrap(v), sec_view, 32); + memcpy(&S, pub_spend, 32); + + if (!parse_hexstr_to_binbuff(blob_hex, bd)) + return XMR_PARSE_ERROR; + + if (!parse_and_validate_block_from_blob(bd, b)) + return XMR_PARSE_ERROR; + + transaction tx = b.miner_tx; + + /* + Ensure we have only one in, one out and in is gen. + */ + if (tx.vin.size() != 1) + return XMR_VIN_COUNT_ERROR; + + if (tx.vout.size() != 1) + return XMR_VOUT_COUNT_ERROR; + + if (tx.vin[0].type() != typeid(txin_gen)) + return XMR_VIN_TYPE_ERROR; + + /* + Ensure that the miner tx single output key is destined for the pool + wallet. + + Don't bother checking any additional pub keys in tx extra. The daemon + created miner tx only has one public key in extra. If we can't derive + from the first (which should be only) found, reject. + */ + std::vector tx_extra_fields; + parse_tx_extra(tx.extra, tx_extra_fields); + tx_extra_pub_key pub_key_field; + if (!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, 0)) + return XMR_TX_EXTRA_ERROR; + public_key R = pub_key_field.pub_key; + public_key P = boost::get(tx.vout[0].target).key; + key_derivation derivation; + generate_key_derivation(R, v, derivation); + public_key derived; + derive_subaddress_public_key(P, derivation, 0, derived); + if (derived != S) + return XMR_MISMATCH_ERROR; + + return XMR_NO_ERROR; } diff --git a/src/xmr.h b/src/xmr.h index 82f1de7..3f03f32 100644 --- a/src/xmr.h +++ b/src/xmr.h @@ -39,11 +39,24 @@ developers. extern "C" { #endif +enum xmr_error +{ + XMR_NO_ERROR = 0, + XMR_PARSE_ERROR = -1, + XMR_VIN_COUNT_ERROR = -2, + XMR_VOUT_COUNT_ERROR = -3, + XMR_VIN_TYPE_ERROR = -4, + XMR_TX_EXTRA_ERROR = -5, + XMR_MISMATCH_ERROR = -6 +}; + int get_hashing_blob(const char *input, const size_t in_size, char **output, size_t *out_size); -int parse_address(const char *input, uint64_t *prefix); +int parse_address(const char *input, uint64_t *prefix, char *pub_spend); void get_hash(const char *input, const size_t in_size, char **output, int variant, uint64_t height); +int validate_block_from_blob(const char *blob_hex, const char *sec_view, + const char *pub_spend); #ifdef __cplusplus } diff --git a/stratum-ss.md b/stratum-ss.md new file mode 100644 index 0000000..cdafb2b --- /dev/null +++ b/stratum-ss.md @@ -0,0 +1,174 @@ +# Stratum mode self-select + +One major concern of pool mining has been that of pool centralization. A +malicious pool operator, controlling a pool with a significant portion of the +total network hash-rate (e.g. 51% or more), has the ability to perform various +attacks. This is made possible due to the fact miners have no visibility into +what block template they are actually mining on. This leads to another concern - +censorship of transactions. Again, as miners have no visibility of the block +template they are mining, they also have no visibility of the transactions +included in the block template. This enables a malicious pool be selective as to +which transactions get included (or not) into a block. + +To address these concerns, I've implemented a new, experimental and optional +*mode* to this pool, which enables miners to select their *own* block template +to mine on. + +What follows are the instructions to test this new mode and the changes made to +the stratum messages. For a miner to test against the pool, there is a very +simple miner, [monero-powpy](https://github.com/jtgrassie/monero-powpy) +(`stratum-ss-miner.py`), and also a hastily cobbled together branch of +[XMRig](https://github.com/jtgrassie/xmrig/tree/stratum-ss). + +## Building + +First you'll need to compile the Monero master branch with pull request +[#5728](https://github.com/monero-project/monero/pull/5728). For example: + + cd "$MONERO_ROOT" + git fetch origin pull/5728/head:pr-5728 + git checkout -b stratum-ss + git rebase pr-5728 + make release + +Now proceed to building the pool as per the instructions in the +[README](./README.md#compiling-from-source). + +## Running + +Start your newly patched and compiled `monerod` and `monero-wallet-rpc`. For +example, in one shell: + + cd "$MONERO_ROOT"/build/Linux/stratum-ss/release/bin + ./monerod --testnet + +And in another shell: + + cd "$MONERO_ROOT"/build/Linux/stratum-ss/release/bin + ./monero-wallet-rpc --testnet --rpc-bind-port 28084 --disable-rpc-login \ + --password "" --wallet-file ~/testnet-pool-wallet + +Next, in a third shell, run `monero-pool`. Instructions per the +[README](./README.md#running). + +Lastly you'll need to run a miner that supports this new stratum mode (see +above). If using [monero-powpy](https://github.com/jtgrassie/monero-powpy), just +run `stratum-ss-miner.py`, optionally editing the parameters first. If using +[XMRig](https://github.com/jtgrassie/xmrig/tree/stratum-ss), edit your +`config.json` file by setting the parameter `self-select` in your `pools` array +(e.g. `"self-select": "localhost:28081"`). + +## Specification + +What follows are the stratum message and flow changes required to enable pool +miners to mine on miner created block templates. + +(1) The miner logs into the pool with an additional *mode* parameter: + + { + "method": "login", + "params": { + "login": "wallet address", + "pass": "password", + "agent": "user-agent/0.1", + "mode": "self-select" /* new field */ + }, + "jsonrpc": "2.0", + "id":1 + } + +(2) The pool responds with a result job which includes the pool wallet address +and an extra nonce: + + { + "result": { + "job": { + "pool_wallet": "pool wallet address", /* new field */ + "extra_nonce": "extra nonce hex", /* new field */ + "target": "target hex", + "job_id": "job id" + }, + "id": "client id", + "status": "OK" + }, + "jsonrpc": "2.0", + "id":1 + } + +(3) The miner can now call the daemons RPC method +[get_block_template](https://ww.getmonero.org/resources/developer-guides/daemon-rpc.html#get_block_template) +with parameters `extra_nonce: extra_nonce` (implemented in pull request +[#5728](https://github.com/monero-project/monero/pull/5728)), and +`wallet_address: pool_wallet`. + +The miner now informs the pool of the resulting block template it will use for +the job: + + { + "method":"block_template", /* new method */ + "params": { + "id": "client id", + "job_id": "job id", + "blob": "block template hex", + "height": N, + "difficulty": N, + "prev_hash": "prev hash hex" + }, + "jsonrpc": "2.0", + "id":1 + } + +(4) The pool validates and caches the supplied block template and responds with +a status: + + { + "result": { + "status": "OK", + "error", null + }, + "jsonrpc": "2.0", + "id":1 + } + +(5) The miner submits results. No changes here: + + { + "method":"submit", + "params": { + "id": "client id", + "job_id": "job id", + "nonce": "deadbeef", + "result": "hash hex" + }, + "jsonrpc": "2.0", + "id":1 + } + +(6) The pool responds to job submissions. No changes here: + + { + "result": { + "status": "OK", + "error", null + }, + "jsonrpc": "2.0", + "id":1 + } + +(7) The pool asks the miner to start a new job: + + { + "method": "job", + "params": { + "pool_wallet": "pool wallet address", /* new field */ + "extra_nonce": "extra nonce hex", /* new field */ + "target": "target hex", + "job_id": "job id" + }, + "jsonrpc": "2.0", + "id":1 + } + +The miner now repeats from step #3. + +[//]: # ( vim: set tw=80: )