Professional Documents
Culture Documents
#include "config.h"
#ifdef HAVE_CURSES
#include <curses.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <math.h>
#include <stdarg.h>
#include <assert.h>
#include <signal.h>
#include <limits.h>
#ifdef USE_USBUTILS
#include <semaphore.h>
#endif
#ifdef USE_LIBSYSTEMD
#include <systemd/sd-daemon.h>
#endif
#include <sys/stat.h>
#include <sys/types.h>
#ifndef WIN32
#include <sys/resource.h>
#else
#include <winsock2.h>
#include <windows.h>
#endif
#include <ccan/opt/opt.h>
#include <jansson.h>
#ifdef HAVE_LIBCURL
#include <curl/curl.h>
#else
char *curly = ":D";
#endif
#include <libgen.h>
#include <sha2.h>
#include "compat.h"
#include "miner.h"
#include "bench_block.h"
#ifdef USE_USBUTILS
#include "usbutils.h"
#endif
#ifdef USE_AVALON
#include "driver-avalon.h"
#endif
#ifdef USE_AVALON2
#include "driver-avalon2.h"
#endif
#ifdef USE_AVALON4
#include "driver-avalon4.h"
#endif
#ifdef USE_AVALON7
#include "driver-avalon7.h"
#endif
#ifdef USE_AVALON_MINER
#include "driver-avalon-miner.h"
#endif
#ifdef USE_BFLSC
#include "driver-bflsc.h"
#endif
#ifdef USE_SP10
#include "driver-spondoolies-sp10.h"
#endif
#ifdef USE_SP30
#include "driver-spondoolies-sp30.h"
#endif
#ifdef USE_BLOCK_ERUPTER
#include "driver-blockerupter.h"
#endif
#ifdef USE_BITFURY
#include "driver-bitfury.h"
#endif
#ifdef USE_BITFURY16
#include "driver-bitfury16.h"
#endif
#ifdef USE_COINTERRA
#include "driver-cointerra.h"
#endif
#ifdef USE_HASHFAST
#include "driver-hashfast.h"
#endif
bool opt_work_update;
bool opt_protocol;
static struct benchfile_layout {
int length;
char *name;
} benchfile_data[] = {
{ 1, "Version" },
{ 64, "MerkleRoot" },
{ 64, "PrevHash" },
{ 8, "DifficultyBits" },
{ 10, "NonceTime" } // 10 digits
};
enum benchwork {
BENCHWORK_VERSION = 0,
BENCHWORK_MERKLEROOT,
BENCHWORK_PREVHASH,
BENCHWORK_DIFFBITS,
BENCHWORK_NONCETIME,
BENCHWORK_COUNT
};
#ifdef HAVE_LIBCURL
static char *opt_btc_address;
static char *opt_btc_sig;
#endif
struct pool *opt_btcd;
static char *opt_benchfile;
static bool opt_benchfile_display;
static FILE *benchfile_in;
static int benchfile_line;
static int benchfile_work;
static bool opt_benchmark;
bool have_longpoll;
bool want_per_device_stats;
bool use_syslog;
bool opt_quiet;
bool opt_realquiet;
bool opt_loginput;
bool opt_compact;
bool opt_decode;
const int opt_cutofftemp = 95;
int opt_log_interval = 5;
static const int max_queue = 1;
const int max_scantime = 60;
const int max_expiry = 600;
uint64_t global_hashrate;
unsigned long global_quota_gcd = 1;
time_t last_getwork;
int opt_pool_fallback = 120;
#if defined(USE_USBUTILS)
int nDevs;
#endif
bool opt_restart = true;
bool opt_nogpu;
#ifdef USE_USBUTILS
char *opt_usb_select = NULL;
int opt_usbdump = -1;
bool opt_usb_list_all;
cgsem_t usb_resource_sem;
static pthread_t usb_poll_thread;
static bool usb_polling;
static bool polling_usb;
static bool usb_reinit;
#endif
char *opt_kernel_path;
char *cgminer_path;
bool opt_gen_stratum_work;
#if defined(USE_BITFORCE)
bool opt_bfl_noncerange;
#endif
#define QUIET (opt_quiet || opt_realquiet)
#if LOCK_TRACKING
pthread_mutex_t lockstat_lock;
#endif
pthread_mutex_t hash_lock;
static pthread_mutex_t *stgd_lock;
pthread_mutex_t console_lock;
cglock_t ch_lock;
static pthread_rwlock_t blk_lock;
static pthread_mutex_t sshare_lock;
pthread_rwlock_t netacc_lock;
pthread_rwlock_t mining_thr_lock;
pthread_rwlock_t devices_lock;
pthread_mutex_t restart_lock;
pthread_cond_t restart_cond;
pthread_cond_t gws_cond;
cglock_t control_lock;
pthread_mutex_t stats_lock;
int hw_errors;
int64_t total_accepted, total_rejected, total_diff1;
int64_t total_getworks, total_stale, total_discarded;
double total_diff_accepted, total_diff_rejected, total_diff_stale;
static int staged_rollable;
unsigned int new_blocks;
static unsigned int work_block;
unsigned int found_blocks;
static
#ifndef HAVE_CURSES
const
#endif
bool curses_active;
/* Protected by ch_lock */
char current_hash[68];
static char prev_block[12];
static char current_block[32];
struct block {
char hash[68];
UT_hash_handle hh;
int block_no;
};
int swork_id;
/* For creating a hash database of stratum shares submitted that have not had
* a response yet */
struct stratum_share {
UT_hash_handle hh;
bool block;
struct work *work;
int id;
time_t sshare_time;
time_t sshare_sent;
};
struct schedtime {
bool enable;
struct tm tm;
};
va_start(ap, fmt);
vsnprintf(exit_buf, sizeof(exit_buf), fmt, ap);
va_end(ap);
_applog(LOG_ERR, exit_buf, true);
exit(1);
}
rd_lock(&mining_thr_lock);
thr = __get_thread(thr_id);
rd_unlock(&mining_thr_lock);
return thr;
}
return thr->cgpu;
}
rd_lock(&devices_lock);
cgpu = devices[id];
rd_unlock(&devices_lock);
return cgpu;
}
if (!sharelog_file)
return;
thr_id = work->thr_id;
cgpu = get_thr_cgpu(thr_id);
pool = work->pool;
t = (unsigned long int)(work->tv_work_found.tv_sec);
target = bin2hex(work->target, sizeof(work->target));
hash = bin2hex(work->hash, sizeof(work->hash));
data = bin2hex(work->data, sizeof(work->data));
// timestamp,disposition,target,pool,dev,thr,sharehash,sharedata
rv = snprintf(s, sizeof(s), "%lu,%s,%s,%s,%s%u,%u,%s,%s\n", t, disposition,
target, pool->rpc_url, cgpu->drv->name, cgpu->device_id, thr_id, hash, data);
free(target);
free(hash);
free(data);
if (rv >= (int)(sizeof(s)))
s[sizeof(s) - 1] = '\0';
else if (rv < 0) {
applog(LOG_ERR, "sharelog printf error");
return;
}
mutex_lock(&sharelog_lock);
ret = fwrite(s, rv, 1, sharelog_file);
fflush(sharelog_file);
mutex_unlock(&sharelog_lock);
if (ret != 1)
applog(LOG_ERR, "sharelog fwrite error");
}
if (!understood_rules || !rule)
return false;
while ((understood_rule = *understood_rules++)) {
if (strcmp(understood_rule, rule) == 0)
return true;
}
return false;
}
static bool gbt_check_rules(json_t* rules_arr, const char** understood_rules)
{
int i, rule_count;
const char *rule;
if (!rules_arr)
return true;
rule_count = json_array_size(rules_arr);
for (i = 0; i < rule_count; i++) {
rule = json_string_value(json_array_get(rules_arr, i));
if (rule && *rule++ == '!' && !gbt_check_required_rule(rule,
understood_rules))
return false;
}
return true;
}
/* Adjust all the pools' quota to the greatest common denominator after a pool
* has been added or the quotas changed. */
void adjust_quota_gcd(void)
{
unsigned long gcd, lowest_quota = ~0UL, quota;
struct pool *pool;
int i;
global_quota_gcd = gcd;
applog(LOG_DEBUG, "Global quota greatest common denominator set to %lu",
gcd);
}
/* Return value is ignored if not called from input_pool */
struct pool *add_pool(void)
{
struct pool *pool;
/* Make sure the pool doesn't think we've been idle since time 0 */
pool->tv_idle.tv_sec = ~0UL;
pool->rpc_req = gbt_req;
pool->rpc_proxy = NULL;
pool->quota = 1;
adjust_quota_gcd();
return pool;
}
mutex_lock(&pool->pool_lock);
ret = *var;
*var = true;
mutex_unlock(&pool->pool_lock);
return ret;
}
mutex_lock(&pool->pool_lock);
ret = *var;
*var = false;
mutex_unlock(&pool->pool_lock);
return ret;
}
cg_rlock(&control_lock);
pool = currentpool;
cg_runlock(&control_lock);
return pool;
}
char *set_int_range(const char *arg, int *i, int min, int max)
{
char *err = opt_set_intval(arg, i);
if (err)
return err;
return NULL;
}
#ifdef USE_DRAGONMINT_T1
static char *set_int_voltage(const char *arg, int *i)
{
return set_int_range(arg, i, CHIP_VOLT_MIN, CHIP_VOLT_MAX);
}
/* Intentionally does NOT accept zero so that zero means the value is NOT set
* and has no effect. */
static char *set_int_1_to_31(const char *arg, int *i)
{
return set_int_range(arg, i, 1, 31);
}
#endif
#ifdef USE_FPGA_SERIAL
static char *opt_add_serial;
static char *add_serial(char *arg)
{
string_elist_add(arg, &scan_devices);
return NULL;
}
#endif
/* Detect that url is for a stratum protocol either via the presence of
* stratum+tcp or by detecting a stratum server response */
bool detect_stratum(struct pool *pool, char *url)
{
bool ret = false;
if (detect_stratum(pool, arg))
goto out;
opt_set_charp(arg, &pool->rpc_url);
if (strncmp(arg, "http://", 7) &&
strncmp(arg, "https://", 8)) {
char httpinput[256];
strcpy(httpinput, "stratum+tcp://");
strncat(httpinput, arg, 242);
detect_stratum(pool, httpinput);
}
out:
return pool->rpc_url;
}
setup_url(pool, arg);
return NULL;
}
if (!semicolon)
return "No semicolon separated quota;URL pair found";
len = strlen(arg);
*semicolon = '\0';
qlen = strlen(arg);
if (!qlen)
return "No parameter for quota found";
len -= qlen + 1;
if (len < 1)
return "No parameter for URL found";
quota = atoi(arg);
if (quota < 0)
return "Invalid negative parameter for quota set";
url = arg + qlen + 1;
pool = add_url();
setup_url(pool, url);
pool->quota = quota;
applog(LOG_INFO, "Setting pool %d to quota %d", pool->pool_no, pool->quota);
adjust_quota_gcd();
return NULL;
}
if (total_userpasses)
return "Use only user + pass or userpass, but not both";
total_users++;
if (total_users > total_pools)
add_pool();
return NULL;
}
if (total_userpasses)
return "Use only user + pass or userpass, but not both";
total_passes++;
if (total_passes > total_pools)
add_pool();
return NULL;
}
if (total_users || total_passes)
return "Use only user + pass or userpass, but not both";
total_userpasses++;
if (total_userpasses > total_pools)
add_pool();
return NULL;
}
return NULL;
}
return NULL;
}
if (temp_cutoff_str) {
for (device = 0, nextptr = strtok(temp_cutoff_str, ","); nextptr; +
+device, nextptr = strtok(NULL, ",")) {
if (device >= total_devices)
quit(1, "Too many values passed to set temp cutoff");
val = atoi(nextptr);
if (val < 0 || val > 200)
quit(1, "Invalid value passed to set temp cutoff");
rd_lock(&devices_lock);
devices[device]->cutofftemp = val;
rd_unlock(&devices_lock);
}
} else {
rd_lock(&devices_lock);
for (i = device; i < total_devices; ++i) {
if (!devices[i]->cutofftemp)
devices[i]->cutofftemp = opt_cutofftemp;
}
rd_unlock(&devices_lock);
return;
}
if (device <= 1) {
rd_lock(&devices_lock);
for (i = device; i < total_devices; ++i)
devices[i]->cutofftemp = val;
rd_unlock(&devices_lock);
}
}
if (err)
return err;
return NULL;
}
if (err)
return err;
return NULL;
}
if (err)
return err;
return NULL;
}
if (!opt->names || !strlen(opt->names))
continue;
if (strlen(p) < 3)
continue;
if (err) {
/* Allow invalid values to be in configuration
* file, just skipping over them provided the
* JSON is still valid after that. */
if (fileconf) {
applog(LOG_ERR, "Invalid config option %s: %s", p,
err);
fileconf_load = -1;
} else {
snprintf(err_buf, sizeof(err_buf), "Parsing JSON
option %s: %s",
p, err);
return err_buf;
}
}
}
free(name);
}
return NULL;
}
#ifdef HAVE_LIBCURL
char conf_web1[] = "http://";
char conf_web2[] = "https://";
if (!val || !json_is_object(val))
return JSON_WEB_ERROR;
if (!cnfbuf)
cnfbuf = strdup(arg);
config_loaded = true;
#ifdef HAVE_LIBCURL
if (strncasecmp(arg, conf_web1, sizeof(conf_web1)-1) == 0 ||
strncasecmp(arg, conf_web2, sizeof(conf_web2)-1) == 0)
return load_web_config(arg);
#endif
if (!cnfbuf)
cnfbuf = strdup(arg);
config_loaded = true;
/* Parse the config now, so we can override it. That can keep pointers
* so don't free config object. */
return parse_config(config, true);
}
return NULL;
}
default_save_file(cnfbuf);
if (!access(cnfbuf, R_OK))
load_config(cnfbuf, NULL);
else {
free(cnfbuf);
cnfbuf = NULL;
}
}
#if defined(USE_USBUTILS)
char *display_devs(int *ndevs)
{
*ndevs = 0;
usb_all(0);
exit(*ndevs);
}
#endif
if (pool->vmask) {
/* This would only be set if the driver requested a vmask and
* the pool has a valid version mask. */
memcpy(work->data, &(pool->vmask_001[2]), 4);
flip64(data32, work->data);
sha256_init(&ctx);
sha256_update(&ctx, data, 64);
cg_memcpy(work->midstate1, ctx.h, 32);
endian_flip32(work->midstate1, work->midstate1);
cg_wlock(&control_lock);
ret = total_work++;
cg_wunlock(&control_lock);
return ret;
}
work->id = total_work_inc();
return work;
}
/* This is the central place all work that is about to be retired should be
* cleaned to remove any dynamically allocated arrays within the struct */
void clean_work(struct work *work)
{
free(work->job_id);
free(work->ntime);
free(work->coinbase);
free(work->nonce1);
memset(work, 0, sizeof(struct work));
}
/* All dynamically allocated work structs should be freed here to not leak any
* ram from arrays allocated within the work struct. Null the actual pointer
* used to call free_work. */
void _free_work(struct work **workptr, const char *file, const char *func, const
int line)
{
struct work *work = *workptr;
if (unlikely(!work)) {
applog(LOG_ERR, "Free work called with null work from %s %s:%d",
file, func, line);
return;
}
clean_work(work);
free(work);
*workptr = NULL;
}
static void gen_hash(unsigned char *data, unsigned char *hash, int len);
static void calc_diff(struct work *work, double known);
char *workpadding =
"0000008000000000000000000000000000000000000000000000000000000000000000000000000000
00000080020000";
#ifdef HAVE_LIBCURL
/* 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 void gbt_merkle_bins(struct pool *pool, json_t *transaction_arr);
static bool work_decode(struct pool *pool, struct work *work, json_t *val);
curl = curl_easy_init();
if (unlikely(!curl))
quit (1, "CURL initialisation failed in update_gbt");
val = json_rpc_call(curl, pool->rpc_url, pool->rpc_userpass,
pool->rpc_req, true, false, &rolltime, pool, false);
if (val) {
struct work *work = make_work();
bool rc = work_decode(pool, work, val);
total_getworks++;
pool->getwork_requested++;
if (rc) {
applog(LOG_DEBUG, "Successfully retrieved and updated GBT from
pool %u %s",
pool->pool_no, pool->rpc_url);
if (pool == current_pool())
opt_work_update = true;
} else {
applog(LOG_DEBUG, "Successfully retrieved but FAILED to decipher
GBT from pool %u %s",
pool->pool_no, pool->rpc_url);
}
json_decref(val);
free_work(work);
} else {
applog(LOG_DEBUG, "FAILED to update GBT from pool %u %s",
pool->pool_no, pool->rpc_url);
}
curl_easy_cleanup(curl);
}
cgtime(&now);
if (now.tv_sec - pool->tv_lastwork.tv_sec > 60)
update_gbt(pool);
cg_wlock(&pool->gbt_lock);
nonce2le = htole64(pool->nonce2);
cg_memcpy(pool->coinbase + pool->nonce2_offset, &nonce2le, pool->n2size);
pool->nonce2++;
cg_dwlock(&pool->gbt_lock);
__gbt_merkleroot(pool, merkleroot);
if (pool->gbt_workid)
work->job_id = strdup(pool->gbt_workid);
cg_runlock(&pool->gbt_lock);
if (opt_debug) {
char *header = bin2hex(work->data, 128);
calc_midstate(pool, work);
local_work++;
work->pool = pool;
work->gbt = true;
work->longpoll = false;
work->getwork_mode = GETWORK_MODE_GBT;
work->work_block = work_block;
/* Nominally allow a driver to ntime roll 60 seconds */
work->drv_rolllimit = 60;
calc_diff(work, 0);
cgtime(&work->tv_staged);
}
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"));
workid = json_string_value(json_object_get(res_val, "workid"));
if (!previousblockhash || !target || !coinbasetxn || !longpollid ||
!expires || !version || !curtime || !bits) {
applog(LOG_ERR, "JSON failed to decode GBT");
return false;
}
cg_wlock(&pool->gbt_lock);
free(pool->coinbasetxn);
pool->coinbasetxn = strdup(coinbasetxn);
cbt_len = strlen(pool->coinbasetxn) / 2;
/* We add 8 bytes of extra data corresponding to nonce2 */
pool->n2size = 8;
pool->coinbase_len = cbt_len + pool->n2size;
cal_len = pool->coinbase_len + 1;
free(pool->coinbase);
pool->coinbase = cgcalloc(cal_len, 1);
hex2bin(pool->coinbase, pool->coinbasetxn, 42);
extra_len = (uint8_t *)(pool->coinbase + 41);
orig_len = *extra_len;
hex2bin(pool->coinbase + 42, pool->coinbasetxn + 84, orig_len);
*extra_len += pool->n2size;
hex2bin(pool->coinbase + 42 + *extra_len, pool->coinbasetxn + 84 + (orig_len
* 2),
cbt_len - orig_len - 42);
pool->nonce2_offset = orig_len + 42;
free(pool->longpollid);
pool->longpollid = strdup(longpollid);
free(pool->gbt_workid);
if (workid)
pool->gbt_workid = strdup(workid);
else
pool->gbt_workid = NULL;
pool->gbt_expires = expires;
pool->gbt_version = htobe32(version);
pool->curtime = htobe32(curtime);
pool->submit_old = submitold;
return true;
}
free(pool->txn_data);
pool->txn_data = NULL;
pool->transactions = 0;
pool->merkles = 0;
pool->transactions = json_array_size(transaction_arr);
binlen = pool->transactions * 32 + 32;
hashbin = alloca(binlen + 32);
memset(hashbin, 0, 32);
binleft = binlen / 32;
if (pool->transactions) {
int len = 0, ofs = 0;
const char *txn;
txn_len = len / 2;
txn_bin = cgmalloc(txn_len);
hex2bin(txn_bin, txn, txn_len);
/* This is needed for pooled mining since only
* transaction data and not hashes are sent */
gen_hash(txn_bin, hashbin + 32 + 32 * i, txn_len);
continue;
}
#endif
if (!txid) {
applog(LOG_ERR, "missing txid in gbt_merkle_bins");
return;
}
if (!hex2bin(binswap, txid, 32)) {
applog(LOG_ERR, "Failed to hex2bin txid in
gbt_merkle_bins");
return;
}
swab256(hashbin + 32 + 32 * i, binswap);
}
}
if (binleft > 1) {
while (42) {
if (binleft == 1)
break;
cg_memcpy(pool->merklebin + (pool->merkles * 32), hashbin + 32,
32);
pool->merkles++;
if (binleft % 2) {
cg_memcpy(hashbin + binlen, hashbin + binlen - 32, 32);
binlen += 32;
binleft++;
}
for (i = 32, j = 64; j < binlen; i += 32, j += 64) {
gen_hash(hashbin + j, hashbin + i, 64);
}
binleft /= 2;
binlen = binleft * 32;
}
}
if (opt_debug) {
char hashhex[68];
previousblockhash = json_string_value(json_object_get(res_val,
"previousblockhash"));
target = json_string_value(json_object_get(res_val, "target"));
transaction_arr = json_object_get(res_val, "transactions");
rules_arr = json_object_get(res_val, "rules");
version = json_integer_value(json_object_get(res_val, "version"));
curtime = json_integer_value(json_object_get(res_val, "curtime"));
bits = json_string_value(json_object_get(res_val, "bits"));
height = json_integer_value(json_object_get(res_val, "height"));
coinbasevalue = json_integer_value(json_object_get(res_val,
"coinbasevalue"));
coinbase_aux = json_object_get(res_val, "coinbaseaux");
flags = json_string_value(json_object_get(coinbase_aux, "flags"));
default_witness_commitment = json_string_value(json_object_get(res_val,
"default_witness_commitment"));
if (rules_arr) {
int i;
int rule_count = json_array_size(rules_arr);
const char *rule;
cg_wlock(&pool->gbt_lock);
hex2bin(hash_swap, previousblockhash, 32);
swap256(pool->previousblockhash, hash_swap);
__bin2hex(pool->prev_hash, pool->previousblockhash, 32);
pool->gbt_version = htobe32(version);
pool->curtime = htobe32(curtime);
snprintf(pool->ntime, 9, "%08x", curtime);
snprintf(pool->bbversion, 9, "%08x", version);
snprintf(pool->nbit, 9, "%s", bits);
pool->nValue = coinbasevalue;
hex2bin((unsigned char *)&pool->gbt_bits, bits, 4);
gbt_merkle_bins(pool, transaction_arr);
if (insert_witness) {
char witness_str[sizeof(witnessdata) * 2];
witnessdata_size = sizeof(witnessdata);
if (!gbt_witness_data(transaction_arr, witnessdata, witnessdata_size))
{
applog(LOG_ERR, "error calculating witness data");
return false;
}
__bin2hex(witness_str, witnessdata, witnessdata_size);
applog(LOG_DEBUG, "calculated witness data: %s", witness_str);
if (default_witness_commitment) {
if (strncmp(witness_str, default_witness_commitment + 4,
witnessdata_size * 2) != 0) {
applog(LOG_ERR, "bad witness data. %s != %s",
default_witness_commitment + 4, witness_str);
return false;
}
}
}
if (pool->transactions < 3)
pool->bad_work++;
pool->height = height;
memset(pool->scriptsig_base, 0, 42);
ofs++; // Leave room for template length
/* Followed by flags */
len = strlen(flags) / 2;
pool->scriptsig_base[ofs++] = len;
hex2bin(pool->scriptsig_base + ofs, flags, len);
ofs += len;
/* Followed by timestamp */
cgtime(&now);
pool->scriptsig_base[ofs++] = 0xfe; // Encode seconds as u32
u32 = (uint32_t *)&pool->scriptsig_base[ofs];
*u32 = htole32(now.tv_sec);
ofs += 4; // sizeof uint32_t
pool->scriptsig_base[ofs++] = 0xfe; // Encode usecs as u32
u32 = (uint32_t *)&pool->scriptsig_base[ofs];
*u32 = htole32(now.tv_usec);
ofs += 4; // sizeof uint32_t
if (opt_btc_sig) {
len = strlen(opt_btc_sig);
if (len > 32)
len = 32;
pool->scriptsig_base[ofs++] = len;
cg_memcpy(pool->scriptsig_base + ofs, opt_btc_sig, len);
ofs += len;
}
len = 41 // prefix
+ ofs // Template length
+ 4 // txin sequence no
+ 1 // txouts
+ 8 // value
+ 1 + 25 // txout
+ 4; // lock
if (insert_witness) {
len += 8 //value
+ 1 + 2 + witnessdata_size; // total scriptPubKey size +
OP_RETURN + push size + data
}
free(pool->coinbase);
pool->coinbase = cgcalloc(len, 1);
cg_memcpy(pool->coinbase + 41, pool->scriptsig_base, ofs);
cg_memcpy(pool->coinbase + 41 + ofs, "\xff\xff\xff\xff", 4);
pool->coinbase[41 + ofs + 4] = insert_witness ? 2 : 1;
u64 = (uint64_t *)&(pool->coinbase[41 + ofs + 4 + 1]);
*u64 = htole64(coinbasevalue);
if (insert_witness) {
unsigned char *witness = &pool->coinbase[41 + ofs + 4 + 1 + 8 + 1 +
25];
memset(witness, 0, 8);
witness_txout_len += 8;
witness[witness_txout_len++] = witnessdata_size + 2; // total
scriptPubKey size
witness[witness_txout_len++] = 0x6a; // OP_RETURN
witness[witness_txout_len++] = witnessdata_size;
memcpy(&witness[witness_txout_len], witnessdata, witnessdata_size);
witness_txout_len += witnessdata_size;
}
pool->nonce2 = 0;
pool->n2size = 4;
pool->coinbase_len = 41 + ofs + 4 + 1 + 8 + 1 + 25 + witness_txout_len + 4;
cg_wunlock(&pool->gbt_lock);
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;
cgtime(&pool->tv_lastwork);
if (!res_val || json_is_null(res_val)) {
applog(LOG_ERR, "JSON Failed to decode result");
goto out;
}
if (pool->gbt_solo) {
if (unlikely(!gbt_solo_decode(pool, res_val)))
goto out;
goto out_true;
}
if (unlikely(!gbt_decode(pool, res_val)))
goto out;
work->gbt = true;
memset(work->hash, 0, sizeof(work->hash));
cgtime(&work->tv_staged);
out_true:
ret = true;
out:
return ret;
}
#else /* HAVE_LIBCURL */
#define json_rpc_call(curl, url, userpass, rpc_req, probe, longpoll, rolltime,
pool, share) (NULL)
#define work_decode(pool, work, val) (false)
#define gen_gbt_work(pool, work) {}
#endif /* HAVE_LIBCURL */
return cgpu->device_id;
}
if (fsecs <= 0)
return;
fprop = 1.0 - 1 / (exp(fsecs / interval));
ftotal = 1.0 + fprop;
*f += (fadd / fsecs * fprop);
*f /= ftotal;
}
mutex_lock(stgd_lock);
ret = __total_staged();
mutex_unlock(stgd_lock);
return ret;
}
#endif
#ifdef HAVE_CURSES
WINDOW *mainwin, *statuswin, *logwin;
#endif
double total_secs = 1.0;
static char statusline[256];
/* logstart is where the log window should start */
static int devcursor, logstart, logcursor;
#ifdef HAVE_CURSES
/* statusy is where the status window goes up to in cases where it won't fit at
startup */
static int statusy;
#endif
#ifdef HAVE_CURSES
static inline void unlock_curses(void)
{
mutex_unlock(&console_lock);
}
lock_curses();
ret = curses_active;
if (!ret)
unlock_curses();
return ret;
}
#endif
/* Convert a uint64_t value into a truncated string for displaying with its
* associated suitable for Mega, Giga etc. Buf array needs to be long enough */
static void suffix_string(uint64_t val, char *buf, size_t bufsiz, int sigdigits)
{
const double dkilo = 1000.0;
const uint64_t kilo = 1000ull;
const uint64_t mega = 1000000ull;
const uint64_t giga = 1000000000ull;
const uint64_t tera = 1000000000000ull;
const uint64_t peta = 1000000000000000ull;
const uint64_t exa = 1000000000000000000ull;
char suffix[2] = "";
bool decimal = true;
double dval;
if (!sigdigits) {
if (decimal)
snprintf(buf, bufsiz, "%.3g%s", dval, suffix);
else
snprintf(buf, bufsiz, "%d%s", (unsigned int)dval, suffix);
} else {
/* Always show sigdigits + 1, padded on right with zeroes
* followed by suffix */
int ndigits = sigdigits - 1 - (dval > 0.0 ? floor(log10(dval)) : 0);
if (cgpu->dev_start_tv.tv_sec == 0)
dev_runtime = total_secs;
else {
cgtime(&now);
dev_runtime = tdiff(&now, &(cgpu->dev_start_tv));
}
double tsince_restart(void)
{
struct timeval now;
cgtime(&now);
return tdiff(&now, &restart_tv_start);
}
double tsince_update(void)
{
struct timeval now;
cgtime(&now);
return tdiff(&now, &update_tv_start);
}
dev_runtime = cgpu_runtime(cgpu);
#ifdef HAVE_CURSES
#define CURBUFSIZ 256
#define cg_mvwprintw(win, y, x, fmt, ...) do { \
char tmp42[CURBUFSIZ]; \
snprintf(tmp42, sizeof(tmp42), fmt, ##__VA_ARGS__); \
mvwprintw(win, y, x, "%s", tmp42); \
} while (0)
#define cg_wprintw(win, fmt, ...) do { \
char tmp42[CURBUFSIZ]; \
snprintf(tmp42, sizeof(tmp42), fmt, ##__VA_ARGS__); \
wprintw(win, "%s", tmp42); \
} while (0)
wattron(statuswin, A_BOLD);
cg_mvwprintw(statuswin, 0, 0, " " PACKAGE " version " VERSION " - Started:
%s", datestamp);
wattroff(statuswin, A_BOLD);
mvwhline(statuswin, 1, 0, '-', linewidth);
cg_mvwprintw(statuswin, 2, 0, " %s", statusline);
wclrtoeol(statuswin);
if (opt_widescreen) {
cg_mvwprintw(statuswin, 3, 0, " A:%.0f R:%.0f HW:%d WU:%.1f/m |"
" ST: %d SS: %"PRId64" NB: %d LW: %d GF: %d RF: %d",
total_diff_accepted, total_diff_rejected, hw_errors,
total_diff1 / total_secs * 60,
total_staged(), total_stale, new_blocks, local_work,
total_go, total_ro);
} else if (alt_status) {
cg_mvwprintw(statuswin, 3, 0, " ST: %d SS: %"PRId64" NB: %d LW: %d
GF: %d RF: %d",
total_staged(), total_stale, new_blocks, local_work,
total_go, total_ro);
} else {
cg_mvwprintw(statuswin, 3, 0, " A:%.0f R:%.0f HW:%d WU:%.1f/m",
total_diff_accepted, total_diff_rejected, hw_errors,
total_diff1 / total_secs * 60);
}
wclrtoeol(statuswin);
if (shared_strategy() && total_pools > 1) {
cg_mvwprintw(statuswin, 4, 0, " Connected to multiple pools with%s
block change notify",
have_longpoll ? "": "out");
} else if (pool->has_stratum) {
cg_mvwprintw(statuswin, 4, 0, " Connected to %s diff %s with stratum as
user %s",
pool->sockaddr_url, pool->diff, pool->rpc_user);
} else {
cg_mvwprintw(statuswin, 4, 0, " Connected to %s diff %s with%s %s as
user %s",
pool->sockaddr_url, pool->diff, have_longpoll ? "": "out",
pool->has_gbt ? "GBT" : "LP", pool->rpc_user);
}
wclrtoeol(statuswin);
cg_mvwprintw(statuswin, 5, 0, " Block: %s... Diff:%s Started: %s Best
share: %s ",
prev_block, block_diff, blocktime, best_share);
mvwhline(statuswin, 6, 0, '-', linewidth);
mvwhline(statuswin, statusy - 1, 0, '-', linewidth);
#ifdef USE_USBUTILS
cg_mvwprintw(statuswin, devcursor - 1, 1, "[U]SB management [P]ool management
[S]ettings [D]isplay options [Q]uit");
#else
cg_mvwprintw(statuswin, devcursor - 1, 1, "[P]ool management [S]ettings
[D]isplay options [Q]uit");
#endif
}
#define STATBEFORELEN 23
const char blanks[] = " ";
if (opt_compact)
return;
if (cgpu->dev_start_tv.tv_sec == 0)
dev_runtime = total_secs;
else {
cgtime(&now);
dev_runtime = tdiff(&now, &(cgpu->dev_start_tv));
}
#ifdef USE_USBUTILS
if (cgpu->usbinfo.nodev)
cg_wprintw(statuswin, "ZOMBIE");
else
#endif
if (cgpu->status == LIFE_DEAD)
cg_wprintw(statuswin, "DEAD ");
else if (cgpu->status == LIFE_SICK)
cg_wprintw(statuswin, "SICK ");
else if (cgpu->deven == DEV_DISABLED)
cg_wprintw(statuswin, "OFF ");
else if (cgpu->deven == DEV_RECOVER)
cg_wprintw(statuswin, "REST ");
else if (opt_widescreen) {
char displayed_hashes[16], displayed_rolling[16];
uint64_t d64;
logline[0] = '\0';
cgpu->drv->get_statline(logline, sizeof(logline), cgpu);
cg_wprintw(statuswin, "%s", logline);
wclrtoeol(statuswin);
}
#endif
#ifdef HAVE_CURSES
/* Check for window resize. Called with curses mutex locked */
static inline void change_logwinsize(void)
{
int x, y, logx, logy;
getmaxyx(mainwin, y, x);
if (x < 80 || y < 25)
return;
y -= logcursor;
getmaxyx(logwin, logy, logx);
/* Detect screen size change */
if (x != logx || y != logy)
wresize(logwin, y, x);
}
erase();
x = getmaxx(statuswin);
if (logstart > LINES - 2)
statusy = LINES - 2;
else
statusy = logstart;
logcursor = statusy;
wresize(statuswin, statusy, x);
getmaxyx(mainwin, y, x);
y -= logcursor;
wresize(logwin, y, x);
mvwin(logwin, logcursor, 0);
unlock_curses();
}
}
/* Mandatory printing */
void _wlogprint(const char *str)
{
if (curses_active_locked()) {
wprintw(logwin, "%s", str);
unlock_curses();
}
}
#endif
#ifdef HAVE_CURSES
bool log_curses_only(int prio, const char *datetime, const char *str)
{
bool high_prio;
if (curses_active_locked()) {
if (!opt_loginput || high_prio) {
wprintw(logwin, "%s%s\n", datetime, str);
if (high_prio) {
touchwin(logwin);
wrefresh(logwin);
}
}
unlock_curses();
return true;
}
return false;
}
void clear_logwin(void)
{
if (curses_active_locked()) {
erase();
wclear(logwin);
unlock_curses();
}
}
void logwin_update(void)
{
if (curses_active_locked()) {
touchwin(logwin);
wrefresh(logwin);
unlock_curses();
}
}
#endif
#ifdef HAVE_CURSES
static void disable_pool(struct pool *pool)
{
if (pool->enabled == POOL_ENABLED)
enabled_pools--;
pool->enabled = POOL_DISABLED;
}
#endif
cgpu = get_thr_cgpu(work->thr_id);
pool->seq_rejects = 0;
cgpu->last_share_pool = pool->pool_no;
cgpu->last_share_pool_time = time(NULL);
cgpu->last_share_diff = work->work_difficulty;
pool->last_share_time = cgpu->last_share_pool_time;
pool->last_share_diff = work->work_difficulty;
applog(LOG_DEBUG, "PROOF OF WORK RESULT: true (yay!!!)");
if (!QUIET) {
if (total_pools > 1)
applog(LOG_NOTICE, "Accepted %s %s %d pool %d %s%s",
hashshow, cgpu->drv->name, cgpu->device_id, work-
>pool->pool_no, resubmit ? "(resubmit)" : "", worktime);
else
applog(LOG_NOTICE, "Accepted %s %s %d %s%s",
hashshow, cgpu->drv->name, cgpu->device_id, resubmit
? "(resubmit)" : "", worktime);
}
sharelog("accept", work);
if (opt_shares && total_diff_accepted >= opt_shares) {
applog(LOG_WARNING, "Successfully mined %d accepted shares as
requested and exiting.", opt_shares);
kill_work();
return;
}
strcpy(reason, "");
if (total_pools > 1)
snprintf(where, sizeof(where), "pool %d", work->pool-
>pool_no);
else
strcpy(where, "");
if (!work->gbt)
res = json_object_get(val, "reject-reason");
if (res) {
const char *reasontmp = json_string_value(res);
swab256(rhash, work->hash);
for (ofs = 0; ofs <= 28; ofs ++) {
if (rhash[ofs])
break;
}
hash32 = (uint32_t *)(rhash + ofs);
h32 = be32toh(*hash32);
uintdiff = round(work->work_difficulty);
suffix_string(work->share_diff, diffdisp, sizeof (diffdisp), 0);
snprintf(hashshow, 64, "%08lx Diff %s/%"PRIu64"%s", h32, diffdisp, uintdiff,
work->block? " BLOCK!" : "");
}
#ifdef HAVE_LIBCURL
static void text_print_status(int thr_id)
{
struct cgpu_info *cgpu;
char logline[256];
cgpu = get_thr_cgpu(thr_id);
if (cgpu) {
get_statline(logline, sizeof(logline), cgpu);
printf("%s\n", logline);
}
}
strcat(gbt_block, "fd"); // +2
__bin2hex(varint, (const unsigned char *)&val16, 2);
} else {
uint32_t val32 = htole32(work->gbt_txns);
strcat(gbt_block, "fe"); // +2
__bin2hex(varint, (const unsigned char *)&val32, 4);
}
strcat(gbt_block, varint); // +8 max
strcat(gbt_block, work->coinbase);
s = cgmalloc(1024);
sprintf(s, "{\"id\": 0, \"method\": \"submitblock\", \"params\": [\"%s",
gbt_block);
/* Has submit/coinbase support */
if (!pool->has_gbt) {
cg_rlock(&pool->gbt_lock);
if (pool->txn_data)
s = realloc_strcat(s, pool->txn_data);
cg_runlock(&pool->gbt_lock);
}
if (work->job_id) {
s = realloc_strcat(s, "\", {\"workid\": \"");
s = realloc_strcat(s, work->job_id);
s = realloc_strcat(s, "\"}]}");
} else
s = realloc_strcat(s, "\"]}");
applog(LOG_DEBUG, "DBG: sending %s submit RPC call: %s", pool->rpc_url, s);
s = realloc_strcat(s, "\n");
cgtime(&tv_submit);
/* issue JSON-RPC request */
val = json_rpc_call(curl, pool->rpc_url, pool->rpc_userpass, s, false, false,
&rolltime, pool, true);
cgtime(&tv_submit_reply);
free(s);
if (unlikely(!val)) {
applog(LOG_INFO, "submit_upstream_work json_rpc_call failed");
if (!pool_tset(pool, &pool->submit_fail)) {
total_ro++;
pool->remotefail_occasions++;
if (opt_lowmem) {
applog(LOG_WARNING, "Pool %d communication failure,
discarding shares", pool->pool_no);
goto out;
}
applog(LOG_WARNING, "Pool %d communication failure, caching
submissions", pool->pool_no);
}
cgsleep_ms(5000);
goto out;
} else if (pool_tclear(pool, &pool->submit_fail))
applog(LOG_WARNING, "Pool %d communication resumed, submitting work",
pool->pool_no);
if (!QUIET) {
show_hash(work, hashshow);
if (opt_worktime) {
char workclone[20];
struct tm *tm, tm_getwork, tm_submit_reply;
double getwork_time = tdiff((struct timeval *)&(work-
>tv_getwork_reply),
(struct timeval *)&(work->tv_getwork));
double getwork_to_work = tdiff((struct timeval *)&(work-
>tv_work_start),
(struct timeval *)&(work-
>tv_getwork_reply));
double work_time = tdiff((struct timeval *)&(work-
>tv_work_found),
(struct timeval *)&(work-
>tv_work_start));
double work_to_submit = tdiff(&tv_submit,
(struct timeval *)&(work-
>tv_work_found));
double submit_time = tdiff(&tv_submit_reply, &tv_submit);
int diffplaces = 3;
if (work->clone) {
snprintf(workclone, sizeof(workclone), "C:%1.3f",
tdiff((struct timeval *)&(work->tv_cloned),
(struct timeval *)&(work->tv_getwork_reply)));
}
else
strcpy(workclone, "O");
if (work->work_difficulty < 1)
diffplaces = 6;
snprintf(worktime, sizeof(worktime),
" <-%08lx.%08lx M:%c D:%1.*f G:%02d:%02d:%02d:%1.3f %s
(%1.3f) W:%1.3f (%1.3f) S:%1.3f R:%02d:%02d:%02d",
(unsigned long)be32toh(*(uint32_t *)&(work->data[28])),
(unsigned long)be32toh(*(uint32_t *)&(work->data[24])),
work->getwork_mode, diffplaces, work->work_difficulty,
tm_getwork.tm_hour, tm_getwork.tm_min,
tm_getwork.tm_sec, getwork_time, workclone,
getwork_to_work, work_time, work_to_submit, submit_time,
tm_submit_reply.tm_hour, tm_submit_reply.tm_min,
tm_submit_reply.tm_sec);
}
}
if (cgpu->dev_start_tv.tv_sec == 0)
dev_runtime = total_secs;
else {
cgtime(&now);
dev_runtime = tdiff(&now, &(cgpu->dev_start_tv));
}
if (!opt_realquiet)
print_status(thr_id);
if (!want_per_device_stats) {
char logline[256];
json_decref(val);
rc = true;
out:
return rc;
}
#endif /* HAVE_LIBCURL */
if (pool_unusable(pool))
continue;
if (pool->shares < lowest) {
lowest = pool->shares;
ret = pool;
}
}
ret->shares++;
return ret;
}
cp = current_pool();
if (pool_strategy == POOL_BALANCE) {
pool = select_balanced(cp);
goto out;
}
if (pool_strategy != POOL_LOADBALANCE) {
pool = cp;
goto out;
} else
pool = NULL;
if (!pool_unusable(tp)) {
pool = tp;
break;
}
}
}
/* truediffone ==
0x00000000FFFF0000000000000000000000000000000000000000000000000000
* Generate a 256 bit binary LE target by cutting up diff into 64 bit sized
* portions or vice versa. */
static const double truediffone =
26959535291011309493156476344723991336010898738574164086137773096960.0;
static const double bits192 =
6277101735386680763835789423207666416102355444464034512896.0;
static const double bits128 = 340282366920938463463374607431768211456.0;
static const double bits64 = 18446744073709551616.0;
return dcut64;
}
d64 = truediffone;
dcut64 = le256todouble(target);
if (unlikely(!dcut64))
dcut64 = 1;
return d64 / dcut64;
}
/*
* Calculate the work->work_difficulty based on the work->target
*/
static void calc_diff(struct work *work, double known)
{
struct cgminer_pool_stats *pool_stats = &(work->pool->cgminer_pool_stats);
double difficulty;
uint64_t uintdiff;
if (known)
work->work_difficulty = known;
else
work->work_difficulty = diff_from_target(work->target);
difficulty = work->work_difficulty;
pool_stats->last_diff = difficulty;
uintdiff = round(difficulty);
suffix_string(uintdiff, work->pool->diff, sizeof(work->pool->diff), 0);
if (difficulty == pool_stats->min_diff)
pool_stats->min_diff_count++;
else if (difficulty < pool_stats->min_diff || pool_stats->min_diff == 0) {
pool_stats->min_diff = difficulty;
pool_stats->min_diff_count = 1;
}
if (difficulty == pool_stats->max_diff)
pool_stats->max_diff_count++;
else if (difficulty > pool_stats->max_diff) {
pool_stats->max_diff = difficulty;
pool_stats->max_diff_count = 1;
}
}
dn = 0;
for (i = 0; i < 4; i++) {
dn *= 0x100;
dn += nonce & 0xff;
nonce /= 0x100;
}
if (!benchfile_in) {
if (opt_benchfile)
benchfile_in = fopen(opt_benchfile, "r");
else
quit(1, "BENCHFILE Invalid benchfile NULL");
if (!benchfile_in)
quit(1, "BENCHFILE Failed to open benchfile '%s'",
opt_benchfile);
benchfile_line = 0;
if (!got) {
if (!fgets(buf, 1024, benchfile_in)) {
if (benchfile_work == 0)
quit(1, "BENCHFILE No work in benchfile '%s'",
opt_benchfile);
fclose(benchfile_in);
benchfile_in = NULL;
return benchfile_get_work(work);
}
}
do {
benchfile_line++;
// Empty lines and lines starting with '#' or '/' are ignored
if (*buf != '\0' && *buf != '#' && *buf != '/') {
char *commas[BENCHWORK_COUNT];
int i, j, len;
long nonce_time;
commas[0] = buf;
for (i = 1; i < BENCHWORK_COUNT; i++) {
commas[i] = strchr(commas[i-1], ',');
if (!commas[i]) {
quit(1, "BENCHFILE Invalid input file line %d"
" - field count is %d but should be %d",
benchfile_line, i, BENCHWORK_COUNT);
}
len = commas[i] - commas[i-1];
if (benchfile_data[i-1].length &&
(len != benchfile_data[i-1].length)) {
quit(1, "BENCHFILE Invalid input file line %d "
"field %d (%s) length is %d but should be %d",
benchfile_line, i,
benchfile_data[i-1].name,
len, benchfile_data[i-1].length);
}
*(commas[i]++) = '\0';
}
j = strlen(item);
for (i = benchfile_data[BENCHWORK_PREVHASH].length-8; i >= 0; i -
= 8) {
sprintf(&(item[j]), "%.8s", &commas[BENCHWORK_PREVHASH]
[i]);
j += 8;
}
nonce_time = atol(commas[BENCHWORK_NONCETIME]);
strcpy(&(item[j]), commas[BENCHWORK_DIFFBITS]);
j += benchfile_data[BENCHWORK_DIFFBITS].length;
memset(work, 0, sizeof(*work));
calc_midstate(work->pool, work);
benchfile_work++;
return true;
}
} while (fgets(buf, 1024, benchfile_in));
if (benchfile_work == 0)
quit(1, "BENCHFILE No work in benchfile '%s'", opt_benchfile);
fclose(benchfile_in);
benchfile_in = NULL;
return benchfile_get_work(work);
}
#ifdef HAVE_CURSES
static void disable_curses_windows(void)
{
leaveok(logwin, false);
leaveok(statuswin, false);
leaveok(mainwin, false);
nocbreak();
echo();
delwin(logwin);
delwin(statuswin);
}
mutex_trylock(&console_lock);
ret = curses_active;
if (!ret)
unlock_curses();
return ret;
}
if (GetConsoleScreenBufferInfo(hout, &csbi)) {
coord.X = 0;
coord.Y = csbi.dwSize.Y - 1;
SetConsoleCursorPosition(hout, coord);
}
#endif
unlock_curses();
}
}
#endif
thr = get_thread(i);
if (thr && PTH(thr) != 0L)
pth = &thr->pth;
thr_info_cancel(thr);
if (pth && *pth)
pthread_join(*pth, NULL);
}
}
thr = get_thread(i);
if (thr && PTH(thr) != 0L)
pth = &thr->pth;
if (pth && *pth)
pthread_join(*pth, NULL);
}
}
if (!successful_connect)
return;
#ifdef USE_USBUTILS
/* Best to get rid of it first so it doesn't
* try to create any new devices */
forcelog(LOG_DEBUG, "Killing off HotPlug thread");
thr = &control_thr[hotplug_thr_id];
kill_timeout(thr);
#endif
thr = get_thread(i);
if (!thr)
continue;
cgpu = thr->cgpu;
if (!cgpu)
continue;
cgpu->shutdown = true;
}
#ifdef USE_USBUTILS
/* Release USB resources in case it's a restart
* and not a QUIT */
forcelog(LOG_DEBUG, "Releasing all USB devices");
cg_completion_timeout(&usb_cleanup, NULL, 1000);
static
#ifdef WIN32
const
#endif
char **initial_args;
void app_restart(void)
{
applog(LOG_WARNING, "Attempting to restart %s", packagename);
#ifdef USE_LIBSYSTEMD
sd_notify(false, "RELOADING=1\n"
"STATUS=Restarting...");
#endif
execv(initial_args[0], (EXECV_2ND_ARG_TYPE)initial_args);
applog(LOG_WARNING, "Failed to restart application");
}
#define stage_work(WORK) do { \
_stage_work(WORK); \
WORK = NULL; \
} while (0)
work_clone->clone = true;
cgtime((struct timeval *)&(work_clone->tv_cloned));
work_clone->longpoll = false;
work_clone->mandatory = false;
/* Make cloned work appear slightly older to bias towards keeping the
* master work item which can be further rolled */
work_clone->tv_staged.tv_sec -= 1;
return work_clone;
}
#ifdef HAVE_LIBCURL
/* Called with pool_lock held. Recruit an extra curl if none are available for
* this pool. */
static void recruit_curl(struct pool *pool)
{
struct curl_ent *ce = cgcalloc(sizeof(struct curl_ent), 1);
ce->curl = curl_easy_init();
if (unlikely(!ce->curl))
quit(1, "Failed to init in recruit_curl");
list_add(&ce->node, &pool->curlring);
pool->curls++;
}
/* Grab an available curl if there is one. If not, then recruit extra curls
* unless we are in a submit_fail situation, or we have opt_delaynet enabled
* and there are already 5 curls in circulation. Limit total number to the
* number of mining threads per pool as well to prevent blasting a pool during
* network delays/outages. */
static struct curl_ent *pop_curl_entry(struct pool *pool)
{
int curl_limit = opt_delaynet ? 5 : (mining_threads + max_queue) * 2;
bool recruited = false;
struct curl_ent *ce;
mutex_lock(&pool->pool_lock);
retry:
if (!pool->curls) {
recruit_curl(pool);
recruited = true;
} else if (list_empty(&pool->curlring)) {
if (pool->curls >= curl_limit) {
pthread_cond_wait(&pool->cr_cond, &pool->pool_lock);
goto retry;
} else {
recruit_curl(pool);
recruited = true;
}
}
ce = list_entry(pool->curlring.next, struct curl_ent, node);
list_del(&ce->node);
mutex_unlock(&pool->pool_lock);
if (recruited)
applog(LOG_DEBUG, "Recruited curl for pool %d", pool->pool_no);
return ce;
}
return true;
}
/* Limit rolls to 7000 to not beyond 2 hours in the future where bitcoind will
* reject blocks as invalid. */
static inline bool can_roll(struct work *work)
{
return (!work->stratum && work->pool && work->rolltime && !work->clone &&
work->rolls < 7000 && !stale_work(work, false));
}
pthread_detach(pthread_self());
RenameThread("SubmitWork");
ce = pop_curl_entry(pool);
/* submit solution to bitcoin via JSON-RPC */
while (!submit_upstream_work(work, ce->curl, resubmit)) {
if (opt_lowmem) {
applog(LOG_NOTICE, "Pool %d share being discarded to minimise
memory cache", pool->pool_no);
break;
}
resubmit = true;
if (stale_work(work, true)) {
applog(LOG_NOTICE, "Pool %d share became stale while retrying
submit, discarding", pool->pool_no);
mutex_lock(&stats_lock);
total_stale++;
pool->stale_shares++;
total_diff_stale += work->work_difficulty;
pool->diff_stale += work->work_difficulty;
mutex_unlock(&stats_lock);
free_work(work);
break;
}
return NULL;
}
if (mrs < 1)
return work;
cloned = false;
work_clone = make_clone(work);
while (mrs-- > 0 && can_roll(work) && should_roll(work)) {
applog(LOG_DEBUG, "Pushing rolled converted work to stage thread");
stage_work(work_clone);
roll_work(work);
work_clone = make_clone(work);
/* Roll it again to prevent duplicates should this be used
* directly later on */
roll_work(work);
cloned = true;
}
if (cloned) {
stage_work(work);
return work_clone;
}
free_work(work_clone);
return work;
}
#else /* HAVE_LIBCURL */
static void *submit_work_thread(void __maybe_unused *userdata)
{
pthread_detach(pthread_self());
return NULL;
}
#endif /* HAVE_LIBCURL */
clean_work(work);
cg_memcpy(work, base_work, sizeof(struct work));
/* Keep the unique new id assigned during make_work to prevent copied
* work from having the same id. */
work->id = id;
if (base_work->job_id)
work->job_id = strdup(base_work->job_id);
if (base_work->nonce1)
work->nonce1 = strdup(base_work->nonce1);
if (base_work->ntime) {
/* If we are passed an noffset the binary work->data ntime and
* the work->ntime hex string need to be adjusted. */
if (noffset) {
uint32_t *work_ntime = (uint32_t *)(work->data + 68);
uint32_t ntime = be32toh(*work_ntime);
ntime += noffset;
*work_ntime = htobe32(ntime);
work->ntime = offset_ntime(base_work->ntime, noffset);
} else
work->ntime = strdup(base_work->ntime);
} else if (noffset) {
uint32_t *work_ntime = (uint32_t *)(work->data + 68);
uint32_t ntime = be32toh(*work_ntime);
ntime += noffset;
*work_ntime = htobe32(ntime);
}
if (base_work->coinbase)
work->coinbase = strdup(base_work->coinbase);
}
*work_ntime = htobe32(ntime);
if (work->ntime) {
free(work->ntime);
work->ntime = bin2hex((unsigned char *)work_ntime, 4);
}
}
return work;
}
if (opt_benchmark || opt_benchfile)
return false;
if (work->work_block != work_block) {
applog(LOG_DEBUG, "Work stale due to block mismatch");
return true;
}
pool = work->pool;
if (!pool->stratum_active || !pool->stratum_notify) {
applog(LOG_DEBUG, "Work stale due to stratum inactive");
return true;
}
same_job = true;
cg_rlock(&pool->data_lock);
if (strcmp(work->job_id, pool->swork.job_id))
same_job = false;
cg_runlock(&pool->data_lock);
if (!same_job) {
applog(LOG_DEBUG, "Work stale due to stratum job_id mismatch");
return true;
}
}
cgtime(&now);
if ((now.tv_sec - work->tv_staged.tv_sec) >= work_expiry) {
applog(LOG_DEBUG, "Work stale due to expiry");
return true;
}
return false;
}
d64 = truediffone;
s64 = le256todouble(work->hash);
if (unlikely(!s64))
s64 = 0;
cg_wlock(&control_lock);
if (unlikely(ret > best_diff)) {
new_best = true;
best_diff = ret;
suffix_string(best_diff, best_share, sizeof(best_share), 0);
}
if (unlikely(ret > work->pool->best_diff))
work->pool->best_diff = ret;
cg_wunlock(&control_lock);
if (unlikely(new_best))
applog(LOG_INFO, "New best share: %s", best_share);
return ret;
}
flip80(swap32, data32);
sha256(swap, 80, hash1);
sha256(hash1, 32, (unsigned char *)(work->hash));
}
if (pool->prio == choice) {
ret = pool;
break;
}
}
if (unlikely(!ret)) {
applog(LOG_ERR, "WTF No pool %d found!", choice);
return pools[choice];
}
return ret;
}
cg_wlock(&control_lock);
last_pool = currentpool;
pool_no = currentpool->pool_no;
switch (pool_strategy) {
/* All of these set to the master pool */
case POOL_BALANCE:
case POOL_FAILOVER:
case POOL_LOADBALANCE:
for (i = 0; i < total_pools; i++) {
pool = priority_pool(i);
if (pool_unusable(pool))
continue;
pool_no = pool->pool_no;
break;
}
break;
/* Both of these simply increment and cycle */
case POOL_ROUNDROBIN:
case POOL_ROTATE:
if (selected && !selected->idle) {
pool_no = selected->pool_no;
break;
}
next_pool = pool_no;
/* Select the next alive pool */
for (i = 1; i < total_pools; i++) {
next_pool++;
if (next_pool >= total_pools)
next_pool = 0;
pool = pools[next_pool];
if (pool_unusable(pool))
continue;
pool_no = next_pool;
break;
}
break;
default:
break;
}
currentpool = pools[pool_no];
pool = currentpool;
cg_wunlock(&control_lock);
mutex_lock(&lp_lock);
pthread_cond_broadcast(&lp_cond);
mutex_unlock(&lp_lock);
}
void _discard_work(struct work **workptr, const char *file, const char *func, const
int line)
{
struct work *work = *workptr;
if (unlikely(!work)) {
applog(LOG_ERR, "Discard work called with null work from %s %s:%d",
file, func, line);
return;
}
if (!work->clone && !work->rolls && !work->mined) {
if (work->pool) {
work->pool->discarded_work++;
work->pool->quota_used--;
work->pool->works--;
}
total_discarded++;
applog(LOG_DEBUG, "Discarded work");
} else
applog(LOG_DEBUG, "Discarded cloned or rolled work");
_free_work(workptr, file, func, line);
}
mutex_lock(stgd_lock);
HASH_ITER(hh, staged_work, work, tmp) {
if (stale_work(work, false)) {
HASH_DEL(staged_work, work);
discard_work(work);
stale++;
}
}
pthread_cond_signal(&gws_cond);
mutex_unlock(stgd_lock);
if (stale)
applog(LOG_DEBUG, "Discarded %d stales that didn't match current hash",
stale);
}
/* A generic wait function for threads that poll that will wait a specified
* time tdiff waiting on the pthread conditional that is broadcast when a
* work restart is required. Returns the value of pthread_cond_timedwait
* which is zero if the condition was met or ETIMEDOUT if not.
*/
int restart_wait(struct thr_info *thr, unsigned int mstime)
{
struct timespec abstime, tdiff;
int rc;
cgcond_time(&abstime);
ms_to_timespec(&tdiff, mstime);
timeraddspec(&abstime, &tdiff);
mutex_lock(&restart_lock);
if (thr->work_restart)
rc = 0;
else
rc = pthread_cond_timedwait(&restart_cond, &restart_lock, &abstime);
mutex_unlock(&restart_lock);
return rc;
}
pthread_detach(pthread_self());
rd_lock(&mining_thr_lock);
mt = mining_threads;
rd_unlock(&mining_thr_lock);
mutex_lock(&restart_lock);
pthread_cond_broadcast(&restart_cond);
mutex_unlock(&restart_lock);
#ifdef USE_USBUTILS
/* Cancels any cancellable usb transfers. Flagged as such it means they
* are usualy waiting on a read result and it's safe to abort the read
* early. */
cancel_usb_transfers();
#endif
return NULL;
}
cgtime(&restart_tv_start);
if (unlikely(pthread_create(&rthread, NULL, restart_thread, NULL)))
quithere(1, "Failed to create restart thread errno=%d", errno);
}
cgtime(&update_tv_start);
rd_lock(&mining_thr_lock);
for (i = 0; i < mining_threads; i++)
mining_thr[i]->work_update = true;
rd_unlock(&mining_thr_lock);
}
cg_wlock(&ch_lock);
cgtime(&block_timeval);
strcpy(current_hash, hexstr);
cg_memcpy(current_block, bedata, 32);
get_timestamp(blocktime, sizeof(blocktime), &block_timeval);
cg_wunlock(&ch_lock);
if (unlikely(current_diff != ddiff)) {
suffix_string(ddiff, block_diff, sizeof(block_diff), 0);
current_diff = ddiff;
applog(LOG_NOTICE, "Network diff set to %s", block_diff);
}
}
/* Search to see if this string is from a block that has been seen before */
static bool block_exists(const char *hexstr, const unsigned char *bedata, const
struct work *work)
{
int deleted_block = 0;
struct block *s;
bool ret = true;
wr_lock(&blk_lock);
HASH_FIND_STR(blocks, hexstr, s);
if (!s) {
s = cgcalloc(sizeof(struct block), 1);
if (unlikely(!s))
quit (1, "block_exists OOM");
strcpy(s->hash, hexstr);
s->block_no = new_blocks++;
ret = false;
/* Only keep the last hour's worth of blocks in memory since
* work from blocks before this is virtually impossible and we
* want to prevent memory usage from continually rising */
if (HASH_COUNT(blocks) > 6) {
struct block *oldblock;
HASH_SORT(blocks, block_sort);
oldblock = blocks;
deleted_block = oldblock->block_no;
HASH_DEL(blocks, oldblock);
free(oldblock);
}
HASH_ADD_STR(blocks, hash, s);
set_blockdiff(work);
if (deleted_block)
applog(LOG_DEBUG, "Deleted block %d from database",
deleted_block);
}
wr_unlock(&blk_lock);
if (!ret)
set_curblock(hexstr, bedata);
if (deleted_block)
applog(LOG_DEBUG, "Deleted block %d from database", deleted_block);
return ret;
}
if (work->mandatory)
return ret;
cg_wlock(&pool->data_lock);
if (pool->swork.clean) {
pool->swork.clean = false;
work->longpoll = true;
}
if (pool->current_height != height) {
pool->current_height = height;
}
cg_wunlock(&pool->data_lock);
work->work_block = ++work_block;
if (work->longpoll) {
if (work->stratum) {
applog(LOG_NOTICE, "Stratum from pool %d detected new block
at height %d",
pool->pool_no, height);
} else {
applog(LOG_NOTICE, "%sLONGPOLL from pool %d detected new
block at height %d",
work->gbt ? "GBT " : "", pool->pool_no, height);
}
} else if (have_longpoll && !pool->gbt_solo)
applog(LOG_NOTICE, "New block detected on network before pool
notification from pool %d at height %d",
pool->pool_no, height);
else if (!pool->gbt_solo)
applog(LOG_NOTICE, "New block detected on network from pool %d at
height %d",
pool->pool_no, height);
restart_threads();
} else {
if (memcmp(pool->prev_block, bedata, 32)) {
/* Work doesn't match what this pool has stored as
* prev_block. Let's see if the work is from an old
* block or the pool is just learning about a new
* block. */
if (memcmp(bedata, current_block, 32)) {
/* Doesn't match current block. It's stale */
applog(LOG_DEBUG, "Stale data from pool %d at height %d",
pool->pool_no, height);
ret = false;
} else {
/* Work is from new block and pool is up now
* current. */
applog(LOG_INFO, "Pool %d now up to date at height %d",
pool->pool_no, height);
cg_memcpy(pool->prev_block, bedata, 32);
}
}
#if 0
/* This isn't ideal, this pool is still on an old block but
* accepting shares from it. To maintain fair work distribution
* we work on it anyway. */
if (memcmp(bedata, current_block, 32))
applog(LOG_DEBUG, "Pool %d still on old block", pool->pool_no);
#endif
if (work->longpoll) {
work->work_block = ++work_block;
if (shared_strategy() || work->pool == current_pool()) {
if (work->stratum) {
applog(LOG_NOTICE, "Stratum from pool %d requested
work restart",
pool->pool_no);
} else {
applog(LOG_NOTICE, "%sLONGPOLL from pool %d requested
work restart",
work->gbt ? "GBT " : "", pool->pool_no);
}
restart_threads();
}
}
}
out:
work->longpoll = false;
return ret;
}
mutex_lock(stgd_lock);
if (work_rollable(work))
staged_rollable++;
if (likely(!getq->frozen)) {
HASH_ADD_INT(staged_work, id, work);
HASH_SORT(staged_work, tv_sort);
} else
rc = false;
pthread_cond_broadcast(&getq->cond);
mutex_unlock(stgd_lock);
return rc;
}
#ifdef HAVE_CURSES
int curses_int(const char *query)
{
int ret;
char *cvar;
cvar = curses_input(query);
ret = atoi(cvar);
free(cvar);
return ret;
}
#endif
#ifdef HAVE_CURSES
static bool input_pool(bool live);
#endif
#ifdef HAVE_CURSES
static void display_pool_summary(struct pool *pool)
{
if (curses_active_locked()) {
wlog("Pool: %s\n", pool->rpc_url);
if (pool->solved)
wlog("SOLVED %d BLOCK%s!\n", pool->solved, pool->solved > 1 ? "S"
: "");
if (!pool->has_stratum)
wlog("%s own long-poll support\n", pool->hdr_path ? "Has" : "Does
not have");
wlog(" Work templates received: %d\n", pool->getwork_requested);
wlog(" Share submissions: %"PRId64"\n", pool->accepted + pool-
>rejected);
wlog(" Accepted shares: %"PRId64"\n", pool->accepted);
wlog(" Rejected shares: %"PRId64"\n", pool->rejected);
wlog(" Accepted difficulty shares: %1.f\n", pool->diff_accepted);
wlog(" Rejected difficulty shares: %1.f\n", pool->diff_rejected);
if (pool->accepted || pool->rejected)
wlog(" Reject ratio: %.1f%%\n", (double)(pool->rejected * 100) /
(double)(pool->accepted + pool->rejected));
/* We can't remove the memory used for this struct pool because there may
* still be work referencing it. We just remove it from the pools list */
void remove_pool(struct pool *pool)
{
int i, last_pool = total_pools - 1;
struct pool *other;
jedata = NULL;
while (jeptr) {
jenext = jeptr->next;
free(jeptr->buf);
free(jeptr);
jeptr = jenext;
}
}
while (*str) {
if (*str == '\\' || *str == '"')
*(ptr++) = '\\';
*(ptr++) = *(str++);
}
*ptr = '\0';
return buf;
}
if (pool->quota != 1) {
fprintf(fcfg, "%s\n\t{\n\t\t\"quota\" : \"%s%s%s%d;%s\",", i >
0 ? "," : "",
pool->rpc_proxy ? json_escape((char *)proxytype(pool-
>rpc_proxytype)) : "",
pool->rpc_proxy ? json_escape(pool->rpc_proxy) : "",
pool->rpc_proxy ? "|" : "",
pool->quota,
json_escape(pool->rpc_url));
} else {
fprintf(fcfg, "%s\n\t{\n\t\t\"url\" : \"%s%s%s%s\",", i > 0 ? ","
: "",
pool->rpc_proxy ? json_escape((char *)proxytype(pool-
>rpc_proxytype)) : "",
pool->rpc_proxy ? json_escape(pool->rpc_proxy) : "",
pool->rpc_proxy ? "|" : "",
json_escape(pool->rpc_url));
}
fprintf(fcfg, "\n\t\t\"user\" : \"%s\",", json_escape(pool->rpc_user));
fprintf(fcfg, "\n\t\t\"pass\" : \"%s\"\n\t}", json_escape(pool-
>rpc_pass));
}
fputs("\n]\n", fcfg);
if (opt->desc == opt_hidden)
continue;
if (carg)
fprintf(fcfg, ",\n\"%s\" : \"%s\"", p+2,
json_escape(carg));
continue;
}
}
free(name);
}
json_escape_free();
}
void zero_bestshare(void)
{
int i;
best_diff = 0;
memset(best_share, 0, 8);
suffix_string(best_diff, best_share, sizeof(best_share), 0);
void zero_stats(void)
{
int i;
cgtime(&total_tv_start);
copy_time(&tv_hashmeter, &total_tv_start);
total_rolling = 0;
rolling1 = 0;
rolling5 = 0;
rolling15 = 0;
total_mhashes_done = 0;
total_getworks = 0;
total_accepted = 0;
total_rejected = 0;
hw_errors = 0;
total_stale = 0;
total_discarded = 0;
local_work = 0;
total_go = 0;
total_ro = 0;
total_secs = 1.0;
total_diff1 = 0;
found_blocks = 0;
total_diff_accepted = 0;
total_diff_rejected = 0;
total_diff_stale = 0;
pool->getwork_requested = 0;
pool->accepted = 0;
pool->rejected = 0;
pool->stale_shares = 0;
pool->discarded_work = 0;
pool->getfail_occasions = 0;
pool->remotefail_occasions = 0;
pool->last_share_time = 0;
pool->diff1 = 0;
pool->diff_accepted = 0;
pool->diff_rejected = 0;
pool->diff_stale = 0;
pool->last_share_diff = 0;
}
zero_bestshare();
copy_time(&cgpu->dev_start_tv, &total_tv_start);
mutex_lock(&hash_lock);
cgpu->total_mhashes = 0;
cgpu->accepted = 0;
cgpu->rejected = 0;
cgpu->hw_errors = 0;
cgpu->utility = 0.0;
cgpu->last_share_pool_time = 0;
cgpu->diff1 = 0;
cgpu->diff_accepted = 0;
cgpu->diff_rejected = 0;
cgpu->last_share_diff = 0;
mutex_unlock(&hash_lock);
if (!ret)
applog(LOG_DEBUG, "Unable to set thread to high priority");
#else
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
#endif
}
if (!ret)
applog(LOG_INFO, "Unable to set thread to low priority");
#else
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST);
#endif
}
#ifdef HAVE_CURSES
static void display_pools(void)
{
struct pool *pool;
int selected, i;
char input;
opt_loginput = true;
immedok(logwin, true);
clear_logwin();
updated:
for (i = 0; i < total_pools; i++) {
pool = pools[i];
if (pool == current_pool())
wattron(logwin, A_BOLD);
if (pool->enabled != POOL_ENABLED)
wattron(logwin, A_DIM);
wlogprint("%d: ", pool->pool_no);
switch (pool->enabled) {
case POOL_ENABLED:
wlogprint("Enabled ");
break;
case POOL_DISABLED:
wlogprint("Disabled ");
break;
case POOL_REJECTING:
wlogprint("Rejecting ");
break;
}
wlogprint("%s Quota %d Prio %d: %s User:%s\n",
pool->idle? "Dead" : "Alive",
pool->quota,
pool->prio,
pool->rpc_url, pool->rpc_user);
wattroff(logwin, A_BOLD | A_DIM);
}
retry:
wlogprint("\nCurrent pool management strategy: %s\n",
strategies[pool_strategy].s);
if (pool_strategy == POOL_ROTATE)
wlogprint("Set to rotate every %d minutes\n", opt_rotate_period);
wlogprint("Pool [A]dd [R]emove [D]isable [E]nable [Q]uota change\n");
wlogprint("[C]hange management strategy [S]witch pool [I]nformation\n");
wlogprint("Or press any other key to continue\n");
logwin_update();
input = getch();
immedok(logwin, false);
opt_loginput = false;
}
immedok(logwin, false);
opt_loginput = false;
}
#endif
#ifdef HAVE_CURSES
static void set_options(void)
{
char input;
opt_loginput = true;
immedok(logwin, true);
clear_logwin();
retry:
wlogprint("[W]rite config file\n[C]gminer restart\n");
wlogprint("Select an option or any other key to return\n");
logwin_update();
input = getch();
default_save_file(filename);
snprintf(prompt, sizeof(prompt), "Config filename to write (Enter for
default) [%s]", filename);
str = curses_input(prompt);
if (strcmp(str, "-1")) {
struct stat statbuf;
strcpy(filename, str);
free(str);
if (!stat(filename, &statbuf)) {
wlogprint("File exists, overwrite?\n");
input = getch();
if (strncasecmp(&input, "y", 1))
goto retry;
}
}
else
free(str);
fcfg = fopen(filename, "w");
if (!fcfg) {
wlogprint("Cannot open or create file\n");
goto retry;
}
write_config(fcfg);
fclose(fcfg);
goto retry;
immedok(logwin, false);
opt_loginput = false;
}
#ifdef USE_USBUTILS
static void mt_enable(struct thr_info *mythr)
{
cgsem_post(&mythr->sem);
}
opt_loginput = true;
immedok(logwin, true);
clear_logwin();
retry:
enabled = 0;
disabled = 0;
zombie = 0;
total = 0;
blacklisted = 0;
rd_lock(&mining_thr_lock);
mt = mining_threads;
rd_unlock(&mining_thr_lock);
immedok(logwin, false);
opt_loginput = false;
}
#endif
RenameThread("Input");
if (!curses_active)
return NULL;
while (1) {
char input;
input = getch();
if (!strncasecmp(&input, "q", 1)) {
kill_work();
return NULL;
} else if (!strncasecmp(&input, "d", 1))
display_options();
else if (!strncasecmp(&input, "p", 1))
display_pools();
else if (!strncasecmp(&input, "s", 1))
set_options();
#ifdef USE_USBUTILS
else if (!strncasecmp(&input, "u", 1))
set_usb();
#endif
if (opt_realquiet) {
disable_curses();
break;
}
}
return NULL;
}
#endif
pthread_detach(pthread_self());
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
RenameThread("API");
set_lowprio();
api(api_thr_id);
PTH(mythr) = 0L;
return NULL;
}
/* Sole work devices are serialised wrt calling get_work so they report in on
* each pass through their scanhash function as well as in get_work whereas
* queued work devices work asynchronously so get them to report in and out
* only across get_work. */
static void thread_reportin(struct thr_info *thr)
{
thr->getwork = false;
cgtime(&thr->last);
thr->cgpu->status = LIFE_WELL;
thr->cgpu->device_last_well = time(NULL);
}
/* Tell the watchdog thread this thread is waiting on get work and should not
* be restarted */
static void thread_reportout(struct thr_info *thr)
{
thr->getwork = true;
cgtime(&thr->last);
thr->cgpu->status = LIFE_WELL;
thr->cgpu->device_last_well = time(NULL);
}
cgtime(&total_tv_end);
tv_tdiff = tdiff(&total_tv_end, &tv_hashmeter);
now_t = total_tv_end.tv_sec;
diff_t = now_t - hashdisplay_t;
if (diff_t >= opt_log_interval) {
alt_status ^= switch_status;
hashdisplay_t = now_t;
showlog = true;
} else if (thr_id < 0) {
/* hashmeter is called by non-mining threads in case nothing
* has reported in to allow hashrate to converge to zero , but
* we only update if it has been more than opt_log_interval */
return;
}
copy_time(&tv_hashmeter, &total_tv_end);
if (thr_id >= 0) {
struct thr_info *thr = get_thread(thr_id);
struct cgpu_info *cgpu = thr->cgpu;
double device_tdiff, thr_mhs;
mutex_lock(&hash_lock);
cgpu->total_mhashes += hashes_done;
decay_time(&cgpu->rolling, hashes_done, device_tdiff,
opt_log_interval);
decay_time(&cgpu->rolling1, hashes_done, device_tdiff, 60.0);
decay_time(&cgpu->rolling5, hashes_done, device_tdiff, 300.0);
decay_time(&cgpu->rolling15, hashes_done, device_tdiff, 900.0);
mutex_unlock(&hash_lock);
copy_time(&cgpu->last_message_tv, &total_tv_end);
decay_time(&cgpu->rolling, 0, device_tdiff, opt_log_interval);
decay_time(&cgpu->rolling1, 0, device_tdiff, 60.0);
decay_time(&cgpu->rolling5, 0, device_tdiff, 300.0);
decay_time(&cgpu->rolling15, 0, device_tdiff, 900.0);
}
mutex_unlock(&hash_lock);
}
mutex_lock(&hash_lock);
total_mhashes_done += hashes_done;
decay_time(&total_rolling, hashes_done, tv_tdiff, opt_log_interval);
decay_time(&rolling1, hashes_done, tv_tdiff, 60.0);
decay_time(&rolling5, hashes_done, tv_tdiff, 300.0);
decay_time(&rolling15, hashes_done, tv_tdiff, 900.0);
global_hashrate = llround(total_rolling) * 1000000;
total_secs = tdiff(&total_tv_end, &total_tv_start);
if (showlog) {
char displayed_hashes[16], displayed_rolling[16];
char displayed_r1[16], displayed_r5[16], displayed_r15[16];
uint64_t d64;
snprintf(statusline, sizeof(statusline),
"(%ds):%s (1m):%s (5m):%s (15m):%s (avg):%sh/s",
opt_log_interval, displayed_rolling, displayed_r1, displayed_r5,
displayed_r15, displayed_hashes);
}
mutex_unlock(&hash_lock);
#ifdef USE_LIBSYSTEMD
sd_notifyf(false, "STATUS=%s", statusline);
#endif
if (showlog) {
if (!curses_active) {
printf("%s \r", statusline);
fflush(stdout);
} else
applog(LOG_INFO, "%s", statusline);
}
}
/* Parses stratum json responses and tries to find the id that the request
* matched to and treat it accordingly. */
static bool parse_stratum_response(struct pool *pool, char *s)
{
json_t *val = NULL, *err_val, *res_val, *id_val;
struct stratum_share *sshare;
json_error_t err;
bool ret = false;
int id;
if (json_is_null(id_val) || !id_val) {
char *ss;
if (err_val)
ss = json_dumps(err_val, JSON_INDENT(3));
else
ss = strdup("(unknown reason)");
free(ss);
goto out;
}
id = json_integer_value(id_val);
mutex_lock(&sshare_lock);
HASH_FIND_INT(stratum_shares, &id, sshare);
if (sshare) {
HASH_DEL(stratum_shares, sshare);
pool->sshares--;
}
mutex_unlock(&sshare_lock);
if (!sshare) {
double pool_diff;
if (!res_val)
goto out;
/* Since the share is untracked, we can only guess at what the
* work difficulty is based on the current pool diff. */
cg_rlock(&pool->data_lock);
pool_diff = pool->sdiff;
cg_runlock(&pool->data_lock);
if (json_is_true(res_val)) {
applog(LOG_NOTICE, "Accepted untracked stratum share from pool
%d", pool->pool_no);
mutex_lock(&stats_lock);
total_rejected++;
pool->rejected++;
total_diff_rejected += pool_diff;
pool->diff_rejected += pool_diff;
mutex_unlock(&stats_lock);
}
goto out;
}
stratum_share_result(val, res_val, err_val, sshare);
free_work(sshare->work);
free(sshare);
ret = true;
out:
if (val)
json_decref(val);
return ret;
}
mutex_lock(&sshare_lock);
HASH_ITER(hh, stratum_shares, sshare, tmpshare) {
if (sshare->work->pool == pool) {
HASH_DEL(stratum_shares, sshare);
diff_cleared += sshare->work->work_difficulty;
free_work(sshare->work);
pool->sshares--;
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 += cleared;
total_stale += cleared;
pool->diff_stale += diff_cleared;
total_diff_stale += diff_cleared;
}
}
mutex_lock(stgd_lock);
HASH_ITER(hh, staged_work, work, tmp) {
if (work->pool == pool) {
HASH_DEL(staged_work, work);
free_work(work);
cleared++;
}
}
mutex_unlock(stgd_lock);
if (cleared)
applog(LOG_INFO, "Cleared %d work items due to stratum disconnect on
pool %d", cleared, pool->pool_no);
}
cg_rlock(&control_lock);
prio = currentpool->prio;
cg_runlock(&control_lock);
return prio;
}
if (pool->enabled != POOL_ENABLED)
return false;
cp = current_pool();
if (cp == pool)
return true;
/* If we're waiting for a response from shares submitted, keep the
* connection open. */
if (pool->sshares)
return true;
/* If the pool has only just come to life and is higher priority than
* the current pool keep the connection open so we can fail back to
* it. */
if (pool_strategy == POOL_FAILOVER && pool->prio < cp_prio())
return true;
/* We've run out of work, bring anything back to life. */
if (no_work)
return true;
return false;
}
cg_rlock(&pool->data_lock);
ret = (pool->sessionid != NULL);
cg_runlock(&pool->data_lock);
return ret;
}
/* One stratum receive thread per pool that has stratum waits on the socket
* checking 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 side will eventually expire data it fails to send. */
static void *stratum_rthread(void *userdata)
{
struct pool *pool = (struct pool *)userdata;
char threadname[16];
pthread_detach(pthread_self());
while (42) {
struct timeval timeout;
int sel_ret;
fd_set rd;
char *s;
if (unlikely(pool->removed)) {
suspend_stratum(pool);
break;
}
wait_lpcurrent(pool);
while (!restart_stratum(pool)) {
pool_died(pool);
if (pool->removed)
goto out;
cgsleep_ms(5000);
}
}
FD_ZERO(&rd);
FD_SET(pool->sock, &rd);
timeout.tv_sec = 90;
timeout.tv_usec = 0;
while (!restart_stratum(pool)) {
pool_died(pool);
if (pool->removed)
goto out;
cgsleep_ms(5000);
}
continue;
}
/* Check this pool hasn't died while being a backup pool and
* has not had its idle flag cleared */
stratum_resumed(pool);
out:
return NULL;
}
/* Each pool has one stratum send thread for sending shares to avoid many
* threads being created for submission since all sends need to be serialised
* anyway. */
static void *stratum_sthread(void *userdata)
{
struct pool *pool = (struct pool *)userdata;
uint64_t last_nonce2 = 0;
uint32_t last_nonce = 0;
char threadname[16];
pthread_detach(pthread_self());
pool->stratum_q = tq_new();
if (!pool->stratum_q)
quit(1, "Failed to create stratum_q in stratum_sthread");
while (42) {
char noncehex[12], nonce2hex[20], s[1024];
struct stratum_share *sshare;
uint32_t *hash32, nonce;
unsigned char nonce2[8];
uint64_t *nonce2_64;
struct work *work;
bool submitted;
if (unlikely(pool->removed))
break;
work = tq_pop(pool->stratum_q);
if (unlikely(!work))
quit(1, "Stratum q returned empty work");
sshare->sshare_time = time(NULL);
/* This work item is freed in parse_stratum_response */
sshare->work = work;
memset(s, 0, 1024);
mutex_lock(&sshare_lock);
/* Give the stratum share a unique id */
sshare->id = swork_id++;
mutex_unlock(&sshare_lock);
if (pool->vmask) {
snprintf(s, sizeof(s),
"{\"params\":
[\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\"], \"id\":
%d, \"method\": \"mining.submit\"}",
pool->rpc_user, work->job_id, nonce2hex, work->ntime,
noncehex, pool->vmask_002[work->micro_job_id], sshare->id);
} else {
snprintf(s, sizeof(s),
"{\"params\":
[\"%s\", \"%s\", \"%s\", \"%s\", \"%s\"], \"id\":
%d, \"method\": \"mining.submit\"}",
pool->rpc_user, work->job_id, nonce2hex, work->ntime,
noncehex, sshare->id);
}
if (likely(stratum_send(pool, s, strlen(s)))) {
mutex_lock(&sshare_lock);
HASH_ADD_INT(stratum_shares, id, sshare);
pool->sshares++;
mutex_unlock(&sshare_lock);
if (pool_tclear(pool, &pool->submit_fail))
applog(LOG_WARNING, "Pool %d communication
resumed, submitting work", pool->pool_no);
applog(LOG_DEBUG, "Successfully submitted, adding to
stratum_shares db");
submitted = true;
break;
}
if (!pool_tset(pool, &pool->submit_fail) && cnx_needed(pool)) {
applog(LOG_WARNING, "Pool %d stratum share submission
failure", pool->pool_no);
total_ro++;
pool->remotefail_occasions++;
}
if (opt_lowmem) {
applog(LOG_DEBUG, "Lowmem option prevents resubmitting
stratum share");
break;
}
cg_rlock(&pool->data_lock);
sessionid_match = (pool->nonce1 && !strcmp(work->nonce1, pool-
>nonce1));
cg_runlock(&pool->data_lock);
if (!sessionid_match) {
applog(LOG_DEBUG, "No matching session id for resubmitting
stratum share");
break;
}
/* Retry every 5 seconds */
sleep(5);
}
if (unlikely(!submitted)) {
applog(LOG_DEBUG, "Failed to submit stratum share, discarding");
free_work(work);
free(sshare);
pool->stale_shares++;
total_stale++;
} else {
int ssdiff;
sshare->sshare_sent = time(NULL);
ssdiff = sshare->sshare_sent - sshare->sshare_time;
if (opt_debug || ssdiff > 0) {
applog(LOG_INFO, "Pool %d stratum share submission lag time
%d seconds",
pool->pool_no, ssdiff);
}
}
}
/* Freeze the work queue but don't free up its memory in case there is
* work still trying to be submitted to the removed pool. */
tq_freeze(pool->stratum_q);
return NULL;
}
if (!initiate_stratum(pool))
return false;
return true;
}
#ifdef HAVE_LIBCURL
static void __setup_gbt_solo(struct pool *pool)
{
cg_wlock(&pool->gbt_lock);
cg_memcpy(pool->coinbase, scriptsig_header_bin, 41);
pool->coinbase[41 + pool->n1_len + 4 + 1 + 8] = 25;
cg_memcpy(pool->coinbase + 41 + pool->n1_len + 4 + 1 + 8 + 1, pool-
>script_pubkey, 25);
cg_wunlock(&pool->gbt_lock);
}
if (!opt_btc_address) {
if (!opt_decode) {
applog(LOG_ERR, "No BTC address specified, unable to mine solo on
%s",
pool->rpc_url);
}
goto out;
}
snprintf(s, 256, "{\"id\": 1, \"method\": \"validateaddress\", \"params\":
[\"%s\"]}\n", opt_btc_address);
val = json_rpc_call(curl, pool->rpc_url, pool->rpc_userpass, s, true,
false, &rolltime, pool, false);
if (!val)
goto out;
res_val = json_object_get(val, "result");
if (!res_val)
goto out;
valid_val = json_object_get(res_val, "isvalid");
if (!valid_val)
goto out;
if (!json_is_true(valid_val)) {
applog(LOG_ERR, "Bitcoin address %s is NOT valid", opt_btc_address);
goto out;
}
applog(LOG_NOTICE, "Solo mining to valid address: %s", opt_btc_address);
ret = true;
address_to_pubkeyhash(pool->script_pubkey, opt_btc_address);
hex2bin(scriptsig_header_bin, scriptsig_header, 41);
__setup_gbt_solo(pool);
if (opt_debug) {
char *cb = bin2hex(pool->coinbase, pool->coinbase_len);
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);
if (!init) {
bool ret = initiate_stratum(pool) && auth_stratum(pool);
if (ret)
init_stratum_threads(pool);
else
pool_tclear(pool, &pool->stratum_init);
return ret;
}
return pool->stratum_active;
}
curl = curl_easy_init();
if (unlikely(!curl)) {
applog(LOG_ERR, "CURL initialisation failed");
return false;
}
if (!gbt_check_rules(rules_arr, gbt_understood_rules)) {
applog(LOG_DEBUG, "Not all rules understood for GBT");
json_decref(val);
val = NULL;
}
}
if (!val) {
json_t *rules_arr;
if (json_is_string(arrval)) {
const char *mutable = json_string_value(arrval);
if (pool->has_gbt)
applog(LOG_DEBUG, "GBT coinbase + append support found, switching
to GBT protocol");
else if (pool->gbt_solo)
applog(LOG_DEBUG, "GBT coinbase without append found, switching
to GBT solo protocol");
else
applog(LOG_DEBUG, "No GBT coinbase + append support found, pool
unusable if it has no stratum");
}
cgtime(&tv_getwork);
val = json_rpc_call(curl, pool->rpc_url, pool->rpc_userpass,
pool->rpc_req, true, false, &rolltime, pool, false);
cgtime(&tv_getwork_reply);
goto retry_stratum;
}
stage_work(work);
total_getworks++;
pool->getwork_requested++;
ret = true;
} else {
applog(LOG_DEBUG, "Successfully retrieved but FAILED to decipher
work from pool %u %s",
pool->pool_no, pool->rpc_url);
free_work(work);
}
if (pool->lp_url)
goto out;
hdr_path = pool->hdr_path;
if (strstr(hdr_path, "://")) {
pool->lp_url = hdr_path;
hdr_path = NULL;
} else {
/* absolute path, on current server */
copy_start = (*hdr_path == '/') ? (hdr_path + 1) :
hdr_path;
if (pool->rpc_url[strlen(pool->rpc_url) - 1] != '/')
need_slash = true;
pool_start_lp(pool);
} else {
applog(LOG_DEBUG, "FAILED to retrieve work from pool %u %s",
pool->pool_no, pool->rpc_url);
if (!pinging && !pool->idle)
applog(LOG_WARNING, "Pool %u slow/down or URL or credentials
invalid", pool->pool_no);
}
out:
if (val)
json_decref(val);
curl_easy_cleanup(curl);
return ret;
}
/* If this is called non_blocking, it will return NULL for work so that must
* be handled. */
static struct work *hash_pop(bool blocking)
{
struct work *work = NULL, *tmp;
int hc;
mutex_lock(stgd_lock);
if (!HASH_COUNT(staged_work)) {
work_emptied = true;
if (!blocking)
goto out_unlock;
do {
struct timespec abstime, tdiff = {10, 0};
int rc;
cgcond_time(&abstime);
timeraddspec(&abstime, &tdiff);
pthread_cond_signal(&gws_cond);
rc = pthread_cond_timedwait(&getq->cond, stgd_lock, &abstime);
/* Check again for !no_work as multiple threads may be
* waiting on this condition and another may set the
* bool separately. */
if (rc && !no_work) {
no_work = true;
applog(LOG_WARNING, "Waiting for work to be available from
pools.");
}
} while (!HASH_COUNT(staged_work));
}
if (no_work) {
applog(LOG_WARNING, "Work available from pools, resuming.");
no_work = false;
}
hc = HASH_COUNT(staged_work);
/* Find clone work if possible, to allow masters to be reused */
if (hc > staged_rollable) {
HASH_ITER(hh, staged_work, work, tmp) {
if (!work_rollable(work))
break;
}
} else
work = staged_work;
HASH_DEL(staged_work, work);
if (work_rollable(work))
staged_rollable--;
return work;
}
static void gen_hash(unsigned char *data, unsigned char *hash, int len)
{
unsigned char hash1[32];
if (unlikely(diff == 0.0)) {
/* This shouldn't happen but best we check to prevent a crash */
applog(LOG_ERR, "Diff zero passed to set_target");
diff = 1.0;
}
d64 = truediffone;
d64 /= diff;
h64 = d64;
data64 = (uint64_t *)(target);
*data64 = htole64(h64);
if (opt_debug) {
char *htarget = bin2hex(target, 32);
cg_wlock(&pool->data_lock);
pool->nonce2 = nonce2;
cg_wunlock(&pool->data_lock);
gen_stratum_work(pool, work);
roll_work_ntime(work, ntime);
work->pool = real_pool;
/* Inherit the sdiff from the original stratum */
work->sdiff = pool->sdiff;
work->thr_id = thr_id;
work->work_block = work_block;
work->pool->works++;
work->mined = true;
work->device_diff = MIN(drv->max_diff, work->work_difficulty);
work->device_diff = MAX(drv->min_diff, work->device_diff);
/* Generates stratum based work based on the most recent notify information
* from the pool. This will keep generating work while a pool is down so we use
* other means to detect when the pool has died in stratum_thread */
static void gen_stratum_work(struct pool *pool, struct work *work)
{
unsigned char merkle_root[32], merkle_sha[64];
uint32_t *data32, *swap32;
uint64_t nonce2le;
int i;
cg_wlock(&pool->data_lock);
/* Store the stratum work diff to check it still matches the pool's
* stratum diff when submitting shares */
work->sdiff = pool->sdiff;
if (opt_debug) {
char *header, *merkle_hash;
calc_midstate(pool, work);
set_target(work->target, work->sdiff);
local_work++;
work->pool = pool;
work->stratum = true;
work->nonce = 0;
work->longpoll = false;
work->getwork_mode = GETWORK_MODE_STRATUM;
work->work_block = work_block;
/* Nominally allow a driver to ntime roll 60 seconds */
work->drv_rolllimit = 60;
calc_diff(work, work->sdiff);
cgtime(&work->tv_staged);
}
#ifdef HAVE_LIBCURL
static void gen_solo_work(struct pool *pool, struct work *work);
/* Use the one instance of gbt_curl, protecting the bool with the gbt_lock but
* avoiding holding the lock once we've set the bool. */
static void get_gbt_curl(struct pool *pool, int poll)
{
cg_ilock(&pool->gbt_lock);
while (pool->gbt_curl_inuse) {
cg_uilock(&pool->gbt_lock);
cgsleep_ms(poll);
cg_ilock(&pool->gbt_lock);
}
cg_ulock(&pool->gbt_lock);
pool->gbt_curl_inuse = true;
cg_wunlock(&pool->gbt_lock);
}
get_gbt_curl(pool, 10);
retry:
/* Bitcoind doesn't like many open RPC connections. */
curl_easy_setopt(pool->gbt_curl, CURLOPT_FORBID_REUSE, 1);
val = json_rpc_call(pool->gbt_curl, pool->rpc_url, pool->rpc_userpass, pool-
>rpc_req,
true, false, &rolltime, pool, false);
if (likely(val)) {
bool rc = work_decode(pool, work, val);
if (rc) {
__setup_gbt_solo(pool);
gen_solo_work(pool, work);
stage_work(work);
} else
free_work(work);
json_decref(val);
} else {
applog(LOG_DEBUG, "Pool %d json_rpc_call failed on get gbt, retrying in
5s",
pool->pool_no);
if (++pool->seq_getfails > 5) {
pool_died(pool);
goto out;
}
cgsleep_ms(5000);
goto retry;
}
out:
release_gbt_curl(pool);
}
cgtime(&now);
if (now.tv_sec - pool->tv_lastwork.tv_sec > 60)
update_gbt_solo(pool);
cg_wlock(&pool->gbt_lock);
work->sdiff = pool->sdiff;
if (opt_debug) {
char *header, *merkle_hash;
calc_midstate(pool, work);
local_work++;
work->gbt = true;
work->pool = pool;
work->nonce = 0;
work->longpoll = false;
work->getwork_mode = GETWORK_MODE_SOLO;
work->work_block = work_block;
/* Nominally allow a driver to ntime roll 60 seconds */
work->drv_rolllimit = 60;
calc_diff(work, work->sdiff);
cgtime(&work->tv_staged);
}
#endif
/* The time difference in seconds between when this device last got work via
* get_work() and generated a valid share. */
int share_work_tdiff(struct cgpu_info *cgpu)
{
return last_getwork - cgpu->last_device_valid_work;
}
thread_reportout(thr);
applog(LOG_DEBUG, "Popping work from get queue to get work");
diff_t = time(NULL);
while (!work) {
work = hash_pop(true);
if (stale_work(work, false)) {
discard_work(work);
wake_gws();
}
}
diff_t = time(NULL) - diff_t;
/* Since this is a blocking function, we need to add grace time to
* the device's last valid work to not make outages appear to be
* device failures. */
if (diff_t > 0) {
applog(LOG_DEBUG, "Get work blocked for %d seconds", (int)diff_t);
cgpu->last_device_valid_work += diff_t;
}
applog(LOG_DEBUG, "Got work from get queue to get work for thread %d",
thr_id);
work->thr_id = thr_id;
if (opt_benchmark)
set_benchmark_work(cgpu, work);
thread_reportin(thr);
work->mined = true;
work->device_diff = MIN(cgpu->drv->max_diff, work->work_difficulty);
work->device_diff = MAX(cgpu->drv->min_diff, work->device_diff);
return work;
}
cgtime(&work->tv_work_found);
if (opt_benchmark) {
struct cgpu_info *cgpu = get_thr_cgpu(work->thr_id);
mutex_lock(&stats_lock);
cgpu->accepted++;
total_accepted++;
pool->accepted++;
cgpu->diff_accepted += work->work_difficulty;
total_diff_accepted += work->work_difficulty;
pool->diff_accepted += work->work_difficulty;
mutex_unlock(&stats_lock);
if (stale_work(work, true)) {
if (opt_submit_stale)
applog(LOG_NOTICE, "Pool %d stale share detected, submitting as
user requested", pool->pool_no);
else if (pool->submit_old)
applog(LOG_NOTICE, "Pool %d stale share detected, submitting as
pool requested", pool->pool_no);
else {
applog(LOG_NOTICE, "Pool %d stale share detected, discarding",
pool->pool_no);
sharelog("discard", work);
mutex_lock(&stats_lock);
total_stale++;
pool->stale_shares++;
total_diff_stale += work->work_difficulty;
pool->diff_stale += work->work_difficulty;
mutex_unlock(&stats_lock);
free_work(work);
return;
}
work->stale = true;
}
if (work->stratum) {
applog(LOG_DEBUG, "Pushing pool %d work to stratum queue", pool-
>pool_no);
if (unlikely(!pool->stratum_q || !tq_push(pool->stratum_q, work))) {
applog(LOG_DEBUG, "Discarding work from removed pool");
free_work(work);
}
} else {
applog(LOG_DEBUG, "Pushing submit work to work thread");
if (unlikely(pthread_create(&submit_thread, NULL, submit_work_thread,
(void *)work)))
quit(1, "Failed to create submit_work_thread");
}
}
mutex_lock(&stats_lock);
hw_errors++;
thr->cgpu->hw_errors++;
mutex_unlock(&stats_lock);
thr->cgpu->drv->hw_error(thr);
}
/* Fills in the work nonce and builds the output data in work->hash */
static void rebuild_nonce(struct work *work, uint32_t nonce)
{
uint32_t *work_nonce = (uint32_t *)(work->data + 64 + 12);
*work_nonce = htole32(nonce);
regen_hash(work);
}
rebuild_nonce(work, nonce);
return (*hash_32 == 0);
}
rebuild_nonce(work, nonce);
diff64 = 0x00000000ffff0000ULL;
diff64 /= diff;
work->share_diff = share_diff(work);
mutex_lock(&stats_lock);
total_diff1 += work->device_diff;
thr->cgpu->diff1 += work->device_diff;
work->pool->diff1 += work->device_diff;
thr->cgpu->last_device_valid_work = time(NULL);
mutex_unlock(&stats_lock);
}
/* To be used once the work has been tested to be meet diff1 and has had its
* nonce adjusted. Returns true if the work target is met. */
bool submit_tested_work(struct thr_info *thr, struct work *work)
{
struct work *work_out;
update_work_stats(thr, work);
if (!fulltest(work->hash, work->target)) {
applog(LOG_INFO, "%s %d: Share above target", thr->cgpu->drv->name,
thr->cgpu->device_id);
return false;
}
work_out = copy_work(work);
submit_work_async(work_out);
return true;
}
/* Rudimentary test to see if cgpu has returned the same nonce twice in a row which
is
* always going to be a duplicate which should be reported as a hw error. */
static bool new_nonce(struct thr_info *thr, uint32_t nonce)
{
struct cgpu_info *cgpu = thr->cgpu;
if (unlikely(cgpu->last_nonce == nonce)) {
applog(LOG_INFO, "%s %d duplicate share detected as HW error",
cgpu->drv->name, cgpu->device_id);
return false;
}
cgpu->last_nonce = nonce;
return true;
}
/* Returns true if nonce for work was a valid share and not a dupe of the very last
* nonce submitted by this device. */
bool submit_nonce(struct thr_info *thr, struct work *work, uint32_t nonce)
{
if (new_nonce(thr, nonce) && test_nonce(work, nonce))
submit_tested_work(thr, work);
else {
inc_hw_errors(thr);
return false;
}
return true;
}
/* Allows drivers to submit work items where the driver has changed the ntime
* value by noffset. Must be only used with a work protocol that does not ntime
* roll itself intrinsically to generate work (eg stratum). We do not touch
* the original work struct, but the copy of it only. */
bool submit_noffset_nonce(struct thr_info *thr, struct work *work_in, uint32_t
nonce,
int noffset)
{
struct work *work = make_work();
bool ret = false;
ret = true;
if (!fulltest(work->hash, work->target)) {
free_work(work);
applog(LOG_INFO, "%s %d: Share above target", thr->cgpu->drv->name,
thr->cgpu->device_id);
goto out;
}
submit_work_async(work);
out:
return ret;
}
static inline bool abandon_work(struct work *work, struct timeval *wdiff, uint64_t
hashes)
{
if (wdiff->tv_sec > max_scantime || hashes >= 0xfffffffe ||
stale_work(work, false))
return true;
return false;
}
/* The main hashing loop for devices that are slow enough to work on one work
* item at a time, without a queue, aborting work before the entire nonce
* range has been hashed if needed. */
static void hash_sole_work(struct thr_info *mythr)
{
const int thr_id = mythr->id;
struct cgpu_info *cgpu = mythr->cgpu;
struct device_drv *drv = cgpu->drv;
struct timeval getwork_start, tv_start, *tv_end, tv_workstart, tv_lastupdate;
struct cgminer_stats *dev_stats = &(cgpu->cgminer_stats);
struct cgminer_stats *pool_stats;
/* Try to cycle approximately 5 times before each log update */
const long cycle = opt_log_interval / 5 ? : 1;
const bool primary = (!mythr->device_thread) || mythr->primary_thread;
struct timeval diff, sdiff, wdiff = {0, 0};
uint32_t max_nonce = drv->can_limit_work(mythr);
int64_t hashes_done = 0;
tv_end = &getwork_start;
cgtime(&getwork_start);
sdiff.tv_sec = sdiff.tv_usec = 0;
cgtime(&tv_lastupdate);
while (likely(!cgpu->shutdown)) {
struct work *work = get_work(mythr, thr_id);
int64_t hashes;
mythr->work_restart = false;
cgpu->new_work = true;
cgtime(&tv_workstart);
work->nonce = 0;
cgpu->max_hashes = 0;
if (!drv->prepare_work(mythr, work)) {
applog(LOG_ERR, "work prepare failed, exiting "
"mining thread %d", thr_id);
break;
}
work->device_diff = MIN(drv->max_diff, work->work_difficulty);
work->device_diff = MAX(drv->min_diff, work->device_diff);
do {
cgtime(&tv_start);
subtime(&tv_start, &getwork_start);
addtime(&getwork_start, &dev_stats->getwork_wait);
if (time_more(&getwork_start, &dev_stats->getwork_wait_max))
copy_time(&dev_stats->getwork_wait_max, &getwork_start);
if (time_less(&getwork_start, &dev_stats->getwork_wait_min))
copy_time(&dev_stats->getwork_wait_min, &getwork_start);
dev_stats->getwork_calls++;
pool_stats = &(work->pool->cgminer_stats);
addtime(&getwork_start, &pool_stats->getwork_wait);
if (time_more(&getwork_start, &pool_stats->getwork_wait_max))
copy_time(&pool_stats->getwork_wait_max, &getwork_start);
if (time_less(&getwork_start, &pool_stats->getwork_wait_min))
copy_time(&pool_stats->getwork_wait_min, &getwork_start);
pool_stats->getwork_calls++;
cgtime(&(work->tv_work_start));
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_testcancel();
/* tv_end is == &getwork_start */
cgtime(&getwork_start);
if (unlikely(hashes == -1)) {
applog(LOG_ERR, "%s %d failure, disabling!", drv->name,
cgpu->device_id);
cgpu->deven = DEV_DISABLED;
dev_error(cgpu, REASON_THREAD_ZERO_HASH);
cgpu->shutdown = true;
break;
}
hashes_done += hashes;
if (hashes > cgpu->max_hashes)
cgpu->max_hashes = hashes;
if (likely(max_nonce == 0xffffffff))
continue;
if (unlikely(mythr->work_restart)) {
/* Apart from device_thread 0, we stagger the
* starting of every next thread to try and get
* all devices busy before worrying about
* getting work for their extra threads */
if (!primary) {
struct timespec rgtp;
rgtp.tv_sec = 0;
rgtp.tv_nsec = 250 * mythr->device_thread * 1000000;
nanosleep(&rgtp, NULL);
}
break;
}
sdiff.tv_sec = sdiff.tv_usec = 0;
} while (!abandon_work(work, &wdiff, cgpu->max_hashes));
free_work(work);
}
cgpu->deven = DEV_DISABLED;
}
wr_lock(&cgpu->qlock);
/* Check we haven't grabbed work somehow between
* checking and picking up the lock. */
if (likely(!cgpu->unqueued_work))
cgpu->unqueued_work = work;
else
need_work = false;
wr_unlock(&cgpu->qlock);
if (unlikely(!need_work))
discard_work(work);
}
/* The queue_full function should be used by the driver to
* actually place work items on the physical device if it
* does have a queue. */
} while (!drv->queue_full(cgpu));
}
if (cgpu->unqueued_work) {
work = cgpu->unqueued_work;
if (unlikely(stale_work(work, false))) {
discard_work(work);
} else
__add_queued(cgpu, work);
cgpu->unqueued_work = NULL;
wake_gws();
}
return work;
}
/* This function is for retrieving one work item from the unqueued pointer and
* adding it to the hashtable of queued work. Code using this function must be
* able to handle NULL as a return which implies there is no work available. */
struct work *get_queued(struct cgpu_info *cgpu)
{
struct work *work;
wr_lock(&cgpu->qlock);
work = __get_queued(cgpu);
wr_unlock(&cgpu->qlock);
return work;
}
add_queued(cgpu, work);
return work;
}
/* This function is for finding an already queued work item in the
* given que hashtable. Code using this function must be able
* to handle NULL as a return which implies there is no matching work.
* The calling function must lock access to the que if it is required.
* The common values for midstatelen, offset, datalen are 32, 64, 12 */
struct work *__find_work_bymidstate(struct work *que, char *midstate, size_t
midstatelen, char *data, int offset, size_t datalen)
{
struct work *work, *tmp, *ret = NULL;
return ret;
}
rd_lock(&cgpu->qlock);
ret = __find_work_bymidstate(cgpu->queued_work, midstate, midstatelen, data,
offset, datalen);
rd_unlock(&cgpu->qlock);
return ret;
}
rd_lock(&cgpu->qlock);
work = __find_work_bymidstate(cgpu->queued_work, midstate, midstatelen, data,
offset, datalen);
if (work)
ret = copy_work(work);
rd_unlock(&cgpu->qlock);
return ret;
}
rd_lock(&cgpu->qlock);
ret = __find_work_byid(cgpu->queued_work, id);
rd_unlock(&cgpu->qlock);
return ret;
}
rd_lock(&cgpu->qlock);
work = __find_work_byid(cgpu->queued_work, id);
if (work)
ret = copy_work(work);
rd_unlock(&cgpu->qlock);
return ret;
}
/* This iterates over a queued hashlist finding work started more than secs
* seconds ago and discards the work as completed. The driver must set the
* work->tv_work_start value appropriately. Returns the number of items aged. */
int age_queued_work(struct cgpu_info *cgpu, double secs)
{
struct work *work, *tmp;
struct timeval tv_now;
int aged = 0;
cgtime(&tv_now);
wr_lock(&cgpu->qlock);
HASH_ITER(hh, cgpu->queued_work, work, tmp) {
if (tdiff(&tv_now, &work->tv_work_start) > secs) {
__work_completed(cgpu, work);
free_work(work);
aged++;
}
}
wr_unlock(&cgpu->qlock);
return aged;
}
/* This function should be used by queued device drivers when they're sure
* the work struct is no longer in use. */
void work_completed(struct cgpu_info *cgpu, struct work *work)
{
wr_lock(&cgpu->qlock);
__work_completed(cgpu, work);
wr_unlock(&cgpu->qlock);
free_work(work);
}
wr_lock(&cgpu->qlock);
work = __find_work_bymidstate(cgpu->queued_work, midstate, midstatelen, data,
offset, datalen);
if (work)
__work_completed(cgpu, work);
wr_unlock(&cgpu->qlock);
return work;
}
if (unlikely(!cgpu))
return;
if (work) {
free_work(work);
applog(LOG_DEBUG, "Discarded queued work item");
}
}
/* This version of hash work is for devices that are fast enough to always
* perform a full nonce range and need a queue to maintain the device busy.
* Work creation and destruction is not done from within this function
* directly. */
void hash_queued_work(struct thr_info *mythr)
{
struct timeval tv_start = {0, 0}, tv_end;
struct cgpu_info *cgpu = mythr->cgpu;
struct device_drv *drv = cgpu->drv;
const int thr_id = mythr->id;
int64_t hashes_done = 0;
while (likely(!cgpu->shutdown)) {
struct timeval diff;
int64_t hashes;
hashes = drv->scanwork(mythr);
if (unlikely(hashes == -1 )) {
applog(LOG_ERR, "%s %d failure, disabling!", drv->name, cgpu-
>device_id);
cgpu->deven = DEV_DISABLED;
dev_error(cgpu, REASON_THREAD_ZERO_HASH);
break;
}
hashes_done += hashes;
cgtime(&tv_end);
timersub(&tv_end, &tv_start, &diff);
/* Update the hashmeter at most 5 times per second */
if ((hashes_done && (diff.tv_sec > 0 || diff.tv_usec > 200000)) ||
diff.tv_sec >= opt_log_interval) {
hashmeter(thr_id, hashes_done);
hashes_done = 0;
copy_time(&tv_start, &tv_end);
}
if (mythr->work_update) {
drv->update_work(cgpu);
mythr->work_update = false;
}
}
cgpu->deven = DEV_DISABLED;
}
/* This version of hash_work is for devices drivers that want to do their own
* work management entirely, usually by using get_work(). Note that get_work
* is a blocking function and will wait indefinitely if no work is available
* so this must be taken into consideration in the driver. */
void hash_driver_work(struct thr_info *mythr)
{
struct timeval tv_start = {0, 0}, tv_end;
struct cgpu_info *cgpu = mythr->cgpu;
struct device_drv *drv = cgpu->drv;
const int thr_id = mythr->id;
int64_t hashes_done = 0;
while (likely(!cgpu->shutdown)) {
struct timeval diff;
int64_t hashes;
hashes = drv->scanwork(mythr);
if (unlikely(hashes == -1 )) {
applog(LOG_ERR, "%s %d failure, disabling!", drv->name, cgpu-
>device_id);
cgpu->deven = DEV_DISABLED;
dev_error(cgpu, REASON_THREAD_ZERO_HASH);
break;
}
hashes_done += hashes;
cgtime(&tv_end);
timersub(&tv_end, &tv_start, &diff);
/* Update the hashmeter at most 5 times per second */
if ((hashes_done && (diff.tv_sec > 0 || diff.tv_usec > 200000)) ||
diff.tv_sec >= opt_log_interval) {
hashmeter(thr_id, hashes_done);
hashes_done = 0;
copy_time(&tv_start, &tv_end);
}
if (mythr->work_update) {
drv->update_work(cgpu);
mythr->work_update = false;
}
}
cgpu->deven = DEV_DISABLED;
}
thread_reportout(mythr);
if (!drv->thread_init(mythr)) {
dev_error(cgpu, REASON_THREAD_FAIL_INIT);
goto out;
}
cgpu->last_device_valid_work = time(NULL);
drv->hash_work(mythr);
drv->thread_shutdown(mythr);
out:
return NULL;
}
enum {
STAT_SLEEP_INTERVAL = 1,
STAT_CTR_INTERVAL = 10000000,
FAILURE_INTERVAL = 30,
};
#ifdef HAVE_LIBCURL
/* Stage another work item from the work returned in a longpoll */
static void convert_to_work(json_t *val, int rolltime, struct pool *pool, struct
timeval *tv_lp, struct timeval *tv_lp_reply)
{
struct work *work;
bool rc;
work = make_work();
if (pool->enabled == POOL_REJECTING)
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
* from a new block so explicitly force the new block detection now
* rather than waiting for it to hit the stage thread. This also
* allows testwork to know whether LP discovered the block or not. */
test_work_current(work);
work = clone_work(work);
stage_work(work);
applog(LOG_DEBUG, "Converted longpoll data to work");
}
if (pool->has_stratum || pool->hdr_path)
return pool;
}
return NULL;
}
#endif /* HAVE_LIBCURL */
/* This will make the longpoll thread wait till it's the current pool, or it
* has been flagged as rejecting, before attempting to open any connections.
*/
static void wait_lpcurrent(struct pool *pool)
{
while (!cnx_needed(pool) && (pool->enabled == POOL_DISABLED ||
(pool != current_pool() && pool_strategy != POOL_LOADBALANCE &&
pool_strategy != POOL_BALANCE))) {
mutex_lock(&lp_lock);
pthread_cond_wait(&lp_cond, &lp_lock);
mutex_unlock(&lp_lock);
}
}
#ifdef HAVE_LIBCURL
static void *longpoll_thread(void *userdata)
{
struct pool *cp = (struct pool *)userdata;
/* This *pool is the source of the actual longpoll, not the pool we've
* tied it to */
struct timeval start, reply, end;
struct pool *pool = NULL;
char threadname[16];
CURL *curl = NULL;
int failures = 0;
char lpreq[1024];
char *lp_url;
int rolltime;
retry_pool:
pool = select_longpoll_pool(cp);
if (!pool) {
applog(LOG_WARNING, "No suitable long-poll found for %s", cp->rpc_url);
while (!pool) {
cgsleep_ms(60000);
pool = select_longpoll_pool(cp);
}
}
if (pool->has_stratum) {
applog(LOG_WARNING, "Block change for %s detection via %s stratum",
cp->rpc_url, pool->rpc_url);
goto out;
}
if (pool->gbt_solo) {
applog(LOG_WARNING, "Block change for %s detection via getblockcount
polling",
cp->rpc_url);
while (42) {
json_t *val, *res_val = NULL;
if (unlikely(pool->removed))
return NULL;
cgtime(&start);
wait_lpcurrent(cp);
sprintf(lpreq, "{\"id\": 0, \"method\": \"getblockcount\"}\n");
if (likely(val))
res_val = json_object_get(val, "result");
if (likely(res_val)) {
int height = json_integer_value(res_val);
const char *prev_hash;
failures = 0;
json_decref(val);
if (height >= cp->height) {
applog(LOG_WARNING, "Block height change to %d
detected on pool %d",
height, cp->pool_no);
update_gbt_solo(pool);
continue;
}
sprintf(lpreq, "{\"id\":
0, \"method\": \"getblockhash\", \"params\": [%d]}\n", height);
get_gbt_curl(pool, 500);
curl_easy_setopt(pool->gbt_curl, CURLOPT_FORBID_REUSE, 1);
val = json_rpc_call(pool->gbt_curl, pool->rpc_url, pool-
>rpc_userpass,
lpreq, true, false, &rolltime, pool,
false);
release_gbt_curl(pool);
if (val) {
/* Do a comparison on a short stretch of
* the hash to make sure it hasn't changed
* due to mining on an orphan branch. */
prev_hash = json_string_value(json_object_get(val,
"result"));
if (unlikely(prev_hash && strncasecmp(prev_hash + 56,
pool->prev_hash, 8))) {
applog(LOG_WARNING, "Mining on orphan branch
detected, switching!");
update_gbt_solo(pool);
}
json_decref(val);
}
cgsleep_ms(500);
} else {
if (val)
json_decref(val);
cgtime(&end);
if (end.tv_sec - start.tv_sec > 30)
continue;
if (failures == 1)
applog(LOG_WARNING, "longpoll failed for %s, retrying
every 30s", lp_url);
cgsleep_ms(30000);
}
}
}
curl = curl_easy_init();
if (unlikely(!curl))
quit (1, "Longpoll CURL initialisation failed");
wait_lpcurrent(cp);
lp_url = pool->rpc_url;
applog(LOG_WARNING, "GBT longpoll ID activated for %s", lp_url);
while (42) {
json_t *val, *soval;
wait_lpcurrent(cp);
cgtime(&start);
cgtime(&reply);
if (likely(val)) {
soval = json_object_get(json_object_get(val, "result"),
"submitold");
if (soval)
pool->submit_old = json_is_true(soval);
else
pool->submit_old = false;
convert_to_work(val, rolltime, pool, &start, &reply);
failures = 0;
json_decref(val);
} else {
/* Some pools regularly drop the longpoll request so
* only see this as longpoll failure if it happens
* immediately and just restart it the rest of the
* time. */
cgtime(&end);
if (end.tv_sec - start.tv_sec > 30)
continue;
if (failures == 1)
applog(LOG_WARNING, "longpoll failed for %s, retrying every
30s", lp_url);
cgsleep_ms(30000);
}
if (pool != cp) {
pool = select_longpoll_pool(cp);
if (pool->has_stratum) {
applog(LOG_WARNING, "Block change for %s detection via %s
stratum",
cp->rpc_url, pool->rpc_url);
break;
}
if (unlikely(!pool))
goto retry_pool;
}
if (unlikely(pool->removed))
break;
}
out:
curl_easy_cleanup(curl);
return NULL;
}
#else /* HAVE_LIBCURL */
static void *longpoll_thread(void __maybe_unused *userdata)
{
pthread_detach(pthread_self());
return NULL;
}
#endif /* HAVE_LIBCURL */
#ifdef USE_USBUTILS
/* Attempt a usb device reset if the device has gone sick */
if (cgpu->usbdev && cgpu->usbdev->handle)
libusb_reset_device(cgpu->usbdev->handle);
#endif
cgpu->drv->reinit_device(cgpu);
}
cgtime(&now);
mutex_lock(&pool->pool_lock);
list_for_each_entry_safe(ent, iter, &pool->curlring, node) {
if (pool->curls < 2)
break;
if (now.tv_sec - ent->tv.tv_sec > 300) {
reaped++;
pool->curls--;
list_del(&ent->node);
curl_easy_cleanup(ent->curl);
free(ent);
}
}
mutex_unlock(&pool->pool_lock);
if (reaped)
applog(LOG_DEBUG, "Reaped %d curl%s from pool %d", reaped, reaped > 1 ?
"s" : "", pool->pool_no);
}
/* Prune old shares we haven't had a response about for over 2 minutes in case
* the pool never plans to respond and we're just leaking memory. If we get a
* response beyond that time they will be seen as untracked shares. */
static void prune_stratum_shares(struct pool *pool)
{
struct stratum_share *sshare, *tmpshare;
time_t current_time = time(NULL);
int cleared = 0;
mutex_lock(&sshare_lock);
HASH_ITER(hh, stratum_shares, sshare, tmpshare) {
if (sshare->work->pool == pool && current_time > sshare->sshare_time +
120) {
HASH_DEL(stratum_shares, sshare);
free_work(sshare->work);
free(sshare);
cleared++;
}
}
mutex_unlock(&sshare_lock);
if (cleared) {
applog(LOG_WARNING, "Lost %d shares due to no stratum share response
from pool %d",
cleared, pool->pool_no);
pool->stale_shares += cleared;
total_stale += cleared;
}
}
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
RenameThread("Watchpool");
set_lowprio();
cgtimer_time(&cgt);
while (42) {
struct timeval now;
int i;
pool->last_shares = pool->diff1;
pool->utility = (pool->utility + shares * 0.63) / 1.63;
pool->shares = pool->utility;
}
if (pool->enabled == POOL_DISABLED)
continue;
if (pool_active(pool, true)) {
if (pool_tclear(pool, &pool->idle))
pool_resus(pool);
} else
cgtime(&pool->tv_idle);
if (current_pool()->idle)
switch_pools(NULL);
cgsleep_ms_r(&cgt, 5000);
cgtimer_time(&cgt);
}
return NULL;
}
/* Makes sure the hashmeter keeps going even if mining threads stall, updates
* the screen at regular intervals, and restarts threads if they appear to have
* died. */
#define WATCHDOG_INTERVAL 2
#define WATCHDOG_SICK_TIME 120
#define WATCHDOG_DEAD_TIME 600
#define WATCHDOG_SICK_COUNT (WATCHDOG_SICK_TIME/WATCHDOG_INTERVAL)
#define WATCHDOG_DEAD_COUNT (WATCHDOG_DEAD_TIME/WATCHDOG_INTERVAL)
#ifdef USE_LIBSYSTEMD
uint64_t notify_usec;
struct timeval notify_interval, notify_tv;
if (sd_watchdog_enabled(false, ¬ify_usec)) {
notify_usec = notify_usec / 2;
us_to_timeval(¬ify_interval, notify_usec);
cgtime(¬ify_tv);
addtime(¬ify_interval, ¬ify_tv);
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
RenameThread("Watchdog");
set_lowprio();
memset(&zero_tv, 0, sizeof(struct timeval));
cgtime(&rotate_tv);
while (1) {
int i;
struct timeval now;
sleep(interval);
discard_stale();
hashmeter(-1, 0);
#ifdef HAVE_CURSES
if (curses_active_locked()) {
struct cgpu_info *cgpu;
int count;
change_logwinsize();
curses_print_status();
count = 0;
for (i = 0; i < total_devices; i++) {
cgpu = get_devices(i);
#ifndef USE_USBUTILS
if (cgpu)
#else
if (cgpu && !cgpu->usbinfo.nodev)
#endif
curses_print_devstatus(cgpu, i, count++);
}
#ifdef USE_USBUTILS
for (i = 0; i < total_devices; i++) {
cgpu = get_devices(i);
if (cgpu && cgpu->usbinfo.nodev)
curses_print_devstatus(cgpu, i, count++);
}
#endif
touchwin(statuswin);
wrefresh(statuswin);
touchwin(logwin);
wrefresh(logwin);
unlock_curses();
}
#endif
cgtime(&now);
#if USE_LIBSYSTEMD
if (notify_usec && !time_more(¬ify_tv, &now)) {
sd_notify(false, "WATCHDOG=1");
copy_time(¬ify_tv, &now);
addtime(¬ify_interval, ¬ify_tv);
applog(LOG_DEBUG, "Notified watchdog");
}
#endif
rd_lock(&mining_thr_lock);
for (i = 0; i < mining_threads; i++)
mining_thr[i]->pause = true;
rd_unlock(&mining_thr_lock);
} else if (sched_paused && should_run()) {
applog(LOG_WARNING, "Restarting execution as per start time %02d:
%02d scheduled",
schedstart.tm.tm_hour, schedstart.tm.tm_min);
if (schedstop.enable)
applog(LOG_WARNING, "Will pause execution as scheduled at
%02d:%02d",
schedstop.tm.tm_hour, schedstop.tm.tm_min);
sched_paused = false;
thr = get_thread(i);
if (!thr)
continue;
cgpu->drv->get_stats(cgpu);
denable = &cgpu->deven;
snprintf(dev_str, sizeof(dev_str), "%s %d", cgpu->drv->name,
cgpu->device_id);
dev_error(cgpu, REASON_DEV_SICK_IDLE_60);
if (opt_restart) {
applog(LOG_ERR, "%s: Attempting to restart",
dev_str);
reinit_device(cgpu);
}
} else if (cgpu->status == LIFE_SICK && (now.tv_sec - thr-
>last.tv_sec > WATCHDOG_DEAD_TIME)) {
cgpu->status = LIFE_DEAD;
applog(LOG_ERR, "%s: Not responded for more than 10
minutes, declaring DEAD!", dev_str);
cgtime(&thr->sick);
dev_error(cgpu, REASON_DEV_DEAD_IDLE_600);
} else if (now.tv_sec - thr->sick.tv_sec > 60 &&
(cgpu->status == LIFE_SICK || cgpu->status ==
LIFE_DEAD)) {
/* Attempt to restart a GPU that's sick or dead once every
minute */
cgtime(&thr->sick);
if (opt_restart)
reinit_device(cgpu);
}
}
}
return NULL;
}
void print_summary(void)
{
struct timeval diff;
int hours, mins, secs, i;
double utility, displayed_hashes, work_util;
cgpu->drv->get_statline_before = &blank_get_statline_before;
cgpu->drv->get_statline = &noop_get_statline;
log_print_status(cgpu);
}
if (opt_shares) {
applog(LOG_WARNING, "Mined %.0f accepted shares of %d requested\n",
total_diff_accepted, opt_shares);
if (opt_shares > total_diff_accepted)
applog(LOG_WARNING, "WARNING - Mined only %.0f shares of %d
requested.", total_diff_accepted, opt_shares);
}
applog(LOG_WARNING, " ");
fflush(stderr);
fflush(stdout);
}
cgtime(&total_tv_end);
#ifdef WIN32
timeEndPeriod(1);
#endif
#ifdef HAVE_CURSES
disable_curses();
#endif
if (!restarting && !opt_realquiet && successful_connect)
print_summary();
curl_global_cleanup();
}
/* Should all else fail and we're unable to clean up threads due to locking
* issues etc, just silently exit. */
static void *killall_thread(void __maybe_unused *arg)
{
pthread_detach(pthread_self());
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
sleep(5);
exit(1);
return NULL;
}
#ifdef USE_LIBSYSTEMD
sd_notify(false, "STOPPING=1\n"
"STATUS=Shutting down...");
#endif
if (clean)
clean_up(false);
#ifdef HAVE_CURSES
else
disable_curses();
#endif
exit(status);
}
#ifdef HAVE_CURSES
char *curses_input(const char *query)
{
char *input;
echo();
input = cgmalloc(255);
leaveok(logwin, false);
wlogprint("%s:\n", query);
wgetnstr(logwin, input, 255);
if (!strlen(input))
strcpy(input, "-1");
leaveok(logwin, true);
noecho();
return input;
}
#endif
if (!pool->blocking)
pthread_detach(pthread_self());
retry:
if (pool->removed)
goto out;
if (pool_active(pool, false)) {
pool_tclear(pool, &pool->idle);
bool first_pool = false;
cg_wlock(&control_lock);
if (!pools_active) {
currentpool = pool;
if (pool->pool_no != 0)
first_pool = true;
pools_active = true;
}
cg_wunlock(&control_lock);
if (unlikely(first_pool))
applog(LOG_NOTICE, "Switching to pool %d %s - first alive pool",
pool->pool_no, pool->rpc_url);
pool_resus(pool);
switch_pools(NULL);
} else {
pool_died(pool);
if (!pool->blocking) {
sleep(5);
goto retry;
}
}
pool->testing = false;
out:
return NULL;
}
/* Always returns true that the pool details were added unless we are not
* live, implying this is the only pool being added, so if no pools are
* active it returns false. */
bool add_pool_details(struct pool *pool, bool live, char *url, char *user, char
*pass)
{
size_t siz;
pool->rpc_url = url;
pool->rpc_user = user;
pool->rpc_pass = pass;
siz = strlen(pool->rpc_user) + strlen(pool->rpc_pass) + 2;
pool->rpc_userpass = cgmalloc(siz);
snprintf(pool->rpc_userpass, siz, "%s:%s", pool->rpc_user, pool->rpc_pass);
pool->testing = true;
pool->idle = true;
pool->blocking = !live;
enable_pool(pool);
#ifdef HAVE_CURSES
static bool input_pool(bool live)
{
char *url, *user, *pass;
struct pool *pool;
bool ret = false;
immedok(logwin, true);
wlogprint("Input server details.\n");
retry:
url = NULL;
user = NULL;
pass = NULL;
url = curses_input("URL");
if (!strcmp(url, "-1")) {
wlogprint("Invalid input\n");
goto out;
}
user = curses_input("Username");
if (!strcmp(user, "-1")) {
wlogprint("Invalid input\n");
goto out;
}
pool = add_pool();
url = setup_url(pool, url);
ret = add_pool_details(pool, live, url, user, pass);
if (!ret) {
remove_pool(pool);
wlogprint("URL %s failed alive testing, reinput details\n", url);
free(url);
free(user);
free(pass);
goto retry;
}
out:
immedok(logwin, false);
if (!ret) {
free(url);
free(user);
free(pass);
}
return ret;
}
#endif
if (r < 0) {
perror("pipe - failed to create pipe for --monitor");
exit(1);
}
#ifdef HAVE_CURSES
static void enable_curses_windows(void)
{
int x,y;
getmaxyx(mainwin, y, x);
statuswin = newwin(logstart, x, 0, 0);
leaveok(statuswin, true);
logwin = newwin(y - logcursor, 0, logcursor, 0);
idlok(logwin, true);
scrollok(logwin, true);
leaveok(logwin, true);
cbreak();
noecho();
}
void enable_curses(void) {
lock_curses();
if (curses_active) {
unlock_curses();
return;
}
mainwin = initscr();
enable_curses_windows();
curses_active = true;
statusy = logstart;
unlock_curses();
}
#endif
/* Various noop functions for drivers that don't support or need their
* variants. */
static void noop_reinit_device(struct cgpu_info __maybe_unused *cgpu)
{
}
drv->thread_prepare = &noop_thread_prepare;
drv->can_limit_work = &noop_can_limit_work;
drv->thread_init = &noop_thread_init;
drv->prepare_work = &noop_prepare_work;
drv->hw_error = &noop_hw_error;
drv->thread_shutdown = &noop_thread_shutdown;
drv->thread_enable = &noop_thread_enable;
drv->zero_stats = &generic_zero_stats;
drv->hash_work = &noop_hash_work;
drv->queue_full = &noop_queue_full;
drv->flush_work = &noop_flush_work;
drv->update_work = &noop_update_work;
drv->max_diff = 1;
drv->min_diff = 1;
}
if (hotplug_mode)
new_threads += cgpu->threads;
else
mining_threads += cgpu->threads;
rwlock_init(&cgpu->qlock);
cgpu->queued_work = NULL;
}
struct _cgpu_devid_counter {
char name[4];
int lastid;
UT_hash_handle hh;
};
#ifdef USE_ICARUS
bool icarus_get_device_id(struct cgpu_info *cgpu)
{
static struct _cgpu_devid_counter *devids = NULL;
struct _cgpu_devid_counter *d;
wr_lock(&devices_lock);
devices = cgrealloc(devices, sizeof(struct cgpu_info *) * (total_devices +
new_devices + 2));
wr_unlock(&devices_lock);
mutex_lock(&stats_lock);
cgpu->last_device_valid_work = time(NULL);
mutex_unlock(&stats_lock);
if (hotplug_mode)
devices[total_devices + new_devices++] = cgpu;
else
devices[total_devices++] = cgpu;
adjust_mostdevs();
#ifdef USE_USBUTILS
if (cgpu->usbdev && !cgpu->unique_id && cgpu->usbdev->serial_string &&
strlen(cgpu->usbdev->serial_string) > 4)
cgpu->unique_id = str_text(cgpu->usbdev->serial_string);
#endif
return true;
}
copy = cgmalloc(sizeof(*copy));
cg_memcpy(copy, drv, sizeof(*copy));
copy->copy = true;
return copy;
}
#ifdef USE_USBUTILS
static void hotplug_process(void)
{
struct thr_info *thr;
int i, j;
cgpu = devices[dev_no];
enable_device(cgpu);
cgpu->cgminer_stats.getwork_wait_min.tv_sec = MIN_SEC_UNSET;
cgpu->rolling = cgpu->total_mhashes = 0;
}
wr_lock(&mining_thr_lock);
mining_thr = cgrealloc(mining_thr, sizeof(thr) * (mining_threads +
new_threads + 1));
for (i = 0; i < new_threads; i++)
mining_thr[mining_threads + i] = cgcalloc(1, sizeof(*thr));
// Start threads
for (i = 0; i < new_devices; ++i) {
struct cgpu_info *cgpu = devices[total_devices];
cgpu->thr = cgmalloc(sizeof(*cgpu->thr) * (cgpu->threads+1));
cgpu->thr[cgpu->threads] = NULL;
cgpu->status = LIFE_INIT;
cgtime(&(cgpu->dev_start_tv));
for (j = 0; j < cgpu->threads; ++j) {
thr = __get_thread(mining_threads);
thr->id = mining_threads;
thr->cgpu = cgpu;
thr->device_thread = j;
if (!cgpu->drv->thread_prepare(thr)) {
null_device_drv(cgpu->drv);
cgpu->deven = DEV_DISABLED;
continue;
}
cgpu->thr[j] = thr;
mining_threads++;
}
total_devices++;
applog(LOG_WARNING, "Hotplug: %s added %s %i", cgpu->drv->dname, cgpu-
>drv->name, cgpu->device_id);
}
wr_unlock(&mining_thr_lock);
adjust_mostdevs();
#ifdef HAVE_CURSES
switch_logsize(true);
#endif
}
usb_reinit = true;
/* Wait till libusb_poll_thread is no longer polling */
while (polling_usb)
cgsleep_ms(100);
RenameThread("Hotplug");
hotplug_mode = true;
cgsleep_ms(5000);
while (0x2a) {
// Version 0.1 just add the devices on - worry about using nodev later
if (hotplug_time == 0)
cgsleep_ms(5000);
else {
new_devices = 0;
new_threads = 0;
if (new_devices)
hotplug_process();
return NULL;
}
#endif
pool->testing = true;
pthread_create(&pool->test_thread, NULL, test_pool_thread, (void
*)pool);
}
}
#ifdef USE_USBUTILS
static void *libusb_poll_thread(void __maybe_unused *arg)
{
struct timeval tv_end;
RenameThread("USBPoll");
while (likely(usb_polling)) {
tv_end.tv_sec = 0;
tv_end.tv_usec = 100000;
while (usb_reinit) {
polling_usb = false;
cgsleep_ms(100);
}
polling_usb = true;
libusb_handle_events_timeout_completed(NULL, &tv_end, NULL);
}
return NULL;
}
if (err) {
fprintf(stderr, "libusb_init() failed err %d", err);
fflush(stderr);
quit(1, "libusb_init() failed");
}
initialise_usblocks();
usb_polling = true;
pthread_create(&usb_poll_thread, NULL, libusb_poll_thread, NULL);
}
#else
#define initialise_usb() {}
#endif
#ifdef USE_LIBSYSTEMD
sd_notify(false, "STATUS=Starting up...");
#endif
# ifdef __linux
/* If we're on a small lowspec platform with only one CPU, we should
* yield after dropping a lock to allow a thread waiting for it to be
* able to get CPU time to grab the lock. */
if (sysconf(_SC_NPROCESSORS_ONLN) == 1)
selective_yield = &sched_yield;
#endif
#if LOCK_TRACKING
// Must be first
if (unlikely(pthread_mutex_init(&lockstat_lock, NULL)))
quithere(1, "Failed to pthread_mutex_init lockstat_lock errno=%d",
errno);
#endif
mutex_init(&hash_lock);
mutex_init(&console_lock);
cglock_init(&control_lock);
mutex_init(&stats_lock);
mutex_init(&sharelog_lock);
cglock_init(&ch_lock);
mutex_init(&sshare_lock);
rwlock_init(&blk_lock);
rwlock_init(&netacc_lock);
rwlock_init(&mining_thr_lock);
rwlock_init(&devices_lock);
mutex_init(&lp_lock);
if (unlikely(pthread_cond_init(&lp_cond, NULL)))
early_quit(1, "Failed to pthread_cond_init lp_cond");
mutex_init(&restart_lock);
if (unlikely(pthread_cond_init(&restart_cond, NULL)))
early_quit(1, "Failed to pthread_cond_init restart_cond");
if (unlikely(pthread_cond_init(&gws_cond, NULL)))
early_quit(1, "Failed to pthread_cond_init gws_cond");
initialise_usb();
devcursor = 8;
logstart = devcursor + 1;
logcursor = logstart + 1;
INIT_LIST_HEAD(&scan_devices);
if (!config_loaded)
load_default_config();
if (opt_benchmark || opt_benchfile) {
struct pool *pool;
pool = add_pool();
pool->rpc_url = cgmalloc(255);
if (opt_benchfile)
strcpy(pool->rpc_url, "Benchfile");
else
strcpy(pool->rpc_url, "Benchmark");
pool->rpc_user = pool->rpc_url;
pool->rpc_pass = pool->rpc_url;
pool->rpc_userpass = pool->rpc_url;
pool->sockaddr_url = pool->rpc_url;
strncpy(pool->diff, "?", sizeof(pool->diff)-1);
pool->diff[sizeof(pool->diff)-1] = '\0';
enable_pool(pool);
pool->idle = false;
successful_connect = true;
#ifdef HAVE_CURSES
if (opt_realquiet || opt_display_devs || opt_decode)
use_curses = false;
if (use_curses)
enable_curses();
#endif
strcat(opt_kernel_path, "/");
if (want_per_device_stats)
opt_log_output = true;
#ifdef HAVE_SYSLOG_H
if (opt_log_output)
setlogmask(LOG_UPTO(LOG_DEBUG));
else
setlogmask(LOG_UPTO(LOG_NOTICE));
#endif
total_control_threads = 8;
control_thr = cgcalloc(total_control_threads, sizeof(*thr));
gwsched_thr_id = 0;
#ifdef USE_USBUTILS
usb_initialise();
if (!total_pools) {
applog(LOG_WARNING, "Need to specify at least one pool server.");
#ifdef HAVE_CURSES
if (!use_curses || !input_pool(false))
#endif
early_quit(1, "Pool setup failed");
}
pool->cgminer_stats.getwork_wait_min.tv_sec = MIN_SEC_UNSET;
pool->cgminer_pool_stats.getwork_wait_min.tv_sec = MIN_SEC_UNSET;
if (!pool->rpc_userpass) {
if (!pool->rpc_pass)
pool->rpc_pass = strdup("");
if (!pool->rpc_user)
early_quit(1, "No login credentials supplied for pool %u
%s", i, pool->rpc_url);
siz = strlen(pool->rpc_user) + strlen(pool->rpc_pass) + 2;
pool->rpc_userpass = cgmalloc(siz);
snprintf(pool->rpc_userpass, siz, "%s:%s", pool->rpc_user, pool-
>rpc_pass);
}
}
/* Set the currentpool to pool 0 */
currentpool = pools[0];
#ifdef HAVE_SYSLOG_H
if (use_syslog)
openlog(PACKAGE, LOG_PID, LOG_USER);
#endif
if (opt_benchmark || opt_benchfile)
goto begin_bench;
for (i = 0; i < total_pools; i++) {
struct pool *pool = pools[i];
enable_pool(pool);
pool->idle = true;
}
while (!pools_active) {
if (!pool_msg) {
applog(LOG_ERR, "No servers were found that could be used to get
work from.");
applog(LOG_ERR, "Please check the details from the list below of
the servers you have input");
applog(LOG_ERR, "Most likely you have input the wrong URL,
forgotten to add a port, or have not set up workers");
for (i = 0; i < total_pools; i++) {
struct pool *pool = pools[i];
begin_bench:
/* Use the DRIVER_PARSE_COMMANDS macro to detect all devices */
DRIVER_PARSE_COMMANDS(DRIVER_DRV_DETECT_ALL)
if (opt_display_devs) {
applog(LOG_ERR, "Devices detected:");
for (i = 0; i < total_devices; ++i) {
struct cgpu_info *cgpu = devices[i];
if (cgpu->name)
applog(LOG_ERR, " %2d. %s %d: %s (driver: %s)", i, cgpu-
>drv->name, cgpu->device_id, cgpu->name, cgpu->drv->dname);
else
applog(LOG_ERR, " %2d. %s %d (driver: %s)", i, cgpu->drv-
>name, cgpu->device_id, cgpu->drv->dname);
}
early_quit(0, "%d devices listed", total_devices);
}
mining_threads = 0;
for (i = 0; i < total_devices; ++i)
enable_device(devices[i]);
if (!opt_decode) {
#ifdef USE_USBUTILS
if (!total_devices) {
applog(LOG_WARNING, "No devices detected!");
applog(LOG_WARNING, "Waiting for USB hotplug devices or press q
to quit");
}
#else
if (!total_devices)
early_quit(1, "All devices disabled, cannot mine!");
#endif
}
most_devices = total_devices;
load_temp_cutoffs();
if (!opt_compact) {
logstart += most_devices;
logcursor = logstart + 1;
#ifdef HAVE_CURSES
check_winsizes();
#endif
}
// Start threads
k = 0;
for (i = 0; i < total_devices; ++i) {
struct cgpu_info *cgpu = devices[i];
cgpu->thr = cgmalloc(sizeof(*cgpu->thr) * (cgpu->threads+1));
cgpu->thr[cgpu->threads] = NULL;
cgpu->status = LIFE_INIT;
if (!cgpu->drv->thread_prepare(thr))
continue;
cgpu->thr[j] = thr;
total_mhashes_done = 0;
for (i = 0; i < total_devices; i++) {
struct cgpu_info *cgpu = devices[i];
cgpu->rolling = cgpu->total_mhashes = 0;
}
cgtime(&total_tv_start);
cgtime(&total_tv_end);
cgtime(&tv_hashmeter);
get_datestamp(datestamp, sizeof(datestamp), &total_tv_start);
watchpool_thr_id = 2;
thr = &control_thr[watchpool_thr_id];
/* start watchpool thread */
if (thr_info_create(thr, NULL, watchpool_thread, NULL))
early_quit(1, "watchpool thread create failed");
pthread_detach(thr->pth);
watchdog_thr_id = 3;
thr = &control_thr[watchdog_thr_id];
/* start watchdog thread */
if (thr_info_create(thr, NULL, watchdog_thread, NULL))
early_quit(1, "watchdog thread create failed");
pthread_detach(thr->pth);
#ifdef USE_USBUTILS
hotplug_thr_id = 6;
thr = &control_thr[hotplug_thr_id];
if (thr_info_create(thr, NULL, hotplug_thread, thr))
early_quit(1, "hotplug thread create failed");
pthread_detach(thr->pth);
#endif
#ifdef HAVE_CURSES
/* Create curses input thread for keyboard input. Create this last so
* that we know all threads are created since this can call kill_work
* to try and shut down all previous threads. */
input_thr_id = 7;
thr = &control_thr[input_thr_id];
if (thr_info_create(thr, NULL, input_thread, thr))
early_quit(1, "input thread create failed");
pthread_detach(thr->pth);
#endif
/* Just to be sure */
if (total_control_threads != 8)
early_quit(1, "incorrect total_control_threads (%d) should be 8",
total_control_threads);
set_highprio();
#ifdef USE_LIBSYSTEMD
sd_notify(false, "READY=1\n"
"STATUS=Started");
#endif
if (opt_work_update)
signal_work_update();
opt_work_update = false;
mutex_lock(stgd_lock);
ts = __total_staged();
/* Wait until hash_pop tells us we need to create more work */
if (ts > max_staged) {
work_filled = true;
pthread_cond_wait(&gws_cond, stgd_lock);
ts = __total_staged();
}
mutex_unlock(stgd_lock);
if (work)
discard_work(work);
work = make_work();
while (42) {
pool = select_pool();
if (!pool_unusable(pool))
break;
switch_pools(NULL);
pool = select_pool();
if (pool_unusable(pool))
cgsleep_ms(5);
};
if (pool->has_stratum) {
if (opt_gen_stratum_work) {
gen_stratum_work(pool, work);
applog(LOG_DEBUG, "Generated stratum work");
stage_work(work);
}
continue;
}
#ifdef HAVE_LIBCURL
if (pool->gbt_solo) {
gen_solo_work(pool, work);
applog(LOG_DEBUG, "Generated GBT SOLO work");
stage_work(work);
continue;
}
if (pool->has_gbt) {
gen_gbt_work(pool, work);
applog(LOG_DEBUG, "Generated GBT work");
stage_work(work);
continue;
}
#endif
if (opt_benchfile) {
get_benchfile_work(work);
applog(LOG_DEBUG, "Generated benchfile work");
stage_work(work);
continue;
} else if (opt_benchmark) {
get_benchmark_work(work);
applog(LOG_DEBUG, "Generated benchmark work");
stage_work(work);
continue;
}
}
return 0;
}