|
|
@ -405,6 +405,10 @@ static void sharelog(const char*disposition, const struct work*work) |
|
|
|
applog(LOG_ERR, "sharelog fwrite error"); |
|
|
|
applog(LOG_ERR, "sharelog fwrite error"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static char *getwork_req = "{\"method\": \"getwork\", \"params\": [], \"id\":0}\n"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static char *gbt_req = "{\"id\": 0, \"method\": \"getblocktemplate\", \"params\": [{\"capabilities\": [\"coinbasetxn\", \"workid\", \"coinbase/append\"]}]}\n"; |
|
|
|
|
|
|
|
|
|
|
|
/* Return value is ignored if not called from add_pool_details */ |
|
|
|
/* Return value is ignored if not called from add_pool_details */ |
|
|
|
struct pool *add_pool(void) |
|
|
|
struct pool *add_pool(void) |
|
|
|
{ |
|
|
|
{ |
|
|
@ -416,17 +420,17 @@ struct pool *add_pool(void) |
|
|
|
pool->pool_no = pool->prio = total_pools; |
|
|
|
pool->pool_no = pool->prio = total_pools; |
|
|
|
pools = realloc(pools, sizeof(struct pool *) * (total_pools + 2)); |
|
|
|
pools = realloc(pools, sizeof(struct pool *) * (total_pools + 2)); |
|
|
|
pools[total_pools++] = pool; |
|
|
|
pools[total_pools++] = pool; |
|
|
|
if (unlikely(pthread_mutex_init(&pool->pool_lock, NULL))) |
|
|
|
mutex_init(&pool->pool_lock); |
|
|
|
quit(1, "Failed to pthread_mutex_init in add_pool"); |
|
|
|
|
|
|
|
if (unlikely(pthread_cond_init(&pool->cr_cond, NULL))) |
|
|
|
if (unlikely(pthread_cond_init(&pool->cr_cond, NULL))) |
|
|
|
quit(1, "Failed to pthread_cond_init in add_pool"); |
|
|
|
quit(1, "Failed to pthread_cond_init in add_pool"); |
|
|
|
if (unlikely(pthread_mutex_init(&pool->stratum_lock, NULL))) |
|
|
|
mutex_init(&pool->stratum_lock); |
|
|
|
quit(1, "Failed to pthread_mutex_init in add_pool"); |
|
|
|
mutex_init(&pool->gbt_lock); |
|
|
|
INIT_LIST_HEAD(&pool->curlring); |
|
|
|
INIT_LIST_HEAD(&pool->curlring); |
|
|
|
|
|
|
|
|
|
|
|
/* Make sure the pool doesn't think we've been idle since time 0 */ |
|
|
|
/* Make sure the pool doesn't think we've been idle since time 0 */ |
|
|
|
pool->tv_idle.tv_sec = ~0UL; |
|
|
|
pool->tv_idle.tv_sec = ~0UL; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pool->rpc_req = getwork_req; |
|
|
|
pool->rpc_proxy = NULL; |
|
|
|
pool->rpc_proxy = NULL; |
|
|
|
|
|
|
|
|
|
|
|
return pool; |
|
|
|
return pool; |
|
|
@ -1353,37 +1357,286 @@ static void calc_midstate(struct work *work) |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static bool work_decode(const json_t *val, struct work *work) |
|
|
|
/* Generate a GBT coinbase from the existing GBT variables stored. Must be
|
|
|
|
|
|
|
|
* entered under gbt_lock */ |
|
|
|
|
|
|
|
static void __build_gbt_coinbase(struct pool *pool) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
int cbt_len, cal_len, orig_len; |
|
|
|
|
|
|
|
unsigned char *coinbase; |
|
|
|
|
|
|
|
uint8_t *extra_len; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cbt_len = strlen(pool->coinbasetxn) / 2; |
|
|
|
|
|
|
|
pool->coinbase_len = cbt_len + 4; |
|
|
|
|
|
|
|
/* We add 4 bytes of extra data corresponding to nonce2 of stratum */ |
|
|
|
|
|
|
|
cal_len = pool->coinbase_len + 1; |
|
|
|
|
|
|
|
if (cal_len % 4) |
|
|
|
|
|
|
|
cal_len += 4 - (cal_len % 4); |
|
|
|
|
|
|
|
coinbase = calloc(cal_len, 1); |
|
|
|
|
|
|
|
hex2bin(coinbase, pool->coinbasetxn, 42); |
|
|
|
|
|
|
|
extra_len = (uint8_t *)(coinbase + 41); |
|
|
|
|
|
|
|
orig_len = *extra_len; |
|
|
|
|
|
|
|
hex2bin(coinbase + 42, pool->coinbasetxn + 84, orig_len); |
|
|
|
|
|
|
|
memcpy(coinbase + 42 + orig_len, &pool->nonce2, 4); |
|
|
|
|
|
|
|
*extra_len += 4; |
|
|
|
|
|
|
|
hex2bin(coinbase + 42 + *extra_len, pool->coinbasetxn + 84 + (orig_len * 2), cbt_len - orig_len - 42); |
|
|
|
|
|
|
|
pool->nonce2++; |
|
|
|
|
|
|
|
free(pool->gbt_coinbase); |
|
|
|
|
|
|
|
pool->gbt_coinbase = coinbase; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void gen_hash(unsigned char *data, unsigned char *hash, int len); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Process transactions with GBT by storing the binary value of the first
|
|
|
|
|
|
|
|
* transaction, and the hashes of the remaining transactions since these |
|
|
|
|
|
|
|
* remain constant with an altered coinbase when generating work. Must be |
|
|
|
|
|
|
|
* entered under gbt_lock */ |
|
|
|
|
|
|
|
static bool __build_gbt_txns(struct pool *pool, json_t *res_val) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
json_t *txn_array; |
|
|
|
|
|
|
|
bool ret = false; |
|
|
|
|
|
|
|
int i, cal_len; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
free(pool->txn_hashes); |
|
|
|
|
|
|
|
pool->txn_hashes = NULL; |
|
|
|
|
|
|
|
pool->gbt_txns = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
txn_array = json_object_get(res_val, "transactions"); |
|
|
|
|
|
|
|
if (!json_is_array(txn_array)) |
|
|
|
|
|
|
|
goto out; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ret = true; |
|
|
|
|
|
|
|
pool->gbt_txns = json_array_size(txn_array); |
|
|
|
|
|
|
|
if (!pool->gbt_txns) |
|
|
|
|
|
|
|
goto out; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pool->txn_hashes = calloc(32 * (pool->gbt_txns + 1), 1); |
|
|
|
|
|
|
|
if (unlikely(!pool->txn_hashes)) |
|
|
|
|
|
|
|
quit(1, "Failed to calloc txn_hashes in __build_gbt_txns"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < pool->gbt_txns; i++) { |
|
|
|
|
|
|
|
json_t *txn_val = json_object_get(json_array_get(txn_array, i), "data"); |
|
|
|
|
|
|
|
const char *txn = json_string_value(txn_val); |
|
|
|
|
|
|
|
int txn_len = strlen(txn); |
|
|
|
|
|
|
|
unsigned char *txn_bin; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cal_len = txn_len; |
|
|
|
|
|
|
|
if (cal_len % 4) |
|
|
|
|
|
|
|
cal_len += 4 - (cal_len % 4); |
|
|
|
|
|
|
|
txn_bin = calloc(cal_len, 1); |
|
|
|
|
|
|
|
if (unlikely(!txn_bin)) |
|
|
|
|
|
|
|
quit(1, "Failed to calloc txn_bin in __build_gbt_txns"); |
|
|
|
|
|
|
|
if (unlikely(!hex2bin(txn_bin, txn, txn_len / 2))) |
|
|
|
|
|
|
|
quit(1, "Failed to hex2bin txn_bin"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gen_hash(txn_bin, pool->txn_hashes + (32 * i), txn_len / 2); |
|
|
|
|
|
|
|
free(txn_bin); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
out: |
|
|
|
|
|
|
|
return ret; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static unsigned char *__gbt_merkleroot(struct pool *pool) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
unsigned char *merkle_hash; |
|
|
|
|
|
|
|
int i, txns; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
merkle_hash = calloc(32 * (pool->gbt_txns + 2), 1); |
|
|
|
|
|
|
|
if (unlikely(!merkle_hash)) |
|
|
|
|
|
|
|
quit(1, "Failed to calloc merkle_hash in __gbt_merkleroot"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gen_hash(pool->gbt_coinbase, merkle_hash, pool->coinbase_len); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (pool->gbt_txns) |
|
|
|
|
|
|
|
memcpy(merkle_hash + 32, pool->txn_hashes, pool->gbt_txns * 32); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
txns = pool->gbt_txns + 1; |
|
|
|
|
|
|
|
while (txns > 1) { |
|
|
|
|
|
|
|
if (txns % 2) { |
|
|
|
|
|
|
|
memcpy(&merkle_hash[txns * 32], &merkle_hash[(txns - 1) * 32], 32); |
|
|
|
|
|
|
|
txns++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
for (i = 0; i < txns; i += 2){ |
|
|
|
|
|
|
|
unsigned char hashout[32]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gen_hash(merkle_hash + (i * 32), hashout, 64); |
|
|
|
|
|
|
|
memcpy(merkle_hash + (i / 2 * 32), hashout, 32); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
txns /= 2; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return merkle_hash; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void calc_diff(struct work *work, int known); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void gen_gbt_work(struct pool *pool, struct work *work) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
unsigned char *merkleroot; |
|
|
|
|
|
|
|
char *cbhex; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mutex_lock(&pool->gbt_lock); |
|
|
|
|
|
|
|
__build_gbt_coinbase(pool); |
|
|
|
|
|
|
|
merkleroot = __gbt_merkleroot(pool); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
memcpy(work->data, &pool->gbt_version, 4); |
|
|
|
|
|
|
|
memcpy(work->data + 4, pool->previousblockhash, 32); |
|
|
|
|
|
|
|
memcpy(work->data + 4 + 32 + 32, &pool->curtime, 4); |
|
|
|
|
|
|
|
memcpy(work->data + 4 + 32 + 32 + 4, &pool->gbt_bits, 4); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
memcpy(work->target, pool->gbt_target, 32); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cbhex = bin2hex(pool->gbt_coinbase, pool->coinbase_len); |
|
|
|
|
|
|
|
sprintf(work->gbt_coinbase, "%s", cbhex); |
|
|
|
|
|
|
|
free(cbhex); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* For encoding the block data on submission */ |
|
|
|
|
|
|
|
work->gbt_txns = pool->gbt_txns + 1; |
|
|
|
|
|
|
|
mutex_unlock(&pool->gbt_lock); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
memcpy(work->data + 4 + 32, merkleroot, 32); |
|
|
|
|
|
|
|
flip32(work->data + 4 + 32, merkleroot); |
|
|
|
|
|
|
|
free(merkleroot); |
|
|
|
|
|
|
|
memset(work->data + 4 + 32 + 32 + 4 + 4, 0, 4); /* nonce */ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hex2bin(work->data + 4 + 32 + 32 + 4 + 4 + 4, "000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000", 48); |
|
|
|
|
|
|
|
hex2bin(work->hash1, "00000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000010000", 64); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (opt_debug) { |
|
|
|
|
|
|
|
char *header = bin2hex(work->data, 128); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
applog(LOG_DEBUG, "Generated GBT header %s", header); |
|
|
|
|
|
|
|
applog(LOG_DEBUG, "Work coinbase %s", work->gbt_coinbase); |
|
|
|
|
|
|
|
free(header); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
calc_midstate(work); |
|
|
|
|
|
|
|
local_work++; |
|
|
|
|
|
|
|
work->pool = pool; |
|
|
|
|
|
|
|
work->gbt = true; |
|
|
|
|
|
|
|
work->id = total_work++; |
|
|
|
|
|
|
|
work->longpoll = false; |
|
|
|
|
|
|
|
work->getwork_mode = GETWORK_MODE_GBT; |
|
|
|
|
|
|
|
work->work_block = work_block; |
|
|
|
|
|
|
|
calc_diff(work, 0); |
|
|
|
|
|
|
|
gettimeofday(&work->tv_staged, NULL); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool gbt_decode(struct pool *pool, json_t *res_val) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
const char *previousblockhash; |
|
|
|
|
|
|
|
const char *target; |
|
|
|
|
|
|
|
const char *coinbasetxn; |
|
|
|
|
|
|
|
const char *longpollid; |
|
|
|
|
|
|
|
unsigned char hash_swap[32]; |
|
|
|
|
|
|
|
int expires; |
|
|
|
|
|
|
|
int version; |
|
|
|
|
|
|
|
int curtime; |
|
|
|
|
|
|
|
bool submitold; |
|
|
|
|
|
|
|
const char *bits; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
previousblockhash = json_string_value(json_object_get(res_val, "previousblockhash")); |
|
|
|
|
|
|
|
target = json_string_value(json_object_get(res_val, "target")); |
|
|
|
|
|
|
|
coinbasetxn = json_string_value(json_object_get(json_object_get(res_val, "coinbasetxn"), "data")); |
|
|
|
|
|
|
|
longpollid = json_string_value(json_object_get(res_val, "longpollid")); |
|
|
|
|
|
|
|
expires = json_integer_value(json_object_get(res_val, "expires")); |
|
|
|
|
|
|
|
version = json_integer_value(json_object_get(res_val, "version")); |
|
|
|
|
|
|
|
curtime = json_integer_value(json_object_get(res_val, "curtime")); |
|
|
|
|
|
|
|
submitold = json_is_true(json_object_get(res_val, "submitold")); |
|
|
|
|
|
|
|
bits = json_string_value(json_object_get(res_val, "bits")); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!previousblockhash || !target || !coinbasetxn || !longpollid || |
|
|
|
|
|
|
|
!expires || !version || !curtime || !bits) { |
|
|
|
|
|
|
|
applog(LOG_ERR, "JSON failed to decode GBT"); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
applog(LOG_DEBUG, "previousblockhash: %s", previousblockhash); |
|
|
|
|
|
|
|
applog(LOG_DEBUG, "target: %s", target); |
|
|
|
|
|
|
|
applog(LOG_DEBUG, "coinbasetxn: %s", coinbasetxn); |
|
|
|
|
|
|
|
applog(LOG_DEBUG, "longpollid: %s", longpollid); |
|
|
|
|
|
|
|
applog(LOG_DEBUG, "expires: %d", expires); |
|
|
|
|
|
|
|
applog(LOG_DEBUG, "version: %d", version); |
|
|
|
|
|
|
|
applog(LOG_DEBUG, "curtime: %d", curtime); |
|
|
|
|
|
|
|
applog(LOG_DEBUG, "submitold: %s", submitold ? "true" : "false"); |
|
|
|
|
|
|
|
applog(LOG_DEBUG, "bits: %s", bits); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mutex_lock(&pool->gbt_lock); |
|
|
|
|
|
|
|
free(pool->coinbasetxn); |
|
|
|
|
|
|
|
pool->coinbasetxn = strdup(coinbasetxn); |
|
|
|
|
|
|
|
free(pool->longpollid); |
|
|
|
|
|
|
|
pool->longpollid = strdup(longpollid); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hex2bin(hash_swap, previousblockhash, 32); |
|
|
|
|
|
|
|
swap256(pool->previousblockhash, hash_swap); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hex2bin(hash_swap, target, 32); |
|
|
|
|
|
|
|
swab256(pool->gbt_target, hash_swap); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pool->gbt_expires = expires; |
|
|
|
|
|
|
|
pool->gbt_version = htobe32(version); |
|
|
|
|
|
|
|
pool->curtime = htobe32(curtime); |
|
|
|
|
|
|
|
pool->submit_old = submitold; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hex2bin((unsigned char *)&pool->gbt_bits, bits, 4); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
__build_gbt_txns(pool, res_val); |
|
|
|
|
|
|
|
mutex_unlock(&pool->gbt_lock); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool getwork_decode(json_t *res_val, struct work *work) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (unlikely(!jobj_binary(val, "data", work->data, sizeof(work->data), true))) { |
|
|
|
if (unlikely(!jobj_binary(res_val, "data", work->data, sizeof(work->data), true))) { |
|
|
|
applog(LOG_ERR, "JSON inval data"); |
|
|
|
applog(LOG_ERR, "JSON inval data"); |
|
|
|
goto err_out; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!jobj_binary(val, "midstate", work->midstate, sizeof(work->midstate), false)) { |
|
|
|
if (!jobj_binary(res_val, "midstate", work->midstate, sizeof(work->midstate), false)) { |
|
|
|
// Calculate it ourselves
|
|
|
|
// Calculate it ourselves
|
|
|
|
applog(LOG_DEBUG, "Calculating midstate locally"); |
|
|
|
applog(LOG_DEBUG, "Calculating midstate locally"); |
|
|
|
calc_midstate(work); |
|
|
|
calc_midstate(work); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!jobj_binary(val, "hash1", work->hash1, sizeof(work->hash1), false)) { |
|
|
|
if (!jobj_binary(res_val, "hash1", work->hash1, sizeof(work->hash1), false)) { |
|
|
|
// Always the same anyway
|
|
|
|
// Always the same anyway
|
|
|
|
memcpy(work->hash1, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x80\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0", 64); |
|
|
|
memcpy(work->hash1, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x80\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0", 64); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (unlikely(!jobj_binary(val, "target", work->target, sizeof(work->target), true))) { |
|
|
|
if (unlikely(!jobj_binary(res_val, "target", work->target, sizeof(work->target), true))) { |
|
|
|
applog(LOG_ERR, "JSON inval target"); |
|
|
|
applog(LOG_ERR, "JSON inval target"); |
|
|
|
goto err_out; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool work_decode(struct pool *pool, struct work *work, json_t *val) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
json_t *res_val = json_object_get(val, "result"); |
|
|
|
|
|
|
|
bool ret = false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!res_val || json_is_null(res_val)) { |
|
|
|
|
|
|
|
applog(LOG_ERR, "JSON Failed to decode result"); |
|
|
|
|
|
|
|
goto out; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (pool->has_gbt) { |
|
|
|
|
|
|
|
if (unlikely(!gbt_decode(pool, res_val))) |
|
|
|
|
|
|
|
goto out; |
|
|
|
|
|
|
|
gettimeofday(&pool->tv_template, NULL); |
|
|
|
|
|
|
|
work->gbt = true; |
|
|
|
|
|
|
|
ret = true; |
|
|
|
|
|
|
|
goto out; |
|
|
|
|
|
|
|
} else if (unlikely(!getwork_decode(res_val, work))) |
|
|
|
|
|
|
|
goto out; |
|
|
|
|
|
|
|
|
|
|
|
memset(work->hash, 0, sizeof(work->hash)); |
|
|
|
memset(work->hash, 0, sizeof(work->hash)); |
|
|
|
|
|
|
|
|
|
|
|
gettimeofday(&work->tv_staged, NULL); |
|
|
|
gettimeofday(&work->tv_staged, NULL); |
|
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
ret = true; |
|
|
|
|
|
|
|
|
|
|
|
err_out: |
|
|
|
out: |
|
|
|
return false; |
|
|
|
return ret; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
int dev_from_id(int thr_id) |
|
|
|
int dev_from_id(int thr_id) |
|
|
@ -1593,8 +1846,9 @@ static void curses_print_status(void) |
|
|
|
mvwprintw(statuswin, 4, 0, " Connected to multiple pools with%s LP", |
|
|
|
mvwprintw(statuswin, 4, 0, " Connected to multiple pools with%s LP", |
|
|
|
have_longpoll ? "": "out"); |
|
|
|
have_longpoll ? "": "out"); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
mvwprintw(statuswin, 4, 0, " Connected to %s with%s LP as user %s", |
|
|
|
mvwprintw(statuswin, 4, 0, " Connected to %s with%s %s as user %s", |
|
|
|
pool->sockaddr_url, have_longpoll ? "": "out", pool->rpc_user); |
|
|
|
pool->sockaddr_url, have_longpoll ? "": "out", |
|
|
|
|
|
|
|
pool->has_gbt ? "GBT" : "LP", pool->rpc_user); |
|
|
|
} |
|
|
|
} |
|
|
|
wclrtoeol(statuswin); |
|
|
|
wclrtoeol(statuswin); |
|
|
|
mvwprintw(statuswin, 5, 0, " Block: %s... Started: %s Best share: %s ", current_hash, blocktime, best_share); |
|
|
|
mvwprintw(statuswin, 5, 0, " Block: %s... Started: %s Best share: %s ", current_hash, blocktime, best_share); |
|
|
@ -1879,7 +2133,7 @@ share_result(json_t *val, json_t *res, json_t *err, const struct work *work, |
|
|
|
struct pool *pool = work->pool; |
|
|
|
struct pool *pool = work->pool; |
|
|
|
struct cgpu_info *cgpu = thr_info[work->thr_id].cgpu; |
|
|
|
struct cgpu_info *cgpu = thr_info[work->thr_id].cgpu; |
|
|
|
|
|
|
|
|
|
|
|
if (json_is_true(res)) { |
|
|
|
if (json_is_true(res) || (work->gbt && json_is_null(res))) { |
|
|
|
cgpu->accepted++; |
|
|
|
cgpu->accepted++; |
|
|
|
total_accepted++; |
|
|
|
total_accepted++; |
|
|
|
pool->accepted++; |
|
|
|
pool->accepted++; |
|
|
@ -1937,6 +2191,7 @@ share_result(json_t *val, json_t *res, json_t *err, const struct work *work, |
|
|
|
else |
|
|
|
else |
|
|
|
strcpy(where, ""); |
|
|
|
strcpy(where, ""); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!work->gbt) |
|
|
|
res = json_object_get(val, "reject-reason"); |
|
|
|
res = json_object_get(val, "reject-reason"); |
|
|
|
if (res) { |
|
|
|
if (res) { |
|
|
|
const char *reasontmp = json_string_value(res); |
|
|
|
const char *reasontmp = json_string_value(res); |
|
|
@ -2025,7 +2280,7 @@ static bool submit_upstream_work(struct work *work, CURL *curl, bool resubmit) |
|
|
|
{ |
|
|
|
{ |
|
|
|
char *hexstr = NULL; |
|
|
|
char *hexstr = NULL; |
|
|
|
json_t *val, *res, *err; |
|
|
|
json_t *val, *res, *err; |
|
|
|
char s[345], sd[345]; |
|
|
|
char s[1024]; |
|
|
|
bool rc = false; |
|
|
|
bool rc = false; |
|
|
|
int thr_id = work->thr_id; |
|
|
|
int thr_id = work->thr_id; |
|
|
|
struct cgpu_info *cgpu = thr_info[thr_id].cgpu; |
|
|
|
struct cgpu_info *cgpu = thr_info[thr_id].cgpu; |
|
|
@ -2046,14 +2301,39 @@ static bool submit_upstream_work(struct work *work, CURL *curl, bool resubmit) |
|
|
|
hexstr = bin2hex(work->data, sizeof(work->data)); |
|
|
|
hexstr = bin2hex(work->data, sizeof(work->data)); |
|
|
|
|
|
|
|
|
|
|
|
/* build JSON-RPC request */ |
|
|
|
/* build JSON-RPC request */ |
|
|
|
sprintf(s, |
|
|
|
if (work->gbt) { |
|
|
|
"{\"method\": \"getwork\", \"params\": [ \"%s\" ], \"id\":1}\r\n", |
|
|
|
char gbt_block[1024], *varint, *header; |
|
|
|
hexstr); |
|
|
|
unsigned char data[80]; |
|
|
|
sprintf(sd, |
|
|
|
|
|
|
|
"{\"method\": \"getwork\", \"params\": [ \"%s\" ], \"id\":1}", |
|
|
|
flip80(data, work->data); |
|
|
|
hexstr); |
|
|
|
header = bin2hex(data, 80); |
|
|
|
|
|
|
|
sprintf(gbt_block, "%s", header); |
|
|
|
|
|
|
|
free(header); |
|
|
|
|
|
|
|
|
|
|
|
applog(LOG_DEBUG, "DBG: sending %s submit RPC call: %s", pool->rpc_url, sd); |
|
|
|
if (work->gbt_txns < 0xfd) { |
|
|
|
|
|
|
|
uint8_t val = work->gbt_txns; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
varint = bin2hex((const unsigned char *)&val, 1); |
|
|
|
|
|
|
|
} else if (work->gbt_txns <= 0xffff) { |
|
|
|
|
|
|
|
uint16_t val = htole16(work->gbt_txns); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
strcat(gbt_block, "fd"); |
|
|
|
|
|
|
|
varint = bin2hex((const unsigned char *)&val, 2); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
uint32_t val = htole32(work->gbt_txns); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
strcat(gbt_block, "fe"); |
|
|
|
|
|
|
|
varint = bin2hex((const unsigned char *)&val, 4); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
strcat(gbt_block, varint); |
|
|
|
|
|
|
|
free(varint); |
|
|
|
|
|
|
|
strcat(gbt_block, work->gbt_coinbase); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sprintf(s, "{\"id\": 0, \"method\": \"submitblock\", \"params\": [\"%s\", {}]}", gbt_block); |
|
|
|
|
|
|
|
} else |
|
|
|
|
|
|
|
sprintf(s, "{\"method\": \"getwork\", \"params\": [ \"%s\" ], \"id\":1}", hexstr); |
|
|
|
|
|
|
|
applog(LOG_DEBUG, "DBG: sending %s submit RPC call: %s", pool->rpc_url, s); |
|
|
|
|
|
|
|
strcat(s, "\n"); |
|
|
|
|
|
|
|
|
|
|
|
gettimeofday(&tv_submit, NULL); |
|
|
|
gettimeofday(&tv_submit, NULL); |
|
|
|
/* issue JSON-RPC request */ |
|
|
|
/* issue JSON-RPC request */ |
|
|
@ -2066,6 +2346,7 @@ static bool submit_upstream_work(struct work *work, CURL *curl, bool resubmit) |
|
|
|
pool->remotefail_occasions++; |
|
|
|
pool->remotefail_occasions++; |
|
|
|
applog(LOG_WARNING, "Pool %d communication failure, caching submissions", pool->pool_no); |
|
|
|
applog(LOG_WARNING, "Pool %d communication failure, caching submissions", pool->pool_no); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
sleep(5); |
|
|
|
goto out; |
|
|
|
goto out; |
|
|
|
} else if (pool_tclear(pool, &pool->submit_fail)) |
|
|
|
} else if (pool_tclear(pool, &pool->submit_fail)) |
|
|
|
applog(LOG_WARNING, "Pool %d communication resumed, submitting work", pool->pool_no); |
|
|
|
applog(LOG_WARNING, "Pool %d communication resumed, submitting work", pool->pool_no); |
|
|
@ -2158,9 +2439,6 @@ out: |
|
|
|
return rc; |
|
|
|
return rc; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static const char *rpc_req = |
|
|
|
|
|
|
|
"{\"method\": \"getwork\", \"params\": [], \"id\":0}\r\n"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* In balanced mode, the amount of diff1 solutions per pool is monitored as a
|
|
|
|
/* In balanced mode, the amount of diff1 solutions per pool is monitored as a
|
|
|
|
* rolling average per 10 minutes and if pools start getting more, it biases |
|
|
|
* rolling average per 10 minutes and if pools start getting more, it biases |
|
|
|
* away from them to distribute work evenly. The share count is reset to the |
|
|
|
* away from them to distribute work evenly. The share count is reset to the |
|
|
@ -2290,18 +2568,18 @@ static bool get_upstream_work(struct work *work, CURL *curl) |
|
|
|
bool rc = false; |
|
|
|
bool rc = false; |
|
|
|
char *url; |
|
|
|
char *url; |
|
|
|
|
|
|
|
|
|
|
|
applog(LOG_DEBUG, "DBG: sending %s get RPC call: %s", pool->rpc_url, rpc_req); |
|
|
|
applog(LOG_DEBUG, "DBG: sending %s get RPC call: %s", pool->rpc_url, pool->rpc_req); |
|
|
|
|
|
|
|
|
|
|
|
url = pool->rpc_url; |
|
|
|
url = pool->rpc_url; |
|
|
|
|
|
|
|
|
|
|
|
gettimeofday(&(work->tv_getwork), NULL); |
|
|
|
gettimeofday(&(work->tv_getwork), NULL); |
|
|
|
|
|
|
|
|
|
|
|
val = json_rpc_call(curl, url, pool->rpc_userpass, rpc_req, false, |
|
|
|
val = json_rpc_call(curl, url, pool->rpc_userpass, pool->rpc_req, false, |
|
|
|
false, &work->rolltime, pool, false); |
|
|
|
false, &work->rolltime, pool, false); |
|
|
|
pool_stats->getwork_attempts++; |
|
|
|
pool_stats->getwork_attempts++; |
|
|
|
|
|
|
|
|
|
|
|
if (likely(val)) { |
|
|
|
if (likely(val)) { |
|
|
|
rc = work_decode(json_object_get(val, "result"), work); |
|
|
|
rc = work_decode(pool, work, val); |
|
|
|
if (unlikely(!rc)) |
|
|
|
if (unlikely(!rc)) |
|
|
|
applog(LOG_DEBUG, "Failed to decode work in get_upstream_work"); |
|
|
|
applog(LOG_DEBUG, "Failed to decode work in get_upstream_work"); |
|
|
|
} else |
|
|
|
} else |
|
|
@ -2761,6 +3039,29 @@ retry: |
|
|
|
goto out; |
|
|
|
goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (pool->has_gbt) { |
|
|
|
|
|
|
|
while (pool->idle) { |
|
|
|
|
|
|
|
struct pool *altpool = select_pool(true); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sleep(5); |
|
|
|
|
|
|
|
if (altpool != pool) { |
|
|
|
|
|
|
|
wc->pool = altpool; |
|
|
|
|
|
|
|
inc_queued(altpool); |
|
|
|
|
|
|
|
dec_queued(pool); |
|
|
|
|
|
|
|
goto retry; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
ret_work = make_work(); |
|
|
|
|
|
|
|
gen_gbt_work(pool, ret_work); |
|
|
|
|
|
|
|
if (unlikely(!stage_work(ret_work))) { |
|
|
|
|
|
|
|
applog(LOG_ERR, "Failed to stage gbt work in get_work_thread"); |
|
|
|
|
|
|
|
kill_work(); |
|
|
|
|
|
|
|
free(ret_work); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
dec_queued(pool); |
|
|
|
|
|
|
|
goto out; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (clone_available()) { |
|
|
|
if (clone_available()) { |
|
|
|
dec_queued(pool); |
|
|
|
dec_queued(pool); |
|
|
|
goto out; |
|
|
|
goto out; |
|
|
@ -2941,11 +3242,13 @@ static void *submit_work_thread(void *userdata) |
|
|
|
applog(LOG_WARNING, "Pool %d communication resumed, submitting work", pool->pool_no); |
|
|
|
applog(LOG_WARNING, "Pool %d communication resumed, submitting work", pool->pool_no); |
|
|
|
applog(LOG_DEBUG, "Successfully submitted, adding to stratum_shares db"); |
|
|
|
applog(LOG_DEBUG, "Successfully submitted, adding to stratum_shares db"); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
applog(LOG_INFO, "Failed to submit stratum share"); |
|
|
|
applog(LOG_WARNING, "Failed to submit stratum share to pool %d", pool->pool_no); |
|
|
|
mutex_lock(&sshare_lock); |
|
|
|
mutex_lock(&sshare_lock); |
|
|
|
HASH_DEL(stratum_shares, sshare); |
|
|
|
HASH_DEL(stratum_shares, sshare); |
|
|
|
mutex_unlock(&sshare_lock); |
|
|
|
mutex_unlock(&sshare_lock); |
|
|
|
free(sshare); |
|
|
|
free(sshare); |
|
|
|
|
|
|
|
pool->stale_shares++; |
|
|
|
|
|
|
|
total_stale++; |
|
|
|
|
|
|
|
|
|
|
|
if (!pool_tset(pool, &pool->submit_fail)) { |
|
|
|
if (!pool_tset(pool, &pool->submit_fail)) { |
|
|
|
total_ro++; |
|
|
|
total_ro++; |
|
|
@ -3016,6 +3319,8 @@ static struct pool *priority_pool(int choice) |
|
|
|
return ret; |
|
|
|
return ret; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool pool_active(struct pool *pool, bool pinging); |
|
|
|
|
|
|
|
|
|
|
|
void switch_pools(struct pool *selected) |
|
|
|
void switch_pools(struct pool *selected) |
|
|
|
{ |
|
|
|
{ |
|
|
|
struct pool *pool, *last_pool; |
|
|
|
struct pool *pool, *last_pool; |
|
|
@ -3084,8 +3389,13 @@ void switch_pools(struct pool *selected) |
|
|
|
if (opt_fail_only) |
|
|
|
if (opt_fail_only) |
|
|
|
pool_tset(pool, &pool->lagging); |
|
|
|
pool_tset(pool, &pool->lagging); |
|
|
|
|
|
|
|
|
|
|
|
if (pool != last_pool) |
|
|
|
if (pool != last_pool) { |
|
|
|
applog(LOG_WARNING, "Switching to %s", pool->rpc_url); |
|
|
|
applog(LOG_WARNING, "Switching to %s", pool->rpc_url); |
|
|
|
|
|
|
|
/* Get a fresh block template since we may not have an up to
|
|
|
|
|
|
|
|
* date one */ |
|
|
|
|
|
|
|
if (pool->has_gbt) |
|
|
|
|
|
|
|
pool_active(pool, true); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
mutex_lock(&lp_lock); |
|
|
|
mutex_lock(&lp_lock); |
|
|
|
pthread_cond_broadcast(&lp_cond); |
|
|
|
pthread_cond_broadcast(&lp_cond); |
|
|
@ -3274,8 +3584,8 @@ static bool test_work_current(struct work *work) |
|
|
|
|
|
|
|
|
|
|
|
if (!work->stratum) { |
|
|
|
if (!work->stratum) { |
|
|
|
if (work->longpoll) { |
|
|
|
if (work->longpoll) { |
|
|
|
applog(LOG_NOTICE, "LONGPOLL from pool %d detected new block", |
|
|
|
applog(LOG_NOTICE, "%sLONGPOLL from pool %d detected new block", |
|
|
|
work->pool->pool_no); |
|
|
|
work->gbt ? "GBT " : "", work->pool->pool_no); |
|
|
|
work->longpoll = false; |
|
|
|
work->longpoll = false; |
|
|
|
} else if (have_longpoll) |
|
|
|
} else if (have_longpoll) |
|
|
|
applog(LOG_NOTICE, "New block detected on network before longpoll"); |
|
|
|
applog(LOG_NOTICE, "New block detected on network before longpoll"); |
|
|
@ -3286,8 +3596,8 @@ static bool test_work_current(struct work *work) |
|
|
|
} else if (work->longpoll) { |
|
|
|
} else if (work->longpoll) { |
|
|
|
work->longpoll = false; |
|
|
|
work->longpoll = false; |
|
|
|
if (work->pool == current_pool()) { |
|
|
|
if (work->pool == current_pool()) { |
|
|
|
applog(LOG_NOTICE, "LONGPOLL from pool %d requested work restart", |
|
|
|
applog(LOG_NOTICE, "%sLONGPOLL from pool %d requested work restart", |
|
|
|
work->pool->pool_no); |
|
|
|
work->gbt ? "GBT " : "", work->pool->pool_no); |
|
|
|
work_block++; |
|
|
|
work_block++; |
|
|
|
restart_threads(); |
|
|
|
restart_threads(); |
|
|
|
} |
|
|
|
} |
|
|
@ -4246,7 +4556,7 @@ static void stratum_share_result(json_t *val, json_t *res_val, json_t *err_val, |
|
|
|
|
|
|
|
|
|
|
|
/* Parses stratum json responses and tries to find the id that the request
|
|
|
|
/* Parses stratum json responses and tries to find the id that the request
|
|
|
|
* matched to and treat it accordingly. */ |
|
|
|
* matched to and treat it accordingly. */ |
|
|
|
static bool parse_stratum_response(char *s) |
|
|
|
static bool parse_stratum_response(struct pool *pool, char *s) |
|
|
|
{ |
|
|
|
{ |
|
|
|
json_t *val = NULL, *err_val, *res_val, *id_val; |
|
|
|
json_t *val = NULL, *err_val, *res_val, *id_val; |
|
|
|
struct stratum_share *sshare; |
|
|
|
struct stratum_share *sshare; |
|
|
@ -4287,9 +4597,9 @@ static bool parse_stratum_response(char *s) |
|
|
|
mutex_unlock(&sshare_lock); |
|
|
|
mutex_unlock(&sshare_lock); |
|
|
|
if (!sshare) { |
|
|
|
if (!sshare) { |
|
|
|
if (json_is_true(res_val)) |
|
|
|
if (json_is_true(res_val)) |
|
|
|
applog(LOG_NOTICE, "Accepted untracked stratum share"); |
|
|
|
applog(LOG_NOTICE, "Accepted untracked stratum share from pool %d", pool->pool_no); |
|
|
|
else |
|
|
|
else |
|
|
|
applog(LOG_NOTICE, "Rejected untracked stratum share"); |
|
|
|
applog(LOG_NOTICE, "Rejected untracked stratum share from pool %d", pool->pool_no); |
|
|
|
goto out; |
|
|
|
goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
stratum_share_result(val, res_val, err_val, sshare); |
|
|
|
stratum_share_result(val, res_val, err_val, sshare); |
|
|
@ -4305,6 +4615,28 @@ out: |
|
|
|
|
|
|
|
|
|
|
|
static void pool_resus(struct pool *pool); |
|
|
|
static void pool_resus(struct pool *pool); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void clear_stratum_shares(struct pool *pool) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
struct stratum_share *sshare, *tmpshare; |
|
|
|
|
|
|
|
int cleared = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mutex_lock(&sshare_lock); |
|
|
|
|
|
|
|
HASH_ITER(hh, stratum_shares, sshare, tmpshare) { |
|
|
|
|
|
|
|
if (sshare->work.pool == pool) { |
|
|
|
|
|
|
|
HASH_DEL(stratum_shares, sshare); |
|
|
|
|
|
|
|
free(sshare); |
|
|
|
|
|
|
|
cleared++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
mutex_unlock(&sshare_lock); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (cleared) { |
|
|
|
|
|
|
|
applog(LOG_WARNING, "Lost %d shares due to stratum disconnect on pool %d", cleared, pool->pool_no); |
|
|
|
|
|
|
|
pool->stale_shares++; |
|
|
|
|
|
|
|
total_stale++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* One stratum thread per pool that has stratum waits on the socket checking
|
|
|
|
/* One stratum thread per pool that has stratum waits on the socket checking
|
|
|
|
* for new messages and for the integrity of the socket connection. We reset |
|
|
|
* for new messages and for the integrity of the socket connection. We reset |
|
|
|
* the connection based on the integrity of the receive side only as the send |
|
|
|
* the connection based on the integrity of the receive side only as the send |
|
|
@ -4341,6 +4673,11 @@ static void *stratum_thread(void *userdata) |
|
|
|
pool->getfail_occasions++; |
|
|
|
pool->getfail_occasions++; |
|
|
|
total_go++; |
|
|
|
total_go++; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* If the socket to our stratum pool disconnects, all
|
|
|
|
|
|
|
|
* tracked submitted shares are lost and we will leak |
|
|
|
|
|
|
|
* the memory if we don't discard their records. */ |
|
|
|
|
|
|
|
clear_stratum_shares(pool); |
|
|
|
|
|
|
|
|
|
|
|
if (initiate_stratum(pool) && auth_stratum(pool)) |
|
|
|
if (initiate_stratum(pool) && auth_stratum(pool)) |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
@ -4356,7 +4693,7 @@ static void *stratum_thread(void *userdata) |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!parse_method(pool, s) && !parse_stratum_response(s)) |
|
|
|
if (!parse_method(pool, s) && !parse_stratum_response(pool, s)) |
|
|
|
applog(LOG_INFO, "Unknown stratum msg: %s", s); |
|
|
|
applog(LOG_INFO, "Unknown stratum msg: %s", s); |
|
|
|
free(s); |
|
|
|
free(s); |
|
|
|
if (pool->swork.clean) { |
|
|
|
if (pool->swork.clean) { |
|
|
@ -4412,6 +4749,9 @@ static bool pool_active(struct pool *pool, bool pinging) |
|
|
|
CURL *curl; |
|
|
|
CURL *curl; |
|
|
|
int rolltime; |
|
|
|
int rolltime; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (pool->has_gbt) |
|
|
|
|
|
|
|
applog(LOG_DEBUG, "Retrieving block template from pool %s", pool->rpc_url); |
|
|
|
|
|
|
|
else |
|
|
|
applog(LOG_INFO, "Testing pool %s", pool->rpc_url); |
|
|
|
applog(LOG_INFO, "Testing pool %s", pool->rpc_url); |
|
|
|
|
|
|
|
|
|
|
|
/* This is the central point we activate stratum when we can */ |
|
|
|
/* This is the central point we activate stratum when we can */ |
|
|
@ -4439,9 +4779,47 @@ retry_stratum: |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Probe for GBT support on first pass */ |
|
|
|
|
|
|
|
if (!pool->probed && !opt_fix_protocol) { |
|
|
|
|
|
|
|
applog(LOG_DEBUG, "Probing for GBT support"); |
|
|
|
|
|
|
|
val = json_rpc_call(curl, pool->rpc_url, pool->rpc_userpass, |
|
|
|
|
|
|
|
gbt_req, true, false, &rolltime, pool, false); |
|
|
|
|
|
|
|
if (val) { |
|
|
|
|
|
|
|
json_t *res_val, *mutables; |
|
|
|
|
|
|
|
int i, mutsize = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
res_val = json_object_get(val, "result"); |
|
|
|
|
|
|
|
if (res_val) { |
|
|
|
|
|
|
|
mutables = json_object_get(res_val, "mutable"); |
|
|
|
|
|
|
|
mutsize = json_array_size(mutables); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < mutsize; i++) { |
|
|
|
|
|
|
|
json_t *arrval = json_array_get(mutables, i); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (json_is_string(arrval)) { |
|
|
|
|
|
|
|
const char *mutable = json_string_value(arrval); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Only use GBT if it supports coinbase append */ |
|
|
|
|
|
|
|
if (!strncasecmp(mutable, "coinbase/append", 15)) { |
|
|
|
|
|
|
|
pool->has_gbt = true; |
|
|
|
|
|
|
|
pool->rpc_req = gbt_req; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
json_decref(val); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (pool->has_gbt) |
|
|
|
|
|
|
|
applog(LOG_DEBUG, "GBT coinbase append support found, switching to GBT protocol"); |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
applog(LOG_DEBUG, "No GBT coinbase append support found, using getwork protocol"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
gettimeofday(&tv_getwork, NULL); |
|
|
|
gettimeofday(&tv_getwork, NULL); |
|
|
|
val = json_rpc_call(curl, pool->rpc_url, pool->rpc_userpass, rpc_req, |
|
|
|
val = json_rpc_call(curl, pool->rpc_url, pool->rpc_userpass, |
|
|
|
true, false, &rolltime, pool, false); |
|
|
|
pool->rpc_req, true, false, &rolltime, pool, false); |
|
|
|
gettimeofday(&tv_getwork_reply, NULL); |
|
|
|
gettimeofday(&tv_getwork_reply, NULL); |
|
|
|
|
|
|
|
|
|
|
|
/* Detect if a http getwork pool has an X-Stratum header at startup,
|
|
|
|
/* Detect if a http getwork pool has an X-Stratum header at startup,
|
|
|
@ -4460,7 +4838,7 @@ retry_stratum: |
|
|
|
struct work *work = make_work(); |
|
|
|
struct work *work = make_work(); |
|
|
|
bool rc; |
|
|
|
bool rc; |
|
|
|
|
|
|
|
|
|
|
|
rc = work_decode(json_object_get(val, "result"), work); |
|
|
|
rc = work_decode(pool, work, val); |
|
|
|
if (rc) { |
|
|
|
if (rc) { |
|
|
|
applog(LOG_DEBUG, "Successfully retrieved and deciphered work from pool %u %s", |
|
|
|
applog(LOG_DEBUG, "Successfully retrieved and deciphered work from pool %u %s", |
|
|
|
pool->pool_no, pool->rpc_url); |
|
|
|
pool->pool_no, pool->rpc_url); |
|
|
@ -4635,7 +5013,15 @@ static bool reuse_work(struct work *work, struct pool *pool) |
|
|
|
if (!pool->stratum_active) |
|
|
|
if (!pool->stratum_active) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
applog(LOG_DEBUG, "Reusing stratum work"); |
|
|
|
applog(LOG_DEBUG, "Reusing stratum work"); |
|
|
|
gen_stratum_work(pool, work);; |
|
|
|
gen_stratum_work(pool, work); |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (pool->has_gbt) { |
|
|
|
|
|
|
|
if (pool->idle) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
applog(LOG_DEBUG, "Reusing GBT work"); |
|
|
|
|
|
|
|
gen_gbt_work(pool, work); |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -4827,6 +5213,8 @@ static void get_work(struct work *work, struct thr_info *thr, const int thr_id) |
|
|
|
goto out; |
|
|
|
goto out; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Reset these flags in case we switch pools with these work structs */ |
|
|
|
|
|
|
|
work->stratum = work->gbt = false; |
|
|
|
retry: |
|
|
|
retry: |
|
|
|
pool = current_pool(); |
|
|
|
pool = current_pool(); |
|
|
|
|
|
|
|
|
|
|
@ -4941,7 +5329,7 @@ static bool hashtest(struct thr_info *thr, struct work *work) |
|
|
|
unsigned char hash2[32]; |
|
|
|
unsigned char hash2[32]; |
|
|
|
uint32_t *hash2_32 = (uint32_t *)hash2; |
|
|
|
uint32_t *hash2_32 = (uint32_t *)hash2; |
|
|
|
struct pool *pool = work->pool; |
|
|
|
struct pool *pool = work->pool; |
|
|
|
int i, diff; |
|
|
|
int i; |
|
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < 80 / 4; i++) |
|
|
|
for (i = 0; i < 80 / 4; i++) |
|
|
|
swap32[i] = swab32(data32[i]); |
|
|
|
swap32[i] = swab32(data32[i]); |
|
|
@ -4967,11 +5355,15 @@ static bool hashtest(struct thr_info *thr, struct work *work) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (work->stratum) { |
|
|
|
if (work->stratum) { |
|
|
|
|
|
|
|
int diff; |
|
|
|
|
|
|
|
|
|
|
|
mutex_lock(&pool->pool_lock); |
|
|
|
mutex_lock(&pool->pool_lock); |
|
|
|
diff = pool->swork.diff; |
|
|
|
diff = pool->swork.diff; |
|
|
|
mutex_unlock(&pool->pool_lock); |
|
|
|
mutex_unlock(&pool->pool_lock); |
|
|
|
|
|
|
|
|
|
|
|
if (unlikely(work->sdiff != diff)) { |
|
|
|
/* Retarget share only if pool diff has dropped since we
|
|
|
|
|
|
|
|
* generated this work */ |
|
|
|
|
|
|
|
if (unlikely(work->sdiff > diff)) { |
|
|
|
applog(LOG_DEBUG, "Share needs retargetting to match pool"); |
|
|
|
applog(LOG_DEBUG, "Share needs retargetting to match pool"); |
|
|
|
set_work_target(work, diff); |
|
|
|
set_work_target(work, diff); |
|
|
|
} |
|
|
|
} |
|
|
@ -5237,7 +5629,7 @@ static void convert_to_work(json_t *val, int rolltime, struct pool *pool, struct |
|
|
|
|
|
|
|
|
|
|
|
work = make_work(); |
|
|
|
work = make_work(); |
|
|
|
|
|
|
|
|
|
|
|
rc = work_decode(json_object_get(val, "result"), work); |
|
|
|
rc = work_decode(pool, work, val); |
|
|
|
if (unlikely(!rc)) { |
|
|
|
if (unlikely(!rc)) { |
|
|
|
applog(LOG_ERR, "Could not convert longpoll data to work"); |
|
|
|
applog(LOG_ERR, "Could not convert longpoll data to work"); |
|
|
|
free_work(work); |
|
|
|
free_work(work); |
|
|
@ -5245,15 +5637,18 @@ static void convert_to_work(json_t *val, int rolltime, struct pool *pool, struct |
|
|
|
} |
|
|
|
} |
|
|
|
work->pool = pool; |
|
|
|
work->pool = pool; |
|
|
|
work->rolltime = rolltime; |
|
|
|
work->rolltime = rolltime; |
|
|
|
work->longpoll = true; |
|
|
|
|
|
|
|
memcpy(&(work->tv_getwork), tv_lp, sizeof(struct timeval)); |
|
|
|
memcpy(&(work->tv_getwork), tv_lp, sizeof(struct timeval)); |
|
|
|
memcpy(&(work->tv_getwork_reply), tv_lp_reply, sizeof(struct timeval)); |
|
|
|
memcpy(&(work->tv_getwork_reply), tv_lp_reply, sizeof(struct timeval)); |
|
|
|
work->getwork_mode = GETWORK_MODE_LP; |
|
|
|
|
|
|
|
calc_diff(work, 0); |
|
|
|
calc_diff(work, 0); |
|
|
|
|
|
|
|
|
|
|
|
if (pool->enabled == POOL_REJECTING) |
|
|
|
if (pool->enabled == POOL_REJECTING) |
|
|
|
work->mandatory = true; |
|
|
|
work->mandatory = true; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (pool->has_gbt) |
|
|
|
|
|
|
|
gen_gbt_work(pool, work); |
|
|
|
|
|
|
|
work->longpoll = true; |
|
|
|
|
|
|
|
work->getwork_mode = GETWORK_MODE_LP; |
|
|
|
|
|
|
|
|
|
|
|
/* We'll be checking this work item twice, but we already know it's
|
|
|
|
/* We'll be checking this work item twice, but we already know it's
|
|
|
|
* from a new block so explicitly force the new block detection now |
|
|
|
* from a new block so explicitly force the new block detection now |
|
|
|
* rather than waiting for it to hit the stage thread. This also |
|
|
|
* rather than waiting for it to hit the stage thread. This also |
|
|
@ -5286,7 +5681,7 @@ static struct pool *select_longpoll_pool(struct pool *cp) |
|
|
|
{ |
|
|
|
{ |
|
|
|
int i; |
|
|
|
int i; |
|
|
|
|
|
|
|
|
|
|
|
if (cp->hdr_path) |
|
|
|
if (cp->hdr_path || cp->has_gbt) |
|
|
|
return cp; |
|
|
|
return cp; |
|
|
|
for (i = 0; i < total_pools; i++) { |
|
|
|
for (i = 0; i < total_pools; i++) { |
|
|
|
struct pool *pool = pools[i]; |
|
|
|
struct pool *pool = pools[i]; |
|
|
@ -5321,6 +5716,8 @@ static void *longpoll_thread(void *userdata) |
|
|
|
struct timeval start, reply, end; |
|
|
|
struct timeval start, reply, end; |
|
|
|
CURL *curl = NULL; |
|
|
|
CURL *curl = NULL; |
|
|
|
int failures = 0; |
|
|
|
int failures = 0; |
|
|
|
|
|
|
|
char lpreq[1024]; |
|
|
|
|
|
|
|
char *lp_url; |
|
|
|
int rolltime; |
|
|
|
int rolltime; |
|
|
|
|
|
|
|
|
|
|
|
curl = curl_easy_init(); |
|
|
|
curl = curl_easy_init(); |
|
|
@ -5344,10 +5741,18 @@ retry_pool: |
|
|
|
|
|
|
|
|
|
|
|
wait_lpcurrent(cp); |
|
|
|
wait_lpcurrent(cp); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (pool->has_gbt) { |
|
|
|
|
|
|
|
lp_url = pool->rpc_url; |
|
|
|
|
|
|
|
applog(LOG_WARNING, "GBT longpoll ID activated for %s", lp_url); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
strcpy(lpreq, getwork_req); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lp_url = pool->lp_url; |
|
|
|
if (cp == pool) |
|
|
|
if (cp == pool) |
|
|
|
applog(LOG_WARNING, "Long-polling activated for %s", pool->lp_url); |
|
|
|
applog(LOG_WARNING, "Long-polling activated for %s", lp_url); |
|
|
|
else |
|
|
|
else |
|
|
|
applog(LOG_WARNING, "Long-polling activated for pool %s via %s", cp->rpc_url, pool->lp_url); |
|
|
|
applog(LOG_WARNING, "Long-polling activated for pool %s via %s", cp->rpc_url, lp_url); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
while (42) { |
|
|
|
while (42) { |
|
|
|
json_t *val, *soval; |
|
|
|
json_t *val, *soval; |
|
|
@ -5356,13 +5761,23 @@ retry_pool: |
|
|
|
|
|
|
|
|
|
|
|
gettimeofday(&start, NULL); |
|
|
|
gettimeofday(&start, NULL); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Update the longpollid every time, but do it under lock to
|
|
|
|
|
|
|
|
* avoid races */ |
|
|
|
|
|
|
|
if (pool->has_gbt) { |
|
|
|
|
|
|
|
mutex_lock(&pool->gbt_lock); |
|
|
|
|
|
|
|
sprintf(lpreq, "{\"id\": 0, \"method\": \"getblocktemplate\", \"params\": " |
|
|
|
|
|
|
|
"[{\"capabilities\": [\"coinbasetxn\", \"workid\", \"coinbase/append\"], " |
|
|
|
|
|
|
|
"\"longpollid\": \"%s\"}]}\n", pool->longpollid); |
|
|
|
|
|
|
|
mutex_unlock(&pool->gbt_lock); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* Longpoll connections can be persistent for a very long time
|
|
|
|
/* Longpoll connections can be persistent for a very long time
|
|
|
|
* and any number of issues could have come up in the meantime |
|
|
|
* and any number of issues could have come up in the meantime |
|
|
|
* so always establish a fresh connection instead of relying on |
|
|
|
* so always establish a fresh connection instead of relying on |
|
|
|
* a persistent one. */ |
|
|
|
* a persistent one. */ |
|
|
|
curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1); |
|
|
|
curl_easy_setopt(curl, CURLOPT_FRESH_CONNECT, 1); |
|
|
|
val = json_rpc_call(curl, pool->lp_url, pool->rpc_userpass, rpc_req, |
|
|
|
val = json_rpc_call(curl, lp_url, pool->rpc_userpass, |
|
|
|
false, true, &rolltime, pool, false); |
|
|
|
lpreq, false, true, &rolltime, pool, false); |
|
|
|
|
|
|
|
|
|
|
|
gettimeofday(&reply, NULL); |
|
|
|
gettimeofday(&reply, NULL); |
|
|
|
|
|
|
|
|
|
|
@ -5384,7 +5799,7 @@ retry_pool: |
|
|
|
if (end.tv_sec - start.tv_sec > 30) |
|
|
|
if (end.tv_sec - start.tv_sec > 30) |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
if (failures == 1) |
|
|
|
if (failures == 1) |
|
|
|
applog(LOG_WARNING, "longpoll failed for %s, retrying every 30s", pool->lp_url); |
|
|
|
applog(LOG_WARNING, "longpoll failed for %s, retrying every 30s", lp_url); |
|
|
|
sleep(30); |
|
|
|
sleep(30); |
|
|
|
} |
|
|
|
} |
|
|
|
if (pool != cp) { |
|
|
|
if (pool != cp) { |
|
|
@ -5460,8 +5875,18 @@ static void *watchpool_thread(void __maybe_unused *userdata) |
|
|
|
if (pool->enabled == POOL_DISABLED || pool->has_stratum) |
|
|
|
if (pool->enabled == POOL_DISABLED || pool->has_stratum) |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Apart from longpollid comms, we retrieve a fresh
|
|
|
|
|
|
|
|
* template if more than 30 seconds has elapsed since |
|
|
|
|
|
|
|
* the last one to keep the data current and as a test |
|
|
|
|
|
|
|
* for when the pool dies. */ |
|
|
|
|
|
|
|
if (!pool->idle && pool->has_gbt && pool == current_pool() && |
|
|
|
|
|
|
|
now.tv_sec - pool->tv_template.tv_sec > 60) { |
|
|
|
|
|
|
|
if (!pool_active(pool, true)) |
|
|
|
|
|
|
|
pool_died(pool); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* Test pool is idle once every minute */ |
|
|
|
/* Test pool is idle once every minute */ |
|
|
|
if (pool->idle && now.tv_sec - pool->tv_idle.tv_sec > 60) { |
|
|
|
if (pool->idle && now.tv_sec - pool->tv_idle.tv_sec > 30) { |
|
|
|
gettimeofday(&pool->tv_idle, NULL); |
|
|
|
gettimeofday(&pool->tv_idle, NULL); |
|
|
|
if (pool_active(pool, true) && pool_tclear(pool, &pool->idle)) |
|
|
|
if (pool_active(pool, true) && pool_tclear(pool, &pool->idle)) |
|
|
|
pool_resus(pool); |
|
|
|
pool_resus(pool); |
|
|
@ -6436,6 +6861,7 @@ int main(int argc, char *argv[]) |
|
|
|
for (i = 0; i < total_pools; i++) { |
|
|
|
for (i = 0; i < total_pools; i++) { |
|
|
|
struct pool *pool = pools[i]; |
|
|
|
struct pool *pool = pools[i]; |
|
|
|
if (pool_active(pool, false)) { |
|
|
|
if (pool_active(pool, false)) { |
|
|
|
|
|
|
|
pool_tclear(pool, &pool->idle); |
|
|
|
if (!currentpool) |
|
|
|
if (!currentpool) |
|
|
|
currentpool = pool; |
|
|
|
currentpool = pool; |
|
|
|
applog(LOG_INFO, "Pool %d %s active", pool->pool_no, pool->rpc_url); |
|
|
|
applog(LOG_INFO, "Pool %d %s active", pool->pool_no, pool->rpc_url); |
|
|
|