mirror of
https://git.wownero.com/wownero/wownero-puddle.git
synced 2024-08-15 01:03:20 +00:00
implement miner selected block templates
This commit is contained in:
parent
86a9900dfc
commit
b5a2dd5f5b
13 changed files with 689 additions and 186 deletions
188
Makefile
188
Makefile
|
@ -1,65 +1,60 @@
|
|||
define LICENSE
|
||||
|
||||
Copyright (c) 2014-2019, The Monero Project
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Parts of the project are originally copyright (c) 2012-2013 The Cryptonote
|
||||
developers.
|
||||
|
||||
endef
|
||||
# Copyright (c) 2019, The Monero Project
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from this
|
||||
# software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
TARGET = monero-pool
|
||||
|
||||
TYPE = debug
|
||||
|
||||
ifeq ($(MAKECMDGOALS),release)
|
||||
TYPE = release
|
||||
TYPE = release
|
||||
endif
|
||||
|
||||
MONERO_BUILD_ROOT = ${MONERO_ROOT}/build/$(shell echo `uname | \
|
||||
sed -e 's|[:/\\ \(\)]|_|g'`/`\
|
||||
git -C ${MONERO_ROOT} branch | grep '\* ' | cut -f2- -d' '| \
|
||||
sed -e 's|[:/\\ \(\)]|_|g'`)/release
|
||||
MONERO_BUILD_ROOT = \
|
||||
${MONERO_ROOT}/build/$(shell echo `uname | \
|
||||
sed -e 's|[:/\\ \(\)]|_|g'`/` \
|
||||
git -C ${MONERO_ROOT} branch | \
|
||||
grep '\* ' | cut -f2- -d' '| \
|
||||
sed -e 's|[:/\\ \(\)]|_|g'`)/release
|
||||
|
||||
MONERO_INC = ${MONERO_ROOT}/src \
|
||||
${MONERO_ROOT}/external/easylogging++ \
|
||||
${MONERO_ROOT}/contrib/epee/include
|
||||
MONERO_INC = \
|
||||
${MONERO_ROOT}/src \
|
||||
${MONERO_ROOT}/external \
|
||||
${MONERO_ROOT}/external/easylogging++ \
|
||||
${MONERO_ROOT}/contrib/epee/include
|
||||
|
||||
MONERO_LIBS = \
|
||||
${MONERO_BUILD_ROOT}/src/cryptonote_basic/libcryptonote_basic.a \
|
||||
${MONERO_BUILD_ROOT}/src/cryptonote_core/libcryptonote_core.a \
|
||||
${MONERO_BUILD_ROOT}/src/crypto/libcncrypto.a \
|
||||
${MONERO_BUILD_ROOT}/src/common/libcommon.a \
|
||||
${MONERO_BUILD_ROOT}/src/ringct/libringct.a \
|
||||
${MONERO_BUILD_ROOT}/src/ringct/libringct_basic.a \
|
||||
${MONERO_BUILD_ROOT}/src/device/libdevice.a \
|
||||
${MONERO_BUILD_ROOT}/src/blockchain_db/libblockchain_db.a \
|
||||
${MONERO_BUILD_ROOT}/contrib/epee/src/libepee.a \
|
||||
${MONERO_BUILD_ROOT}/external/easylogging++/libeasylogging.a
|
||||
|
||||
|
@ -70,48 +65,52 @@ OS := $(shell uname -s)
|
|||
CPPDEFS = _GNU_SOURCE AUTO_INITIALIZE_EASYLOGGINGPP LOG_USE_COLOR
|
||||
|
||||
CCPARAM = -Wall $(CFLAGS) -maes -fPIC
|
||||
CXXFLAGS = -std=c++11
|
||||
CXXFLAGS = -std=c++11 -Wno-reorder
|
||||
|
||||
ifeq ($(OS), Darwin)
|
||||
CXXFLAGS += -stdlib=libc++
|
||||
CPPDEFS += HAVE_MEMSET_S
|
||||
CXXFLAGS += -stdlib=libc++
|
||||
CPPDEFS += HAVE_MEMSET_S
|
||||
endif
|
||||
|
||||
ifeq ($(OS),Darwin)
|
||||
LDPARAM =
|
||||
LDPARAM =
|
||||
else
|
||||
LDPARAM = -Wl,-warn-unresolved-symbols -fPIC -pie
|
||||
LDPARAM = -Wl,-warn-unresolved-symbols -fPIC -pie
|
||||
endif
|
||||
|
||||
ifeq ($(TYPE),debug)
|
||||
CCPARAM += -g
|
||||
CPPDEFS += DEBUG
|
||||
CCPARAM += -g
|
||||
CPPDEFS += DEBUG
|
||||
endif
|
||||
|
||||
ifeq ($(TYPE), release)
|
||||
CCPARAM += -O3 -Wno-unused-variable
|
||||
ifneq ($(OS), Darwin)
|
||||
LDPARAM = -Wl,--unresolved-symbols=ignore-in-object-files
|
||||
endif
|
||||
CCPARAM += -O3 -Wno-unused-variable
|
||||
ifneq ($(OS), Darwin)
|
||||
LDPARAM = -Wl,--unresolved-symbols=ignore-in-object-files
|
||||
endif
|
||||
endif
|
||||
|
||||
LDPARAM += $(LDFLAGS)
|
||||
|
||||
LIBS := lmdb pthread microhttpd unbound
|
||||
ifeq ($(OS), Darwin)
|
||||
LIBS += c++ boost_system-mt boost_date_time-mt boost_chrono-mt \
|
||||
boost_filesystem-mt boost_thread-mt boost_regex-mt
|
||||
LIBS += c++ \
|
||||
boost_system-mt boost_date_time-mt boost_chrono-mt \
|
||||
boost_filesystem-mt boost_thread-mt boost_regex-mt \
|
||||
boost_serialization-mt boost_program_options-mt
|
||||
else
|
||||
LIBS += dl boost_system boost_date_time boost_chrono boost_filesystem \
|
||||
boost_thread boost_regex uuid
|
||||
LIBS += dl uuid \
|
||||
boost_system boost_date_time boost_chrono \
|
||||
boost_filesystem boost_thread boost_regex \
|
||||
boost_serialization boost_program_options
|
||||
endif
|
||||
|
||||
PKG_LIBS := $(shell pkg-config \
|
||||
"libevent >= 2.1" \
|
||||
json-c \
|
||||
openssl \
|
||||
libsodium \
|
||||
--libs)
|
||||
"libevent >= 2.1" \
|
||||
json-c \
|
||||
openssl \
|
||||
libsodium \
|
||||
--libs)
|
||||
|
||||
STATIC_LIBS =
|
||||
DLIBS =
|
||||
|
@ -119,11 +118,11 @@ DLIBS =
|
|||
INCPATH := $(DIRS) ${MONERO_INC} /opt/local/include /usr/local/include
|
||||
|
||||
PKG_INC := $(shell pkg-config \
|
||||
"libevent >= 2.1" \
|
||||
json-c \
|
||||
openssl \
|
||||
libsodium \
|
||||
--cflags)
|
||||
"libevent >= 2.1" \
|
||||
json-c \
|
||||
openssl \
|
||||
libsodium \
|
||||
--cflags)
|
||||
|
||||
LIBPATH := /opt/local/lib/ /usr/local/lib
|
||||
|
||||
|
@ -150,36 +149,42 @@ SDFILES := $(addprefix $(STORE)/,$(CSOURCE:.S=.d))
|
|||
|
||||
$(TARGET): preflight dirs $(OBJECTS) $(COBJECTS) $(SOBJECTS) $(HTMLOBJECTS)
|
||||
@echo Linking $(OBJECTS)...
|
||||
$(C++) -o $(STORE)/$(TARGET) $(OBJECTS) $(COBJECTS) $(SOBJECTS) $(HTMLOBJECTS) \
|
||||
$(LDPARAM) $(MONERO_LIBS) \
|
||||
$(foreach LIBRARY, $(LIBS),-l$(LIBRARY)) \
|
||||
$(foreach LIB,$(LIBPATH),-L$(LIB)) \
|
||||
$(PKG_LIBS) $(STATIC_LIBS)
|
||||
$(C++) -o $(STORE)/$(TARGET) \
|
||||
$(OBJECTS) $(COBJECTS) $(SOBJECTS) $(HTMLOBJECTS) \
|
||||
$(LDPARAM) $(MONERO_LIBS) \
|
||||
$(foreach LIBRARY, $(LIBS),-l$(LIBRARY)) \
|
||||
$(foreach LIB,$(LIBPATH),-L$(LIB)) \
|
||||
$(PKG_LIBS) $(STATIC_LIBS)
|
||||
@cp pool.conf $(STORE)/
|
||||
@cp tools/* $(STORE)/
|
||||
|
||||
$(STORE)/%.o: %.cpp
|
||||
@echo Creating object file for $*...
|
||||
$(C++) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) $(CXXFLAGS) \
|
||||
$(foreach INC,$(INCPATH),-I$(INC)) \
|
||||
$(PKG_INC) \
|
||||
$(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) \
|
||||
-c $< -o $@
|
||||
@sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' $(STORE)/$*.dd > $(STORE)/$*.d
|
||||
$(foreach INC,$(INCPATH),-I$(INC)) \
|
||||
$(PKG_INC) \
|
||||
$(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) \
|
||||
-c $< -o $@
|
||||
@sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' \
|
||||
$(STORE)/$*.dd > $(STORE)/$*.d
|
||||
@rm -f $(STORE)/$*.dd
|
||||
|
||||
$(STORE)/%.o: %.c
|
||||
@echo Creating object file for $*...
|
||||
$(CC) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) $(foreach INC,$(INCPATH),-I$(INC)) $(PKG_INC)\
|
||||
$(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) -c $< -o $@
|
||||
@sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' $(STORE)/$*.dd > $(STORE)/$*.d
|
||||
$(CC) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) \
|
||||
$(foreach INC,$(INCPATH),-I$(INC)) $(PKG_INC) \
|
||||
$(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) -c $< -o $@
|
||||
@sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' \
|
||||
$(STORE)/$*.dd > $(STORE)/$*.d
|
||||
@rm -f $(STORE)/$*.dd
|
||||
|
||||
$(STORE)/%.o: %.S
|
||||
@echo Creating object file for $*...
|
||||
$(CC) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) $(foreach INC,$(INCPATH),-I$(INC)) $(PKG_INC)\
|
||||
$(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) -c $< -o $@
|
||||
@sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' $(STORE)/$*.dd > $(STORE)/$*.d
|
||||
$(CC) -Wp,-MMD,$(STORE)/$*.dd $(CCPARAM) \
|
||||
$(foreach INC,$(INCPATH),-I$(INC)) $(PKG_INC) \
|
||||
$(foreach CPPDEF,$(CPPDEFS),-D$(CPPDEF)) -c $< -o $@
|
||||
@sed -e '1s/^\(.*\)$$/$(subst /,\/,$(dir $@))\1/' \
|
||||
$(STORE)/$*.dd > $(STORE)/$*.d
|
||||
@rm -f $(STORE)/$*.dd
|
||||
|
||||
$(STORE)/%.o: %.html
|
||||
|
@ -202,17 +207,18 @@ clean:
|
|||
dirs:
|
||||
@-if [ ! -e $(STORE) ]; then mkdir -p $(STORE); fi;
|
||||
@-$(foreach DIR,$(DIRS), \
|
||||
if [ ! -e $(STORE)/$(DIR) ]; then mkdir -p $(STORE)/$(DIR); fi; )
|
||||
if [ ! -e $(STORE)/$(DIR) ]; then mkdir -p $(STORE)/$(DIR); fi; )
|
||||
|
||||
preflight:
|
||||
ifeq ($(origin MONERO_ROOT), undefined)
|
||||
$(error You need to set an environment variable MONERO_ROOT to your monero repository root)
|
||||
$(error You need to set an environment variable MONERO_ROOT \
|
||||
to your monero repository root)
|
||||
endif
|
||||
ifndef PKG_LIBS
|
||||
$(error Missing dependencies)
|
||||
$(error Missing dependencies)
|
||||
endif
|
||||
ifndef XXD
|
||||
$(error Command xxd not found)
|
||||
$(error Command xxd not found)
|
||||
endif
|
||||
|
||||
-include $(DFILES)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
BIN
qr-small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 552 B |
375
src/pool.c
375
src/pool.c
|
@ -87,6 +87,7 @@ developers.
|
|||
#define ADDRESS_MAX 128
|
||||
#define BLOCK_TIME 120
|
||||
#define HR_BLOCK_COUNT 5
|
||||
#define TEMLATE_HEIGHT_VARIANCE 5
|
||||
|
||||
#define uint128_t unsigned __int128
|
||||
|
||||
|
@ -117,6 +118,7 @@ developers.
|
|||
*/
|
||||
|
||||
enum block_status { BLOCK_LOCKED=0, BLOCK_UNLOCKED=1, BLOCK_ORPHANED=2 };
|
||||
enum stratum_mode { MODE_NORMAL=0, MODE_SELF_SELECT=1 };
|
||||
|
||||
typedef struct config_t
|
||||
{
|
||||
|
@ -135,6 +137,7 @@ typedef struct config_t
|
|||
uint32_t webui_port;
|
||||
char log_file[MAX_PATH];
|
||||
bool block_notified;
|
||||
bool disable_self_select;
|
||||
} config_t;
|
||||
|
||||
typedef struct block_template_t
|
||||
|
@ -156,6 +159,7 @@ typedef struct job_t
|
|||
uint64_t target;
|
||||
uint128_t *submissions;
|
||||
size_t submissions_count;
|
||||
block_template_t *miner_template;
|
||||
} job_t;
|
||||
|
||||
typedef struct client_t
|
||||
|
@ -171,6 +175,7 @@ typedef struct client_t
|
|||
uint64_t hashes;
|
||||
time_t connected_since;
|
||||
bool is_proxy;
|
||||
uint32_t mode;
|
||||
} client_t;
|
||||
|
||||
typedef struct pool_clients_t
|
||||
|
@ -235,6 +240,8 @@ static BIGNUM *base_diff;
|
|||
static pool_stats_t pool_stats;
|
||||
static pthread_mutex_t mutex_clients = PTHREAD_MUTEX_INITIALIZER;
|
||||
static FILE *fd_log;
|
||||
static char sec_view[32];
|
||||
static char pub_spend[32];
|
||||
|
||||
#define JSON_GET_OR_ERROR(name, parent, type, client) \
|
||||
json_object *name = NULL; \
|
||||
|
@ -783,6 +790,17 @@ template_recycle(void *item)
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
retarget(client_t *client, job_t *job)
|
||||
{
|
||||
double duration = difftime(time(NULL), client->connected_since);
|
||||
uint8_t retarget_time = client->is_proxy ? 5 : 120;
|
||||
uint64_t target = fmax((double)client->hashes /
|
||||
duration * retarget_time, config.pool_start_diff);
|
||||
job->target = target;
|
||||
log_debug("Client %.32s target now %"PRIu64, client->client_id, target);
|
||||
}
|
||||
|
||||
static void
|
||||
target_to_hex(uint64_t target, char *target_hex)
|
||||
{
|
||||
|
@ -790,7 +808,6 @@ target_to_hex(uint64_t target, char *target_hex)
|
|||
{
|
||||
log_debug("High target requested: %"PRIu64, target);
|
||||
bin_to_hex((const char*)&target, 8, &target_hex[0], 16);
|
||||
target_hex[16] = '\0';
|
||||
return;
|
||||
}
|
||||
BIGNUM *diff = BN_new();
|
||||
|
@ -806,7 +823,6 @@ target_to_hex(uint64_t target, char *target_hex)
|
|||
BN_rshift(diff, diff, 224);
|
||||
uint32_t w = BN_get_word(diff);
|
||||
bin_to_hex((const char*)&w, 4, &target_hex[0], 8);
|
||||
target_hex[8] = '\0';
|
||||
BN_free(bnt);
|
||||
BN_free(diff);
|
||||
}
|
||||
|
@ -818,10 +834,10 @@ stratum_get_proxy_job_body(char *body, const client_t *client,
|
|||
int json_id = client->json_id;
|
||||
const char *client_id = client->client_id;
|
||||
const job_t *job = &client->active_jobs[0];
|
||||
char job_id[33];
|
||||
char job_id[33] = {0};
|
||||
bin_to_hex((const char*)job->id, sizeof(uuid_t), job_id, 32);
|
||||
uint64_t target = job->target;
|
||||
char target_hex[17];
|
||||
char target_hex[17] = {0};
|
||||
target_to_hex(target, &target_hex[0]);
|
||||
const block_template_t *bt = job->block_template;
|
||||
|
||||
|
@ -858,18 +874,58 @@ stratum_get_proxy_job_body(char *body, const client_t *client,
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
stratum_get_job_body_ss(char *body, const client_t *client, bool response)
|
||||
{
|
||||
/* job_id, target, pool_wallet, extra_nonce */
|
||||
int json_id = client->json_id;
|
||||
const char *client_id = client->client_id;
|
||||
const job_t *job = &client->active_jobs[0];
|
||||
char job_id[33] = {0};
|
||||
bin_to_hex((const char*)job->id, sizeof(uuid_t), job_id, 32);
|
||||
uint64_t target = job->target;
|
||||
char target_hex[17] = {0};
|
||||
target_to_hex(target, &target_hex[0]);
|
||||
char extra_bin[8];
|
||||
memcpy(extra_bin, &job->extra_nonce, 4);
|
||||
memcpy(extra_bin+4, &instance_id, 4);
|
||||
char extra_hex[17] = {0};
|
||||
bin_to_hex(extra_bin, 8, extra_hex, 16);
|
||||
|
||||
if (response)
|
||||
{
|
||||
snprintf(body, JOB_BODY_MAX, "{\"id\":%d,\"jsonrpc\":\"2.0\","
|
||||
"\"error\":null,\"result\""
|
||||
":{\"id\":\"%.32s\",\"job\":{"
|
||||
"\"job_id\":\"%.32s\",\"target\":\"%s\","
|
||||
"\"extra_nonce\":\"%s\", \"pool_wallet\":\"%s\"},"
|
||||
"\"status\":\"OK\"}}\n",
|
||||
json_id, client_id, job_id, target_hex, extra_hex,
|
||||
config.pool_wallet);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(body, JOB_BODY_MAX, "{\"jsonrpc\":\"2.0\",\"method\":"
|
||||
"\"job\",\"params\""
|
||||
":{\"id\":\"%.32s\",\"job_id\":\"%.32s\","
|
||||
"\"target\":\"%s\","
|
||||
"\"extra_nonce\":\"%s\", \"pool_wallet\":\"%s\"}}\n",
|
||||
client_id, job_id, target_hex, extra_hex, config.pool_wallet);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
stratum_get_job_body(char *body, const client_t *client, bool response)
|
||||
{
|
||||
int json_id = client->json_id;
|
||||
const char *client_id = client->client_id;
|
||||
const job_t *job = &client->active_jobs[0];
|
||||
char job_id[33];
|
||||
char job_id[33] = {0};
|
||||
bin_to_hex((const char*)job->id, sizeof(uuid_t), job_id, 32);
|
||||
const char *blob = job->blob;
|
||||
uint64_t target = job->target;
|
||||
uint64_t height = job->block_template->height;
|
||||
char target_hex[17];
|
||||
char target_hex[17] = {0};
|
||||
target_to_hex(target, &target_hex[0]);
|
||||
|
||||
if (response)
|
||||
|
@ -925,17 +981,28 @@ client_clear_jobs(client_t *client)
|
|||
for (size_t i=0; 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*) ⊂
|
||||
*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);
|
||||
|
||||
|
|
13
src/util.c
13
src/util.c
|
@ -57,11 +57,11 @@ is_hex_string(const char *str)
|
|||
}
|
||||
|
||||
void
|
||||
hex_to_bin(const char *hex, char *bin, size_t bin_size)
|
||||
hex_to_bin(const char *hex, const size_t hex_len,
|
||||
char *bin, const size_t bin_size)
|
||||
{
|
||||
size_t len = strlen(hex);
|
||||
assert(len % 2 == 0);
|
||||
assert(bin_size >= len / 2);
|
||||
assert(hex_len % 2 == 0);
|
||||
assert(bin_size >= hex_len >> 1);
|
||||
const char *ph = hex;
|
||||
char *end = bin + bin_size;
|
||||
while (*ph && bin < end)
|
||||
|
@ -72,7 +72,8 @@ hex_to_bin(const char *hex, char *bin, size_t bin_size)
|
|||
}
|
||||
|
||||
void
|
||||
bin_to_hex(const char *bin, size_t bin_size, char *hex, size_t hex_size)
|
||||
bin_to_hex(const char *bin, const size_t bin_size,
|
||||
char *hex, const size_t hex_size)
|
||||
{
|
||||
assert(bin_size << 1 == hex_size);
|
||||
const char *hex_chars = "0123456789abcdef";
|
||||
|
@ -86,7 +87,7 @@ bin_to_hex(const char *bin, size_t bin_size, char *hex, size_t hex_size)
|
|||
}
|
||||
|
||||
void
|
||||
reverse_bin(char *bin, size_t len)
|
||||
reverse_bin(char *bin, const size_t len)
|
||||
{
|
||||
size_t start = 0;
|
||||
size_t end = len-1;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
85
src/xmr.cpp
85
src/xmr.cpp
|
@ -45,12 +45,16 @@ developers.
|
|||
#include "cryptonote_basic/difficulty.h"
|
||||
#include "crypto/crypto.h"
|
||||
#include "crypto/hash.h"
|
||||
#include "common/base58.h"
|
||||
#include "serialization/binary_utils.h"
|
||||
#include "ringct/rctSigs.h"
|
||||
#include "common/base58.h"
|
||||
#include "string_tools.h"
|
||||
|
||||
#include "xmr.h"
|
||||
|
||||
using namespace epee::string_tools;
|
||||
using namespace cryptonote;
|
||||
using namespace crypto;
|
||||
|
||||
int get_hashing_blob(const char *input, const size_t in_size,
|
||||
char **output, size_t *out_size)
|
||||
|
@ -59,18 +63,17 @@ int get_hashing_blob(const char *input, const size_t in_size,
|
|||
blobdata bd = std::string(input, in_size);
|
||||
if (!parse_and_validate_block_from_blob(bd, b))
|
||||
{
|
||||
printf("Failed to parse block\n");
|
||||
return -1;
|
||||
return XMR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
blobdata blob = get_block_hashing_blob(b);
|
||||
*out_size = blob.length();
|
||||
*output = (char*) malloc(*out_size);
|
||||
memcpy(*output, blob.data(), *out_size);
|
||||
return 0;
|
||||
return XMR_NO_ERROR;
|
||||
}
|
||||
|
||||
int parse_address(const char *input, uint64_t *prefix)
|
||||
int parse_address(const char *input, uint64_t *prefix, char *pub_spend)
|
||||
{
|
||||
uint64_t tag;
|
||||
std::string decoded;
|
||||
|
@ -78,14 +81,80 @@ int parse_address(const char *input, uint64_t *prefix)
|
|||
if (rv)
|
||||
{
|
||||
*prefix = tag;
|
||||
if (pub_spend != NULL)
|
||||
{
|
||||
account_public_address address;
|
||||
::serialization::parse_binary(decoded, address);
|
||||
public_key S = address.m_spend_public_key;
|
||||
memcpy(pub_spend, &S, 32);
|
||||
}
|
||||
}
|
||||
return rv ? 0 : -1;
|
||||
return rv ? XMR_NO_ERROR : XMR_PARSE_ERROR;
|
||||
}
|
||||
|
||||
void get_hash(const char *input, const size_t in_size,
|
||||
char **output, int variant, uint64_t height)
|
||||
{
|
||||
crypto::cn_slow_hash(input, in_size,
|
||||
reinterpret_cast<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;
|
||||
}
|
||||
|
||||
|
|
15
src/xmr.h
15
src/xmr.h
|
@ -39,11 +39,24 @@ developers.
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum xmr_error
|
||||
{
|
||||
XMR_NO_ERROR = 0,
|
||||
XMR_PARSE_ERROR = -1,
|
||||
XMR_VIN_COUNT_ERROR = -2,
|
||||
XMR_VOUT_COUNT_ERROR = -3,
|
||||
XMR_VIN_TYPE_ERROR = -4,
|
||||
XMR_TX_EXTRA_ERROR = -5,
|
||||
XMR_MISMATCH_ERROR = -6
|
||||
};
|
||||
|
||||
int get_hashing_blob(const char *input, const size_t in_size,
|
||||
char **output, size_t *out_size);
|
||||
int parse_address(const char *input, uint64_t *prefix);
|
||||
int parse_address(const char *input, uint64_t *prefix, char *pub_spend);
|
||||
void get_hash(const char *input, const size_t in_size,
|
||||
char **output, int variant, uint64_t height);
|
||||
int validate_block_from_blob(const char *blob_hex, const char *sec_view,
|
||||
const char *pub_spend);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
174
stratum-ss.md
Normal file
174
stratum-ss.md
Normal 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: )
|
Loading…
Reference in a new issue