rig/xmrig.c
2017-04-21 12:13:49 +03:00

611 lines
14 KiB
C

/* XMRig
* Copyright 2010 Jeff Garzik <jgarzik@pobox.com>
* Copyright 2012-2014 pooler <pooler@litecoinpool.org>
* Copyright 2014 Lucas Jones <https://github.com/lucasjones>
* Copyright 2014-2016 Wolf9466 <https://github.com/OhGodAPet>
* Copyright 2016 Jay D Dee <jayddee246@gmail.com>
* Copyright 2016-2017 XMRig <support@xmrig.com>
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <unistd.h>
#include <jansson.h>
#include <sys/time.h>
#ifdef WIN32
# include <winsock2.h>
# include <windows.h>
#endif
#include <jansson.h>
#include <curl/curl.h>
#include <pthread.h>
#include "compat.h"
#include "xmrig.h"
#include "algo/cryptonight/cryptonight.h"
#include "options.h"
#include "cpu.h"
#include "persistent_memory.h"
#include "stratum.h"
#include "stats.h"
#include "util.h"
#include "utils/summary.h"
#include "utils/applog.h"
#define LP_SCANTIME 60
#define JSON_BUF_LEN 345
struct workio_cmd {
struct thr_info *thr;
struct work *work;
};
struct thr_info *thr_info;
static int work_thr_id = -1;
static int timer_thr_id = -1;
static int stratum_thr_id = -1;
struct work_restart *work_restart = NULL;
static struct stratum_ctx *stratum_ctx = NULL;
static bool backup_active = false;
static bool g_want_donate = false;
static void workio_cmd_free(struct workio_cmd *wc);
/**
* @brief work_free
* @param w
*/
static inline void work_free(struct work *w) {
free(w->job_id);
free(w->xnonce2);
}
/**
* @brief work_copy
* @param dest
* @param src
*/
static inline void work_copy(struct work *dest, const struct work *src) {
memcpy(dest, src, sizeof(struct work));
if (src->job_id) {
dest->job_id = strdup(src->job_id);
}
if (src->xnonce2) {
dest->xnonce2 = malloc(src->xnonce2_len);
memcpy(dest->xnonce2, src->xnonce2, src->xnonce2_len);
}
}
/**
* @brief restart_threads
*/
static inline void restart_threads(void) {
for (int i = 0; i < opt_n_threads; i++) {
work_restart[i].restart = 1;
}
}
/**
* @brief gen_workify
* @param sctx
* @param work
*/
static inline void gen_workify(struct stratum_ctx *sctx) {
pthread_mutex_lock(&stratum_ctx->work_lock);
if (stratum_ctx->work.job_id && (!stratum_ctx->g_work_time || strcmp(stratum_ctx->work.job_id, stratum_ctx->g_work.job_id))) {
free(sctx->g_work.job_id);
memcpy(&sctx->g_work, &sctx->work, sizeof(struct work));
sctx->work.job_id = strdup(sctx->work.job_id);
time(&stratum_ctx->g_work_time);
pthread_mutex_unlock(&stratum_ctx->work_lock);
applog(LOG_DEBUG, "Stratum detected new block");
restart_threads();
return;
}
pthread_mutex_unlock(&stratum_ctx->work_lock);
}
/**
* @brief submit_upstream_work
* @param work
* @return
*/
static bool submit_upstream_work(struct work *work) {
char s[JSON_BUF_LEN];
/* pass if the previous hash is not the current previous hash */
if (memcmp(work->data + 1, stratum_ctx->g_work.data + 1, 32)) {
return true;
}
char *noncestr = bin2hex(((const unsigned char*) work->data) + 39, 4);
char *hashhex = bin2hex((const unsigned char *) work->hash, 32);
snprintf(s, JSON_BUF_LEN,
"{\"method\":\"submit\",\"params\":{\"id\":\"%s\",\"job_id\":\"%s\",\"nonce\":\"%s\",\"result\":\"%s\"},\"id\":1}",
stratum_ctx->id, work->job_id, noncestr, hashhex);
free(hashhex);
free(noncestr);
if (unlikely(!stratum_send_line(stratum_ctx, s))) {
return false;
}
return true;
}
/**
* @brief workio_cmd_free
* @param wc
*/
static void workio_cmd_free(struct workio_cmd *wc) {
if (!wc) {
return;
}
work_free(wc->work);
free(wc->work);
memset(wc, 0, sizeof(*wc)); /* poison */
free(wc);
}
/**
* @brief workio_submit_work
* @param wc
* @param curl
* @return
*/
static bool workio_submit_work(struct workio_cmd *wc) {
while (!submit_upstream_work(wc->work)) {
sleep(opt_retry_pause);
}
return true;
}
/**
* @brief workio_thread
* @param userdata
* @return
*/
static void *workio_thread(void *userdata) {
struct thr_info *mythr = userdata;
bool ok = true;
while (ok) {
struct workio_cmd *wc;
/* wait for workio_cmd sent to us, on our queue */
wc = tq_pop(mythr->q, NULL );
if (!wc) {
ok = false;
break;
}
workio_submit_work(wc);
workio_cmd_free(wc);
}
tq_freeze(mythr->q);
return NULL ;
}
/**
* @brief submit_work
* @param thr
* @param work_in
* @return
*/
static bool submit_work(struct thr_info *thr, const struct work *work_in) {
struct workio_cmd *wc;
/* fill out work request message */
wc = calloc(1, sizeof(*wc));
wc->work = malloc(sizeof(*work_in));
if (likely(wc->work)) {
wc->thr = thr;
work_copy(wc->work, work_in);
if (likely(tq_push(thr_info[work_thr_id].q, wc))) {
return true;
}
}
workio_cmd_free(wc);
return false;
}
static bool should_pause(int thr_id) {
bool ret = false;
pthread_mutex_lock(&stratum_ctx->sock_lock);
if (!stratum_ctx->ready) {
ret = true;
}
pthread_mutex_unlock(&stratum_ctx->sock_lock);
return ret;
}
/**
* @brief miner_thread
* @param userdata
* @return
*/
static void *miner_thread(void *userdata) {
struct thr_info *mythr = userdata;
int thr_id = mythr->id;
struct work work = { { 0 } };
uint32_t max_nonce;
uint32_t end_nonce = 0xffffffffU / opt_n_threads * (thr_id + 1) - 0x20;
struct cryptonight_ctx *persistentctx = (struct cryptonight_ctx *) create_persistent_ctx(thr_id);
if (cpu_info.count > 1 && opt_affinity != -1L) {
affine_to_cpu_mask(thr_id, (unsigned long) opt_affinity);
}
uint32_t *nonceptr = (uint32_t*) (((char*)work.data) + 39);
uint32_t hash[32 / 4] __attribute__((aligned(32)));
while (1) {
unsigned long hashes_done;
struct timeval tv_start;
int64_t max64;
int rc;
if (should_pause(thr_id)) {
sleep(1);
continue;
}
pthread_mutex_lock(&stratum_ctx->work_lock);
if (memcmp(work.data, stratum_ctx->g_work.data, 39) || memcmp(((uint8_t*) work.data) + 43, ((uint8_t*) stratum_ctx->g_work.data) + 43, 33)) {
work_free(&work);
work_copy(&work, &stratum_ctx->g_work);
nonceptr = (uint32_t*) (((char*)work.data) + 39);
*nonceptr = 0xffffffffU / opt_n_threads * thr_id;
} else {
++(*nonceptr);
}
pthread_mutex_unlock(&stratum_ctx->work_lock);
work_restart[thr_id].restart = 0;
/* adjust max_nonce to meet target scan time */
max64 = LP_SCANTIME;
//max64 *= thr_hashrates[thr_id];
if (max64 <= 0) {
max64 = 0x40LL;
}
if (*nonceptr + max64 > end_nonce) {
max_nonce = end_nonce;
} else {
max_nonce = *nonceptr + max64;
}
hashes_done = 0;
gettimeofday(&tv_start, NULL );
/* scan nonces for a proof-of-work hash */
rc = scanhash_cryptonight(thr_id, hash, work.data, work.target, max_nonce, &hashes_done, persistentctx);
stats_add_hashes(thr_id, &tv_start, hashes_done);
memcpy(work.hash, hash, 32);
/* if nonce found, submit work */
if (rc && !submit_work(mythr, &work)) {
continue;
}
}
tq_freeze(mythr->q);
return NULL;
}
/**
* @brief stratum_thread
* @param userdata
* @return
*/
static void *timer_thread(void *userdata) {
const int max_user_time = 100 - opt_donate_level;
int user_time_remaning = max_user_time;
int donate_time_remaning = 0;
while (1) {
sleep(60);
if (user_time_remaning > 0) {
if (--user_time_remaning == 0) {
g_want_donate = true;
donate_time_remaning = opt_donate_level;
stratum_disconnect(stratum_ctx);
continue;
}
}
if (donate_time_remaning > 0) {
if (--donate_time_remaning == 0) {
g_want_donate = false;
user_time_remaning = max_user_time;
stratum_disconnect(stratum_ctx);
continue;
}
}
}
}
static void switch_stratum() {
static bool want_donate = false;
if (g_want_donate && !want_donate) {
stratum_ctx->url = "stratum+tcp://donate.xmrig.com:443";
applog(LOG_NOTICE, "Switching to dev pool");
want_donate = true;
}
if (!g_want_donate && want_donate) {
stratum_ctx->url = backup_active ? opt_backup_url : opt_url;
applog(LOG_NOTICE, "Switching to user pool: \"%s\"", stratum_ctx->url);
want_donate = false;
}
}
/**
* @brief stratum_thread
* @param userdata
* @return
*/
static void *stratum_thread(void *userdata) {
char *s;
stratum_ctx->url = opt_url;
stratum_ctx->ready = false;
while (1) {
int failures = 0;
switch_stratum();
while (!stratum_ctx->curl) {
pthread_mutex_lock(&stratum_ctx->work_lock);
stratum_ctx->g_work_time = 0;
pthread_mutex_unlock(&stratum_ctx->work_lock);
restart_threads();
switch_stratum();
if (!stratum_connect(stratum_ctx, stratum_ctx->url) || !stratum_authorize(stratum_ctx, opt_user, opt_pass)) {
stratum_disconnect(stratum_ctx);
failures++;
if (failures > opt_retries && opt_backup_url) {
failures = 0;
backup_active = !backup_active;
stratum_ctx->url = backup_active ? opt_backup_url : opt_url;
sleep(opt_retry_pause);
applog(LOG_WARNING, "Switch to: \"%s\"", stratum_ctx->url);
continue;
}
applog(LOG_ERR, "...retry after %d seconds", opt_retry_pause);
sleep(opt_retry_pause);
}
}
gen_workify(stratum_ctx);
if (opt_keepalive && !stratum_socket_full(stratum_ctx, 90)) {
stratum_keepalived(stratum_ctx);
}
if (!stratum_socket_full(stratum_ctx, 300)) {
applog(LOG_ERR, "Stratum connection timed out");
s = NULL;
} else {
s = stratum_recv_line(stratum_ctx);
}
if (!s) {
stratum_disconnect(stratum_ctx);
applog(LOG_ERR, "Stratum connection interrupted");
continue;
}
if (!stratum_handle_method(stratum_ctx, s)) {
stratum_handle_response(s);
}
free(s);
}
return NULL ;
}
/**
* @brief start work I/O thread
* @return
*/
static bool start_workio() {
work_thr_id = opt_n_threads;
struct thr_info *thr = &thr_info[work_thr_id];
thr->id = work_thr_id;
thr->q = tq_new();
if (unlikely(!thr->q || pthread_create(&thr->pth, NULL, workio_thread, thr))) {
return false;
}
return true;
}
/**
* @brief start_stratum
* @return
*/
static bool start_stratum() {
stratum_thr_id = opt_n_threads + 1;
stratum_ctx = persistent_calloc(1, sizeof(struct stratum_ctx));
pthread_mutex_init(&stratum_ctx->work_lock, NULL);
pthread_mutex_init(&stratum_ctx->sock_lock, NULL);
struct thr_info *thr = &thr_info[stratum_thr_id];
thr->id = stratum_thr_id;
thr->q = tq_new();
if (unlikely(!thr->q || pthread_create(&thr->pth, NULL, stratum_thread, thr))) {
return false;
}
tq_push(thr_info[stratum_thr_id].q, strdup(opt_url));
return true;
}
/**
* @brief start_timer
* @return
*/
static bool start_timer() {
timer_thr_id = opt_n_threads + 2;
if (opt_donate_level < 1) {
return true;
}
struct thr_info *thr = &thr_info[timer_thr_id];
thr->id = timer_thr_id;
thr->q = tq_new();
if (unlikely(!thr->q || pthread_create(&thr->pth, NULL, timer_thread, thr))) {
return false;
}
return true;
}
/**
* @brief start_mining
* @return
*/
static bool start_mining() {
for (int i = 0; i < opt_n_threads; i++) {
struct thr_info *thr = &thr_info[i];
thr->id = i;
thr->q = tq_new();
if (unlikely(!thr->q || pthread_create(&thr->pth, NULL, miner_thread, thr))) {
applog(LOG_ERR, "thread %d create failed", i);
return false;
}
}
return true;
}
/**
* @brief main
* @param argc
* @param argv
* @return
*/
int main(int argc, char *argv[]) {
cpu_init();
applog_init();
parse_cmdline(argc, argv);
persistent_memory_allocate();
print_summary();
stats_init();
os_specific_init();
work_restart = persistent_calloc(opt_n_threads, sizeof(*work_restart));
thr_info = persistent_calloc(opt_n_threads + 3, sizeof(struct thr_info));
if (!start_workio()) {
applog(LOG_ERR, "workio thread create failed");
return 1;
}
if (!start_stratum()) {
applog(LOG_ERR, "stratum thread create failed");
return 1;
}
start_timer();
if (!start_mining()) {
return 1;
}
pthread_join(thr_info[work_thr_id].pth, NULL);
applog(LOG_INFO, "workio thread dead, exiting.");
persistent_memory_free();
return 0;
}