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

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
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

View file

@ -11,3 +11,4 @@ pool-fee = 0.01
payment-threshold = 0.33
log-level = 5
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 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; i<CLIENT_JOBS_MAX; i++)
{
job_t *job = &client->active_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; i<CLIENT_JOBS_MAX; i++)
{
job_t *job = &client->active_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*) &sub;
*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);

View file

@ -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;

View file

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

View file

@ -39,6 +39,7 @@
<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 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 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>
@ -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];
}

View file

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

View file

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

View file

@ -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<crypto::hash&>(*output), variant, height);
cn_slow_hash(input, in_size,
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" {
#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
}

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