implement miner selected block templates

This commit is contained in:
Jethro Grassie 2019-07-05 17:28:04 -04:00
parent 86a9900dfc
commit b5a2dd5f5b
No known key found for this signature in database
GPG Key ID: DE8ED755616565BB
13 changed files with 689 additions and 186 deletions

188
Makefile
View File

@ -1,65 +1,60 @@
define LICENSE # Copyright (c) 2019, The Monero Project
#
Copyright (c) 2014-2019, The Monero Project # All rights reserved.
#
All rights reserved. # Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
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.
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
2. Redistributions in binary form must reproduce the above copyright notice, # and/or other materials provided with the distribution.
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
3. Neither the name of the copyright holder nor the names of its contributors # software without specific prior written permission.
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
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # POSSIBILITY OF SUCH DAMAGE.
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
TARGET = monero-pool TARGET = monero-pool
TYPE = debug TYPE = debug
ifeq ($(MAKECMDGOALS),release) ifeq ($(MAKECMDGOALS),release)
TYPE = release TYPE = release
endif endif
MONERO_BUILD_ROOT = ${MONERO_ROOT}/build/$(shell echo `uname | \ MONERO_BUILD_ROOT = \
sed -e 's|[:/\\ \(\)]|_|g'`/`\ ${MONERO_ROOT}/build/$(shell echo `uname | \
git -C ${MONERO_ROOT} branch | grep '\* ' | cut -f2- -d' '| \ sed -e 's|[:/\\ \(\)]|_|g'`/` \
sed -e 's|[:/\\ \(\)]|_|g'`)/release git -C ${MONERO_ROOT} branch | \
grep '\* ' | cut -f2- -d' '| \
sed -e 's|[:/\\ \(\)]|_|g'`)/release
MONERO_INC = ${MONERO_ROOT}/src \ MONERO_INC = \
${MONERO_ROOT}/external/easylogging++ \ ${MONERO_ROOT}/src \
${MONERO_ROOT}/contrib/epee/include ${MONERO_ROOT}/external \
${MONERO_ROOT}/external/easylogging++ \
${MONERO_ROOT}/contrib/epee/include
MONERO_LIBS = \ MONERO_LIBS = \
${MONERO_BUILD_ROOT}/src/cryptonote_basic/libcryptonote_basic.a \ ${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/crypto/libcncrypto.a \
${MONERO_BUILD_ROOT}/src/common/libcommon.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/ringct/libringct_basic.a \
${MONERO_BUILD_ROOT}/src/device/libdevice.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}/contrib/epee/src/libepee.a \
${MONERO_BUILD_ROOT}/external/easylogging++/libeasylogging.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 CPPDEFS = _GNU_SOURCE AUTO_INITIALIZE_EASYLOGGINGPP LOG_USE_COLOR
CCPARAM = -Wall $(CFLAGS) -maes -fPIC CCPARAM = -Wall $(CFLAGS) -maes -fPIC
CXXFLAGS = -std=c++11 CXXFLAGS = -std=c++11 -Wno-reorder
ifeq ($(OS), Darwin) ifeq ($(OS), Darwin)
CXXFLAGS += -stdlib=libc++ CXXFLAGS += -stdlib=libc++
CPPDEFS += HAVE_MEMSET_S CPPDEFS += HAVE_MEMSET_S
endif endif
ifeq ($(OS),Darwin) ifeq ($(OS),Darwin)
LDPARAM = LDPARAM =
else else
LDPARAM = -Wl,-warn-unresolved-symbols -fPIC -pie LDPARAM = -Wl,-warn-unresolved-symbols -fPIC -pie
endif endif
ifeq ($(TYPE),debug) ifeq ($(TYPE),debug)
CCPARAM += -g CCPARAM += -g
CPPDEFS += DEBUG CPPDEFS += DEBUG
endif endif
ifeq ($(TYPE), release) ifeq ($(TYPE), release)
CCPARAM += -O3 -Wno-unused-variable CCPARAM += -O3 -Wno-unused-variable
ifneq ($(OS), Darwin) ifneq ($(OS), Darwin)
LDPARAM = -Wl,--unresolved-symbols=ignore-in-object-files LDPARAM = -Wl,--unresolved-symbols=ignore-in-object-files
endif endif
endif endif
LDPARAM += $(LDFLAGS) LDPARAM += $(LDFLAGS)
LIBS := lmdb pthread microhttpd unbound LIBS := lmdb pthread microhttpd unbound
ifeq ($(OS), Darwin) ifeq ($(OS), Darwin)
LIBS += c++ boost_system-mt boost_date_time-mt boost_chrono-mt \ LIBS += c++ \
boost_filesystem-mt boost_thread-mt boost_regex-mt 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 else
LIBS += dl boost_system boost_date_time boost_chrono boost_filesystem \ LIBS += dl uuid \
boost_thread boost_regex uuid boost_system boost_date_time boost_chrono \
boost_filesystem boost_thread boost_regex \
boost_serialization boost_program_options
endif endif
PKG_LIBS := $(shell pkg-config \ PKG_LIBS := $(shell pkg-config \
"libevent >= 2.1" \ "libevent >= 2.1" \
json-c \ json-c \
openssl \ openssl \
libsodium \ libsodium \
--libs) --libs)
STATIC_LIBS = STATIC_LIBS =
DLIBS = DLIBS =
@ -119,11 +118,11 @@ DLIBS =
INCPATH := $(DIRS) ${MONERO_INC} /opt/local/include /usr/local/include INCPATH := $(DIRS) ${MONERO_INC} /opt/local/include /usr/local/include
PKG_INC := $(shell pkg-config \ PKG_INC := $(shell pkg-config \
"libevent >= 2.1" \ "libevent >= 2.1" \
json-c \ json-c \
openssl \ openssl \
libsodium \ libsodium \
--cflags) --cflags)
LIBPATH := /opt/local/lib/ /usr/local/lib LIBPATH := /opt/local/lib/ /usr/local/lib
@ -150,36 +149,42 @@ SDFILES := $(addprefix $(STORE)/,$(CSOURCE:.S=.d))
$(TARGET): preflight dirs $(OBJECTS) $(COBJECTS) $(SOBJECTS) $(HTMLOBJECTS) $(TARGET): preflight dirs $(OBJECTS) $(COBJECTS) $(SOBJECTS) $(HTMLOBJECTS)
@echo Linking $(OBJECTS)... @echo Linking $(OBJECTS)...
$(C++) -o $(STORE)/$(TARGET) $(OBJECTS) $(COBJECTS) $(SOBJECTS) $(HTMLOBJECTS) \ $(C++) -o $(STORE)/$(TARGET) \
$(LDPARAM) $(MONERO_LIBS) \ $(OBJECTS) $(COBJECTS) $(SOBJECTS) $(HTMLOBJECTS) \
$(foreach LIBRARY, $(LIBS),-l$(LIBRARY)) \ $(LDPARAM) $(MONERO_LIBS) \
$(foreach LIB,$(LIBPATH),-L$(LIB)) \ $(foreach LIBRARY, $(LIBS),-l$(LIBRARY)) \
$(PKG_LIBS) $(STATIC_LIBS) $(foreach LIB,$(LIBPATH),-L$(LIB)) \
$(PKG_LIBS) $(STATIC_LIBS)
@cp pool.conf $(STORE)/ @cp pool.conf $(STORE)/
@cp tools/* $(STORE)/ @cp tools/* $(STORE)/
$(STORE)/%.o: %.cpp $(STORE)/%.o: %.cpp
@echo Creating object file for $*... @echo Creating object file for $*...
$(C++) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) $(CXXFLAGS) \ $(C++) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) $(CXXFLAGS) \
$(foreach INC,$(INCPATH),-I$(INC)) \ $(foreach INC,$(INCPATH),-I$(INC)) \
$(PKG_INC) \ $(PKG_INC) \
$(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) \ $(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) \
-c $< -o $@ -c $< -o $@
@sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' $(STORE)/$*.dd > $(STORE)/$*.d @sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' \
$(STORE)/$*.dd > $(STORE)/$*.d
@rm -f $(STORE)/$*.dd @rm -f $(STORE)/$*.dd
$(STORE)/%.o: %.c $(STORE)/%.o: %.c
@echo Creating object file for $*... @echo Creating object file for $*...
$(CC) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) $(foreach INC,$(INCPATH),-I$(INC)) $(PKG_INC)\ $(CC) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) \
$(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) -c $< -o $@ $(foreach INC,$(INCPATH),-I$(INC)) $(PKG_INC) \
@sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' $(STORE)/$*.dd > $(STORE)/$*.d $(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) -c $< -o $@
@sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' \
$(STORE)/$*.dd > $(STORE)/$*.d
@rm -f $(STORE)/$*.dd @rm -f $(STORE)/$*.dd
$(STORE)/%.o: %.S $(STORE)/%.o: %.S
@echo Creating object file for $*... @echo Creating object file for $*...
$(CC) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) $(foreach INC,$(INCPATH),-I$(INC)) $(PKG_INC)\ $(CC) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) \
$(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) -c $< -o $@ $(foreach INC,$(INCPATH),-I$(INC)) $(PKG_INC) \
@sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' $(STORE)/$*.dd > $(STORE)/$*.d $(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) -c $< -o $@
@sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' \
$(STORE)/$*.dd > $(STORE)/$*.d
@rm -f $(STORE)/$*.dd @rm -f $(STORE)/$*.dd
$(STORE)/%.o: %.html $(STORE)/%.o: %.html
@ -202,17 +207,18 @@ clean:
dirs: dirs:
@-if [ ! -e $(STORE) ]; then mkdir -p $(STORE); fi; @-if [ ! -e $(STORE) ]; then mkdir -p $(STORE); fi;
@-$(foreach DIR,$(DIRS), \ @-$(foreach DIR,$(DIRS), \
if [ ! -e $(STORE)/$(DIR) ]; then mkdir -p $(STORE)/$(DIR); fi; ) if [ ! -e $(STORE)/$(DIR) ]; then mkdir -p $(STORE)/$(DIR); fi; )
preflight: preflight:
ifeq ($(origin MONERO_ROOT), undefined) 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 endif
ifndef PKG_LIBS ifndef PKG_LIBS
$(error Missing dependencies) $(error Missing dependencies)
endif endif
ifndef XXD ifndef XXD
$(error Command xxd not found) $(error Command xxd not found)
endif endif
-include $(DFILES) -include $(DFILES)

View File

@ -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 I have no plans to add any other payout mechanisms or other coins. Work should
stay focussed on performance, efficiency and stability. 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 ## Project status
I have tested this quite a bit on the Monero testnet (if you plan 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 451ytzQg1vUVkuAW73VsQ72G96FUjASi4WNQse3v8ALfjiR5vLzGQ2hMUdYhG38Fi15eJ5FJ1ZL4EV1SFVi228muGX4f3SV
``` ```
![QR code](./qr-small.png)
would be very much appreciated. would be very much appreciated.
## License ## License

View File

@ -11,3 +11,4 @@ pool-fee = 0.01
payment-threshold = 0.33 payment-threshold = 0.33
log-level = 5 log-level = 5
webui-port=4243 webui-port=4243
disable-self-select=0

BIN
qr-small.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

View File

@ -87,6 +87,7 @@ developers.
#define ADDRESS_MAX 128 #define ADDRESS_MAX 128
#define BLOCK_TIME 120 #define BLOCK_TIME 120
#define HR_BLOCK_COUNT 5 #define HR_BLOCK_COUNT 5
#define TEMLATE_HEIGHT_VARIANCE 5
#define uint128_t unsigned __int128 #define uint128_t unsigned __int128
@ -117,6 +118,7 @@ developers.
*/ */
enum block_status { BLOCK_LOCKED=0, BLOCK_UNLOCKED=1, BLOCK_ORPHANED=2 }; 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 typedef struct config_t
{ {
@ -135,6 +137,7 @@ typedef struct config_t
uint32_t webui_port; uint32_t webui_port;
char log_file[MAX_PATH]; char log_file[MAX_PATH];
bool block_notified; bool block_notified;
bool disable_self_select;
} config_t; } config_t;
typedef struct block_template_t typedef struct block_template_t
@ -156,6 +159,7 @@ typedef struct job_t
uint64_t target; uint64_t target;
uint128_t *submissions; uint128_t *submissions;
size_t submissions_count; size_t submissions_count;
block_template_t *miner_template;
} job_t; } job_t;
typedef struct client_t typedef struct client_t
@ -171,6 +175,7 @@ typedef struct client_t
uint64_t hashes; uint64_t hashes;
time_t connected_since; time_t connected_since;
bool is_proxy; bool is_proxy;
uint32_t mode;
} client_t; } client_t;
typedef struct pool_clients_t typedef struct pool_clients_t
@ -235,6 +240,8 @@ static BIGNUM *base_diff;
static pool_stats_t pool_stats; static pool_stats_t pool_stats;
static pthread_mutex_t mutex_clients = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t mutex_clients = PTHREAD_MUTEX_INITIALIZER;
static FILE *fd_log; static FILE *fd_log;
static char sec_view[32];
static char pub_spend[32];
#define JSON_GET_OR_ERROR(name, parent, type, client) \ #define JSON_GET_OR_ERROR(name, parent, type, client) \
json_object *name = NULL; \ 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 static void
target_to_hex(uint64_t target, char *target_hex) 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); log_debug("High target requested: %"PRIu64, target);
bin_to_hex((const char*)&target, 8, &target_hex[0], 16); bin_to_hex((const char*)&target, 8, &target_hex[0], 16);
target_hex[16] = '\0';
return; return;
} }
BIGNUM *diff = BN_new(); BIGNUM *diff = BN_new();
@ -806,7 +823,6 @@ target_to_hex(uint64_t target, char *target_hex)
BN_rshift(diff, diff, 224); BN_rshift(diff, diff, 224);
uint32_t w = BN_get_word(diff); uint32_t w = BN_get_word(diff);
bin_to_hex((const char*)&w, 4, &target_hex[0], 8); bin_to_hex((const char*)&w, 4, &target_hex[0], 8);
target_hex[8] = '\0';
BN_free(bnt); BN_free(bnt);
BN_free(diff); BN_free(diff);
} }
@ -818,10 +834,10 @@ stratum_get_proxy_job_body(char *body, const client_t *client,
int json_id = client->json_id; int json_id = client->json_id;
const char *client_id = client->client_id; const char *client_id = client->client_id;
const job_t *job = &client->active_jobs[0]; 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); bin_to_hex((const char*)job->id, sizeof(uuid_t), job_id, 32);
uint64_t target = job->target; uint64_t target = job->target;
char target_hex[17]; char target_hex[17] = {0};
target_to_hex(target, &target_hex[0]); target_to_hex(target, &target_hex[0]);
const block_template_t *bt = job->block_template; 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 static void
stratum_get_job_body(char *body, const client_t *client, bool response) stratum_get_job_body(char *body, const client_t *client, bool response)
{ {
int json_id = client->json_id; int json_id = client->json_id;
const char *client_id = client->client_id; const char *client_id = client->client_id;
const job_t *job = &client->active_jobs[0]; 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); bin_to_hex((const char*)job->id, sizeof(uuid_t), job_id, 32);
const char *blob = job->blob; const char *blob = job->blob;
uint64_t target = job->target; uint64_t target = job->target;
uint64_t height = job->block_template->height; uint64_t height = job->block_template->height;
char target_hex[17]; char target_hex[17] = {0};
target_to_hex(target, &target_hex[0]); target_to_hex(target, &target_hex[0]);
if (response) if (response)
@ -925,17 +981,28 @@ client_clear_jobs(client_t *client)
for (size_t i=0; i<CLIENT_JOBS_MAX; i++) for (size_t i=0; i<CLIENT_JOBS_MAX; i++)
{ {
job_t *job = &client->active_jobs[i]; job_t *job = &client->active_jobs[i];
if (job->blob != NULL) if (job->blob)
{ {
free(job->blob); free(job->blob);
job->blob = NULL; job->blob = NULL;
} }
if (job->submissions != NULL) if (job->submissions)
{ {
free(job->submissions); free(job->submissions);
job->submissions = NULL; job->submissions = NULL;
job->submissions_count = 0; 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) client_find_job(client_t *client, const char *job_id)
{ {
uuid_t jid; 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; i<CLIENT_JOBS_MAX; i++) for (size_t i=0; i<CLIENT_JOBS_MAX; i++)
{ {
job_t *job = &client->active_jobs[i]; job_t *job = &client->active_jobs[i];
@ -958,17 +1025,28 @@ client_send_job(client_t *client, bool response)
{ {
/* First cycle jobs */ /* First cycle jobs */
job_t *last = &client->active_jobs[CLIENT_JOBS_MAX-1]; job_t *last = &client->active_jobs[CLIENT_JOBS_MAX-1];
if (last->blob != NULL) if (last->blob)
{ {
free(last->blob); free(last->blob);
last->blob = NULL; last->blob = NULL;
} }
if (last->submissions != NULL) if (last->submissions)
{ {
free(last->submissions); free(last->submissions);
last->submissions = NULL; last->submissions = NULL;
last->submissions_count = 0; 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--) for (size_t i=CLIENT_JOBS_MAX-1; i>0; i--)
{ {
job_t *current = &client->active_jobs[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]; job_t *job = &client->active_jobs[0];
memset(job, 0, sizeof(job_t)); 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 */ /* Quick check we actually have a block template */
block_template_t *bt = bstack_peek(bst); block_template_t *bt = bstack_peek(bst);
if (!bt) if (!bt)
@ -996,7 +1088,7 @@ client_send_job(client_t *client, bool response)
/* Convert template to blob */ /* Convert template to blob */
size_t bin_size = strlen(bt->blocktemplate_blob) >> 1; size_t bin_size = strlen(bt->blocktemplate_blob) >> 1;
char *block = calloc(bin_size, sizeof(char)); 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 */ /* Set the extra nonce in our reserved space */
char *p = block; char *p = block;
@ -1027,16 +1119,11 @@ client_send_job(client_t *client, bool response)
job->block_template = bt; job->block_template = bt;
/* Send */ /* Send */
char job_id[33]; char job_id[33] = {0};
bin_to_hex((const char*)job->id, sizeof(uuid_t), job_id, 32); bin_to_hex((const char*)job->id, sizeof(uuid_t), job_id, 32);
/* Retarget */ /* Retarget */
double duration = difftime(time(NULL), client->connected_since); retarget(client, job);
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);
char body[JOB_BODY_MAX]; char body[JOB_BODY_MAX];
if (!client->is_proxy) 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); const char *ss = json_object_get_string(status);
json_object *error = NULL; json_object *error = NULL;
json_object_object_get_ex(root, "error", &error); 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(code, error, json_type_object);
JSON_GET_OR_WARN(message, error, json_type_string); 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); json_object_put(root);
return; return;
} }
if (status == NULL || strcmp(ss, "OK") != 0) if (!status || strcmp(ss, "OK") != 0)
{ {
log_error("Error getting block header by height: %s", ss); log_error("Error getting block header by height: %s", ss);
json_object_put(root); 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); const char *ss = json_object_get_string(status);
json_object *error = NULL; json_object *error = NULL;
json_object_object_get_ex(root, "error", &error); 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(code, error, json_type_object);
JSON_GET_OR_WARN(message, error, json_type_string); 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); json_object_put(root);
return; return;
} }
if (status == NULL || strcmp(ss, "OK") != 0) if (!status || strcmp(ss, "OK") != 0)
{ {
log_warn("Error getting block headers by range: %s", ss); log_warn("Error getting block headers by range: %s", ss);
json_object_put(root); 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); const char *ss = json_object_get_string(status);
json_object *error = NULL; json_object *error = NULL;
json_object_object_get_ex(root, "error", &error); 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(code, error, json_type_object);
JSON_GET_OR_WARN(message, error, json_type_string); 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); json_object_put(root);
return; return;
} }
if (status == NULL || strcmp(ss, "OK") != 0) if (!status || strcmp(ss, "OK") != 0)
{ {
log_error("Error getting block template: %s", ss); log_error("Error getting block template: %s", ss);
json_object_put(root); json_object_put(root);
@ -1419,6 +1506,32 @@ startup_pauout(uint64_t height)
return 0; 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 static void
rpc_on_last_block_header(const char* data, rpc_callback_t *callback) 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); const char *ss = json_object_get_string(status);
json_object *error = NULL; json_object *error = NULL;
json_object_object_get_ex(root, "error", &error); 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(code, error, json_type_object);
JSON_GET_OR_WARN(message, error, json_type_string); 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); json_object_put(root);
return; return;
} }
if (status == NULL || strcmp(ss, "OK") != 0) if (!status || strcmp(ss, "OK") != 0)
{ {
log_error("Error getting last block header: %s", ss); log_error("Error getting last block header: %s", ss);
json_object_put(root); 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); uint64_t bh = json_object_get_int64(height);
bool need_new_template = false; bool need_new_template = false;
block_t *front = bstack_peek(bsh); block_t *front = bstack_peek(bsh);
if (front != NULL && bh > front->height) if (front && bh > front->height)
{ {
need_new_template = true; need_new_template = true;
block_t *block = bstack_push(bsh, NULL); block_t *block = bstack_push(bsh, NULL);
response_to_block(block_header, block); response_to_block(block_header, block);
} }
else if (front == NULL) else if (!front)
{ {
block_t *block = bstack_push(bsh, NULL); block_t *block = bstack_push(bsh, NULL);
response_to_block(block_header, block); 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 an alternative block. Thus, still store it. This doesn't matter
as upon payout, blocks are checked whether they are orphaned or not. 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(code, error, json_type_object);
JSON_GET_OR_WARN(message, error, json_type_string); 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); const char *em = json_object_get_string(message);
log_warn("Error (%d) with block submission: %s", ec, em); 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); 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_GET_OR_WARN(result, root, json_type_object);
json_object *error = NULL; json_object *error = NULL;
json_object_object_get_ex(root, "error", &error); 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(code, error, json_type_object);
JSON_GET_OR_WARN(message, error, json_type_string); JSON_GET_OR_WARN(message, error, json_type_string);
@ -1741,6 +1854,15 @@ send_payments(void)
return 0; 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 static void
fetch_last_block_header(void) fetch_last_block_header(void)
{ {
@ -1819,9 +1941,9 @@ client_find(struct bufferevent *bev, client_t **client)
static void static void
client_clear(struct bufferevent *bev) client_clear(struct bufferevent *bev)
{ {
client_t *client; client_t *client = NULL;
client_find(bev, &client); client_find(bev, &client);
if (client == NULL) if (!client)
return; return;
client_clear_jobs(client); client_clear_jobs(client);
memset(client, 0, sizeof(client_t)); 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(params, message, json_type_object, client);
JSON_GET_OR_ERROR(login, params, json_type_string, client); JSON_GET_OR_ERROR(login, params, json_type_string, client);
JSON_GET_OR_ERROR(pass, 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); const char *address = json_object_get_string(login);
uint64_t prefix; uint64_t prefix;
parse_address(address, &prefix); parse_address(address, &prefix, NULL);
if (prefix != MAINNET_ADDRESS_PREFIX && prefix != TESTNET_ADDRESS_PREFIX) if (prefix != MAINNET_ADDRESS_PREFIX && prefix != TESTNET_ADDRESS_PREFIX)
return send_validation_error(client, return send_validation_error(client,
"login only main wallet addresses are supported"); "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->address, address, sizeof(client->address));
strncpy(client->worker_id, worker_id, sizeof(client->worker_id)); strncpy(client->worker_id, worker_id, sizeof(client->worker_id));
uuid_t cid; uuid_t cid;
uuid_generate(cid); uuid_generate(cid);
bin_to_hex((const char*)cid, sizeof(uuid_t), client->client_id, 32); 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); 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 static void
client_on_submit(json_object *message, client_t *client) 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 */ /* 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; char *btb = bt->blocktemplate_blob;
size_t bin_size = strlen(btb) >> 1; size_t bin_size = strlen(btb) >> 1;
char *block = calloc(bin_size, sizeof(char)); 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; 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 pool_nonce = 0;
uint32_t worker_nonce = 0; uint32_t worker_nonce = 0;
if (client->is_proxy)
if (client->mode != MODE_SELF_SELECT)
{ {
/* /* Set the extra nonce and instance_id in our reserved space */
A proxy supplies pool_nonce and worker_nonce p += bt->reserved_offset;
so add them in the resrved space too. memcpy(p, &job->extra_nonce, sizeof(extra_nonce));
*/
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; p += 4;
memcpy(p, &pool_nonce, sizeof(pool_nonce)); memcpy(p, &instance_id, sizeof(instance_id));
p += 4; if (client->is_proxy)
memcpy(p, &worker_nonce, sizeof(worker_nonce)); {
/*
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; uint128_t sub = 0;
uint32_t *psub = (uint32_t*) &sub; uint32_t *psub = (uint32_t*) &sub;
*psub++ = result_nonce; *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; const int cn_variant = major_version >= 7 ? major_version - 6 : 0;
get_hash(hashing_blob, hashing_blob_size, get_hash(hashing_blob, hashing_blob_size,
(char**)&result_hash, cn_variant, bt->height); (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) if (memcmp(submitted_hash, result_hash, 32) != 0)
{ {
@ -2102,17 +2313,18 @@ client_on_read(struct bufferevent *bev, void *ctx)
struct evbuffer *input, *output; struct evbuffer *input, *output;
char *line; char *line;
size_t n; size_t n;
client_t *client; client_t *client = NULL;
client_find(bev, &client); client_find(bev, &client);
if (client == NULL) if (!client)
return; return;
input = bufferevent_get_input(bev); input = bufferevent_get_input(bev);
output = bufferevent_get_output(bev); output = bufferevent_get_output(bev);
size_t len = evbuffer_get_length(input); 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"; const char *too_long = "Message too long\n";
evbuffer_add(output, too_long, strlen(too_long)); evbuffer_add(output, too_long, strlen(too_long));
@ -2132,7 +2344,7 @@ client_on_read(struct bufferevent *bev, void *ctx)
bool unknown = false; bool unknown = false;
if (method_name == NULL) if (!method || !method_name)
{ {
unknown = true; unknown = true;
} }
@ -2140,6 +2352,10 @@ client_on_read(struct bufferevent *bev, void *ctx)
{ {
client_on_login(message, client); 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) else if (strcmp(method_name, "submit") == 0)
{ {
client_on_submit(message, client); 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.log_level = 5;
config.webui_port = 4243; config.webui_port = 4243;
config.block_notified = block_notified; config.block_notified = block_notified;
config.disable_self_select = false;
char path[MAX_PATH]; char path[MAX_PATH] = {0};
if (config_file) if (config_file)
{ {
strncpy(path, config_file, MAX_PATH); 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); log_info("Reading config at: %s", path);
FILE *fp = fopen(path, "r"); FILE *fp = fopen(path, "r");
if (fp == NULL) if (!fp)
{ {
log_fatal("Cannot open config file. Aborting."); log_fatal("Cannot open config file. Aborting.");
abort(); abort();
@ -2276,13 +2493,13 @@ read_config(const char *config_file, const char *log_file, bool block_notified)
char *key; char *key;
char *val; char *val;
const char *tok = " ="; const char *tok = " =";
while (fgets(line, sizeof(line), fp) != NULL) while (fgets(line, sizeof(line), fp))
{ {
key = strtok(line, tok); key = strtok(line, tok);
if (key == NULL) if (!key)
continue; continue;
val = strtok(NULL, tok); val = strtok(NULL, tok);
if (val == NULL) if (!val)
continue; continue;
val[strcspn(val, "\r\n")] = 0; val[strcspn(val, "\r\n")] = 0;
if (strcmp(key, "rpc-host") == 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); config.block_notified = atoi(val);
} }
else if (strcmp(key, "disable-self-select") == 0)
{
config.disable_self_select = atoi(val);
}
} }
fclose(fp); fclose(fp);
if (log_file != NULL) if (log_file)
strncpy(config.log_file, log_file, sizeof(config.log_file)); strncpy(config.log_file, log_file, sizeof(config.log_file));
if (!config.pool_wallet[0]) 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 " "pool_fee = %.3f\n payment_threshold = %.2f\n "
"wallet_rpc_host = %s\n wallet_rpc_port = %u\n pool_port = %u\n " "wallet_rpc_host = %s\n wallet_rpc_port = %u\n pool_port = %u\n "
"log_level = %u\n webui_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.rpc_host, config.rpc_port, config.rpc_timeout,
config.pool_wallet, config.pool_start_diff, config.share_mul, config.pool_wallet, config.pool_start_diff, config.share_mul,
config.pool_fee, config.payment_threshold, config.pool_fee, config.payment_threshold,
config.wallet_rpc_host, config.wallet_rpc_port, config.pool_port, config.wallet_rpc_host, config.wallet_rpc_port, config.pool_port,
config.log_level, config.webui_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 static void
@ -2448,6 +2670,8 @@ run(void)
else else
fetch_last_block_header(); fetch_last_block_header();
fetch_view_key();
timer_10m = evtimer_new(base, timer_on_10m, NULL); timer_10m = evtimer_new(base, timer_on_10m, NULL);
timer_on_10m(-1, EV_TIMEOUT, NULL); timer_on_10m(-1, EV_TIMEOUT, NULL);
@ -2476,7 +2700,7 @@ cleanup(void)
BN_CTX_free(bn_ctx); BN_CTX_free(bn_ctx);
pthread_mutex_destroy(&mutex_clients); pthread_mutex_destroy(&mutex_clients);
log_info("Pool shutdown successfully"); log_info("Pool shutdown successfully");
if (fd_log != NULL) if (fd_log)
fclose(fd_log); fclose(fd_log);
} }
@ -2525,19 +2749,19 @@ int main(int argc, char **argv)
read_config(config_file, log_file, block_notified); read_config(config_file, log_file, block_notified);
log_set_level(LOG_FATAL - config.log_level); 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"); 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); log_info("Failed to open log file: %s", config.log_file);
else else
log_set_fp(fd_log); log_set_fp(fd_log);
} }
if (config_file != NULL) if (config_file)
free(config_file); free(config_file);
if (log_file != NULL) if (log_file)
free(log_file); free(log_file);
int err = 0; int err = 0;
@ -2567,6 +2791,7 @@ int main(int argc, char **argv)
uic.pool_stats = &pool_stats; uic.pool_stats = &pool_stats;
uic.pool_fee = config.pool_fee; uic.pool_fee = config.pool_fee;
uic.pool_port = config.pool_port; uic.pool_port = config.pool_port;
uic.allow_self_select = !config.disable_self_select;
uic.payment_threshold = config.payment_threshold; uic.payment_threshold = config.payment_threshold;
start_web_ui(&uic); start_web_ui(&uic);

View File

@ -57,11 +57,11 @@ is_hex_string(const char *str)
} }
void 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(hex_len % 2 == 0);
assert(len % 2 == 0); assert(bin_size >= hex_len >> 1);
assert(bin_size >= len / 2);
const char *ph = hex; const char *ph = hex;
char *end = bin + bin_size; char *end = bin + bin_size;
while (*ph && bin < end) while (*ph && bin < end)
@ -72,7 +72,8 @@ hex_to_bin(const char *hex, char *bin, size_t bin_size)
} }
void 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); assert(bin_size << 1 == hex_size);
const char *hex_chars = "0123456789abcdef"; 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 void
reverse_bin(char *bin, size_t len) reverse_bin(char *bin, const size_t len)
{ {
size_t start = 0; size_t start = 0;
size_t end = len-1; size_t end = len-1;

View File

@ -36,9 +36,11 @@ developers.
#define UTIL_H #define UTIL_H
int is_hex_string(const char *str); int is_hex_string(const char *str);
void hex_to_bin(const char *hex, char *bin, size_t bin_size); void hex_to_bin(const char *hex, const size_t hex_len,
void bin_to_hex(const char *bin, size_t bin_size, char *hex, size_t hex_size); char *bin, const size_t bin_size);
void reverse_bin(char *bin, size_t len); 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); char *stecpy(char *dst, const char *src, const char *end);
#endif #endif

View File

@ -39,6 +39,7 @@
<tr><td>Payment threshold: </td><td id="payment_threshold"></td></tr> <tr><td>Payment threshold: </td><td id="payment_threshold"></td></tr>
<tr><td>Pool fee: </td><td id="pool_fee"></td></tr> <tr><td>Pool fee: </td><td id="pool_fee"></td></tr>
<tr><td>Pool port: </td><td id="pool_port"></td></tr> <tr><td>Pool port: </td><td id="pool_port"></td></tr>
<tr><td>Allow self-select: </td><td id="allow_self_select"></td></tr>
<tr><td>Miners connected: </td><td id="connected_miners"></td></tr> <tr><td>Miners connected: </td><td id="connected_miners"></td></tr>
<tr class="wallet"><td>Your HR: </td><td id="miner_hashrate"></td></tr> <tr class="wallet"><td>Your HR: </td><td id="miner_hashrate"></td></tr>
<tr class="wallet"><td>Balance due: </td><td id="miner_balance"></td></tr> <tr class="wallet"><td>Balance due: </td><td id="miner_balance"></td></tr>
@ -113,6 +114,8 @@
el.innerHTML = format_hashrate(stats[e]); el.innerHTML = format_hashrate(stats[e]);
else if (e == "pool_fee") else if (e == "pool_fee")
el.innerHTML = (stats[e]*100) + "%"; el.innerHTML = (stats[e]*100) + "%";
else if (e == "allow_self_select")
el.innerHTML = stats[e] == 1 ? "Yes" : "No";
else else
el.innerHTML = stats[e]; el.innerHTML = stats[e];
} }

View File

@ -55,7 +55,7 @@ developers.
#include "webui.h" #include "webui.h"
#define TAG_MAX 17 #define TAG_MAX 17
#define PAGE_MAX 4096 #define PAGE_MAX 8192
#define JSON_MAX 512 #define JSON_MAX 512
extern unsigned char webui_html[]; 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 ltf = context->pool_stats->last_template_fetched;
uint64_t lbf = context->pool_stats->last_block_found; uint64_t lbf = context->pool_stats->last_block_found;
uint32_t pbf = context->pool_stats->pool_blocks_found; uint32_t pbf = context->pool_stats->pool_blocks_found;
unsigned ss = context->allow_self_select;
uint64_t mh = 0; uint64_t mh = 0;
double mb = 0.0; double mb = 0.0;
const char *wa = MHD_lookup_connection_value(connection, const char *wa = MHD_lookup_connection_value(connection,
@ -96,12 +97,13 @@ send_json_stats (void *cls, struct MHD_Connection *connection)
"\"payment_threshold\":%.2f," "\"payment_threshold\":%.2f,"
"\"pool_fee\":%.3f," "\"pool_fee\":%.3f,"
"\"pool_port\":%d," "\"pool_port\":%d,"
"\"allow_self_select\":%u,"
"\"connected_miners\":%d," "\"connected_miners\":%d,"
"\"miner_hashrate\":%"PRIu64"," "\"miner_hashrate\":%"PRIu64","
"\"miner_balance\":%.8f" "\"miner_balance\":%.8f"
"}", ph, nh, height, ltf, lbf, pbf, "}", ph, nh, height, ltf, lbf, pbf,
context->payment_threshold, context->pool_fee, 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); mh, mb);
response = MHD_create_response_from_buffer(strlen(json), response = MHD_create_response_from_buffer(strlen(json),
(void*) json, MHD_RESPMEM_MUST_COPY); (void*) json, MHD_RESPMEM_MUST_COPY);

View File

@ -54,6 +54,7 @@ typedef struct wui_context_t
double pool_fee; double pool_fee;
double payment_threshold; double payment_threshold;
uint32_t pool_port; uint32_t pool_port;
unsigned allow_self_select;
} wui_context_t; } wui_context_t;
int start_web_ui(wui_context_t *context); int start_web_ui(wui_context_t *context);

View File

@ -45,12 +45,16 @@ developers.
#include "cryptonote_basic/difficulty.h" #include "cryptonote_basic/difficulty.h"
#include "crypto/crypto.h" #include "crypto/crypto.h"
#include "crypto/hash.h" #include "crypto/hash.h"
#include "common/base58.h"
#include "serialization/binary_utils.h" #include "serialization/binary_utils.h"
#include "ringct/rctSigs.h"
#include "common/base58.h"
#include "string_tools.h"
#include "xmr.h" #include "xmr.h"
using namespace epee::string_tools;
using namespace cryptonote; using namespace cryptonote;
using namespace crypto;
int get_hashing_blob(const char *input, const size_t in_size, int get_hashing_blob(const char *input, const size_t in_size,
char **output, size_t *out_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); blobdata bd = std::string(input, in_size);
if (!parse_and_validate_block_from_blob(bd, b)) if (!parse_and_validate_block_from_blob(bd, b))
{ {
printf("Failed to parse block\n"); return XMR_PARSE_ERROR;
return -1;
} }
blobdata blob = get_block_hashing_blob(b); blobdata blob = get_block_hashing_blob(b);
*out_size = blob.length(); *out_size = blob.length();
*output = (char*) malloc(*out_size); *output = (char*) malloc(*out_size);
memcpy(*output, blob.data(), *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; uint64_t tag;
std::string decoded; std::string decoded;
@ -78,14 +81,80 @@ int parse_address(const char *input, uint64_t *prefix)
if (rv) if (rv)
{ {
*prefix = tag; *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, void get_hash(const char *input, const size_t in_size,
char **output, int variant, uint64_t height) char **output, int variant, uint64_t height)
{ {
crypto::cn_slow_hash(input, in_size, cn_slow_hash(input, in_size,
reinterpret_cast<crypto::hash&>(*output), variant, height); reinterpret_cast<hash&>(*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_field> 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<txout_to_key>(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;
} }

View File

@ -39,11 +39,24 @@ developers.
extern "C" { extern "C" {
#endif #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, int get_hashing_blob(const char *input, const size_t in_size,
char **output, size_t *out_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, void get_hash(const char *input, const size_t in_size,
char **output, int variant, uint64_t height); 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 #ifdef __cplusplus
} }

174
stratum-ss.md Normal file
View File

@ -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: )