7911 lines
240 KiB
7911 lines
240 KiB
/* |
|
* Copyright 1995-2016 Andrew Smith |
|
* |
|
* 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. See COPYING for more details. |
|
*/ |
|
|
|
#include "ckdb.h" |
|
|
|
/* |
|
* Allow overriding the username however the username must still be present |
|
* This should ONLY be used for web reporting cmds i.e. read only |
|
* Current PHP allows this for a hard coded user |
|
*/ |
|
static K_ITEM *adminuser(K_TREE *trf_root, char *reply, size_t siz) |
|
{ |
|
K_ITEM *i_username, *i_admin; |
|
char reply2[1024] = ""; |
|
|
|
i_username = require_name(trf_root, "username", MIN_USERNAME, |
|
(char *)userpatt, reply, siz); |
|
if (!i_username) |
|
return NULL; |
|
|
|
i_admin = optional_name(trf_root, "admin", MIN_USERNAME, |
|
(char *)userpatt, reply2, sizeof(reply2)); |
|
if (i_admin) |
|
return i_admin; |
|
|
|
return i_username; |
|
} |
|
|
|
static char *cmd_adduser(PGconn *conn, char *cmd, char *id, tv_t *now, char *by, |
|
char *code, char *inet, __maybe_unused tv_t *notcd, |
|
K_TREE *trf_root) |
|
{ |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
K_ITEM *i_username, *i_emailaddress, *i_passwordhash, *u_item = NULL; |
|
int event = EVENT_OK; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", MIN_USERNAME, |
|
(char *)userpatt, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
/* If a username added from the web site looks like an address |
|
* then disallow it - a false positive is not an issue |
|
* Allowing it will create a security issue - someone could create |
|
* an account with someone else's, as yet unused, payout address |
|
* and redirect the payout to another payout address. |
|
* ... and the person who owns the payout address can't check that |
|
* in advance, they'll just find out with their first payout not |
|
* arriving at their payout address */ |
|
if (!like_address(transfer_data(i_username))) { |
|
i_emailaddress = require_name(trf_root, "emailaddress", 7, |
|
(char *)mailpatt, reply, siz); |
|
if (!i_emailaddress) |
|
return strdup(reply); |
|
|
|
i_passwordhash = require_name(trf_root, "passwordhash", 64, |
|
(char *)hashpatt, reply, siz); |
|
if (!i_passwordhash) |
|
return strdup(reply); |
|
|
|
event = events_add(EVENTID_CREACC, trf_root); |
|
if (event == EVENT_OK) { |
|
u_item = users_add(conn, transfer_data(i_username), |
|
transfer_data(i_emailaddress), |
|
transfer_data(i_passwordhash), 0, |
|
by, code, inet, now, trf_root); |
|
} |
|
} |
|
|
|
if (!u_item) { |
|
LOGERR("%s() %s.failed.DBE", __func__, id); |
|
return reply_event(event, "failed.DBE"); |
|
} |
|
LOGDEBUG("%s.ok.added %s", id, transfer_data(i_username)); |
|
snprintf(reply, siz, "ok.added %s", transfer_data(i_username)); |
|
return strdup(reply); |
|
} |
|
|
|
static char *cmd_newpass(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
tv_t *now, char *by, char *code, char *inet, |
|
__maybe_unused tv_t *cd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_username, *i_oldhash, *i_newhash, *i_2fa, *u_item; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
int event = EVENT_OK; |
|
bool ok = true; |
|
char *oldhash; |
|
int32_t value; |
|
USERS *users; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", MIN_USERNAME, |
|
(char *)userpatt, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
i_oldhash = optional_name(trf_root, "oldhash", 64, (char *)hashpatt, |
|
reply, siz); |
|
if (i_oldhash) |
|
oldhash = transfer_data(i_oldhash); |
|
else { |
|
// fail if the oldhash is invalid |
|
if (*reply) |
|
ok = false; |
|
oldhash = EMPTY; |
|
} |
|
|
|
i_2fa = require_name(trf_root, "2fa", 1, (char *)intpatt, reply, siz); |
|
if (!i_2fa) { |
|
event = events_add(EVENTID_INV2FA, trf_root); |
|
return reply_event(event, reply); |
|
} |
|
|
|
if (ok) { |
|
i_newhash = require_name(trf_root, "newhash", |
|
64, (char *)hashpatt, |
|
reply, siz); |
|
if (!i_newhash) |
|
return strdup(reply); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
|
|
if (u_item) { |
|
DATA_USERS(users, u_item); |
|
if (USER_TOTP_ENA(users)) { |
|
value = (int32_t)atoi(transfer_data(i_2fa)); |
|
ok = check_2fa(users, value); |
|
if (!ok) |
|
event = events_add(EVENTID_WRONG2FA, trf_root); |
|
} |
|
if (ok) { |
|
ok = users_update(NULL, |
|
u_item, |
|
oldhash, |
|
transfer_data(i_newhash), |
|
NULL, |
|
by, code, inet, now, |
|
trf_root, |
|
NULL, &event); |
|
} |
|
} else |
|
ok = false; |
|
} |
|
|
|
if (!ok) { |
|
LOGERR("%s.failed.%s", id, transfer_data(i_username)); |
|
return reply_event(event, "failed."); |
|
} |
|
LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); |
|
return strdup("ok."); |
|
} |
|
|
|
static char *cmd_chkpass(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_username, *i_passwordhash, *i_2fa, *u_item; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
int event = EVENT_OK; |
|
USERS *users; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", MIN_USERNAME, |
|
(char *)userpatt, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
i_passwordhash = require_name(trf_root, "passwordhash", 64, (char *)hashpatt, reply, siz); |
|
if (!i_passwordhash) |
|
return strdup(reply); |
|
|
|
i_2fa = require_name(trf_root, "2fa", 1, (char *)intpatt, reply, siz); |
|
if (!i_2fa) { |
|
event = events_add(EVENTID_INV2FA, trf_root); |
|
return reply_event(event, reply); |
|
} |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
|
|
if (!u_item) { |
|
event = events_add(EVENTID_INVUSER, trf_root); |
|
ok = false; |
|
} else { |
|
DATA_USERS(users, u_item); |
|
ok = check_hash(users, transfer_data(i_passwordhash)); |
|
if (!ok) |
|
event = events_add(EVENTID_PASSFAIL, trf_root); |
|
if (ok && USER_TOTP_ENA(users)) { |
|
uint32_t value = (int32_t)atoi(transfer_data(i_2fa)); |
|
ok = check_2fa(users, value); |
|
if (!ok) |
|
event = events_add(EVENTID_WRONG2FA, trf_root); |
|
} |
|
} |
|
|
|
if (!ok) { |
|
LOGERR("%s.failed.%s", id, transfer_data(i_username)); |
|
return reply_event(event, "failed."); |
|
} |
|
LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); |
|
return strdup("ok."); |
|
} |
|
|
|
static char *cmd_2fa(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
tv_t *now, char *by, char *code, char *inet, |
|
__maybe_unused tv_t *notcd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_username, *i_action, *i_entropy, *i_value, *u_item, *u_new; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
int event = EVENT_OK; |
|
size_t len, off; |
|
char tmp[1024]; |
|
int32_t entropy, value; |
|
USERS *users; |
|
char *action, *buf = NULL, *st = NULL; |
|
char *sfa_status = EMPTY, *sfa_error = EMPTY, *sfa_msg = EMPTY; |
|
bool ok = false, key = false; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", MIN_USERNAME, |
|
(char *)userpatt, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
// Field always expected, blank means to report the status |
|
i_action = require_name(trf_root, "action", 0, NULL, reply, siz); |
|
if (!i_action) |
|
return strdup(reply); |
|
action = transfer_data(i_action); |
|
|
|
/* Field always expected with a value, |
|
* but the value is only used when generating a Secret Key */ |
|
i_entropy = require_name(trf_root, "entropy", 1, (char *)intpatt, |
|
reply, siz); |
|
if (!i_entropy) |
|
return strdup(reply); |
|
|
|
// Field always expected, use 0 if not required |
|
i_value = require_name(trf_root, "value", 1, (char *)intpatt, |
|
reply, siz); |
|
if (!i_value) { |
|
event = events_add(EVENTID_INV2FA, trf_root); |
|
return reply_event(event, reply); |
|
} |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
|
|
if (u_item) { |
|
DATA_USERS(users, u_item); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
|
|
switch (users->databits & (USER_TOTPAUTH | USER_TEST2FA)) { |
|
case 0: |
|
break; |
|
case USER_TOTPAUTH: |
|
sfa_status = "ok"; |
|
break; |
|
case (USER_TOTPAUTH | USER_TEST2FA): |
|
sfa_status = "test"; |
|
key = true; |
|
break; |
|
default: |
|
// USER_TEST2FA only <- currently invalid |
|
LOGERR("%s() users databits invalid for " |
|
"'%s/%"PRId64, |
|
__func__, |
|
st = safe_text_nonull(users->username), |
|
users->databits); |
|
FREENULL(st); |
|
goto dame; |
|
} |
|
|
|
if (!*action) { |
|
ok = true; |
|
} else if (strcmp(action, "setup") == 0) { |
|
// Can't setup if anything is already present -> new |
|
if (users->databits & (USER_TOTPAUTH | USER_TEST2FA)) |
|
goto dame; |
|
entropy = (int32_t)atoi(transfer_data(i_entropy)); |
|
u_new = gen_2fa_key(u_item, entropy, by, code, inet, |
|
now, trf_root); |
|
if (u_new) { |
|
ok = true; |
|
sfa_status = "test"; |
|
key = true; |
|
u_item = u_new; |
|
DATA_USERS(users, u_item); |
|
} |
|
} else if (strcmp(action, "test") == 0) { |
|
// Can't test if it's not ready to test |
|
if ((users->databits & (USER_TOTPAUTH | USER_TEST2FA)) |
|
!= (USER_TOTPAUTH | USER_TEST2FA)) |
|
goto dame; |
|
value = (int32_t)atoi(transfer_data(i_value)); |
|
ok = tst_2fa(u_item, value, by, code, inet, now, |
|
trf_root); |
|
if (!ok) |
|
sfa_error = "Invalid code"; |
|
else { |
|
key = false; |
|
sfa_status = "ok"; |
|
sfa_msg = "2FA Enabled"; |
|
} |
|
// Report sfa_error to web |
|
ok = true; |
|
} else if (strcmp(action, "untest") == 0) { |
|
// Can't untest if it's not ready to test |
|
if ((users->databits & (USER_TOTPAUTH | USER_TEST2FA)) |
|
!= (USER_TOTPAUTH | USER_TEST2FA)) |
|
goto dame; |
|
// since it's currently test, the value isn't required |
|
u_new = remove_2fa(u_item, 0, by, code, inet, now, |
|
trf_root, false); |
|
if (u_new) { |
|
ok = true; |
|
sfa_status = EMPTY; |
|
key = false; |
|
sfa_msg = "2FA Cancelled"; |
|
} |
|
} else if (strcmp(action, "new") == 0) { |
|
// Can't new if 2FA isn't already present -> setup |
|
if ((users->databits & USER_TOTPAUTH) == 0) |
|
goto dame; |
|
value = (int32_t)atoi(transfer_data(i_value)); |
|
if (!check_2fa(users, value)) { |
|
event = events_add(EVENTID_WRONG2FA, trf_root); |
|
sfa_error = "Invalid code"; |
|
// Report sfa_error to web |
|
ok = true; |
|
} else { |
|
entropy = (int32_t)atoi(transfer_data(i_entropy)); |
|
u_new = gen_2fa_key(u_item, entropy, by, code, |
|
inet, now, trf_root); |
|
if (u_new) { |
|
ok = true; |
|
sfa_status = "test"; |
|
key = true; |
|
u_item = u_new; |
|
DATA_USERS(users, u_item); |
|
} |
|
} |
|
} else if (strcmp(action, "remove") == 0) { |
|
// Can't remove if 2FA isn't already present |
|
if (!(users->databits & (USER_TOTPAUTH | USER_TEST2FA))) |
|
goto dame; |
|
// remove requires value |
|
value = (int32_t)atoi(transfer_data(i_value)); |
|
if (!check_2fa(users, value)) { |
|
event = events_add(EVENTID_WRONG2FA, trf_root); |
|
sfa_error = "Invalid code"; |
|
// Report sfa_error to web |
|
ok = true; |
|
} else { |
|
/* already tested 2fa so don't retest, also, |
|
* a retest will fail using the same value */ |
|
u_new = remove_2fa(u_item, value, by, code, |
|
inet, now, trf_root, false); |
|
if (u_new) { |
|
ok = true; |
|
sfa_status = EMPTY; |
|
key = false; |
|
sfa_msg = "2FA Removed"; |
|
} |
|
} |
|
} |
|
if (key) { |
|
char *keystr, *issuer = "KanoCKDB"; |
|
char cd_buf[DATE_BUFSIZ]; |
|
unsigned char *bin; |
|
OPTIONCONTROL *oc; |
|
K_ITEM *oc_item; |
|
size_t binlen; |
|
bin = users_userdata_get_bin(users, |
|
USER_TOTPAUTH_NAME, |
|
USER_TOTPAUTH, |
|
&binlen); |
|
if (binlen != TOTPAUTH_KEYSIZE) { |
|
LOGERR("%s() invalid key for '%s/%s " |
|
"len(%d) != %d", |
|
__func__, |
|
st = safe_text_nonull(users->username), |
|
USER_TOTPAUTH_NAME, (int)binlen, |
|
TOTPAUTH_KEYSIZE); |
|
FREENULL(st); |
|
} |
|
if (bin && binlen == TOTPAUTH_KEYSIZE) { |
|
keystr = tob32(users, bin, binlen, |
|
USER_TOTPAUTH_NAME, |
|
TOTPAUTH_DSP_KEYSIZE); |
|
snprintf(tmp, sizeof(tmp), "2fa_key=%s%c", |
|
keystr, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
FREENULL(keystr); |
|
|
|
oc_item = find_optioncontrol(TOTPAUTH_ISSUER, |
|
now, |
|
OPTIONCONTROL_HEIGHT); |
|
if (oc_item) { |
|
DATA_OPTIONCONTROL(oc, oc_item); |
|
issuer = oc->optionvalue; |
|
} else { |
|
tv_to_buf(now, cd_buf, sizeof(cd_buf)); |
|
LOGEMERG("%s(): missing optioncontrol " |
|
"%s (%s/%d)", |
|
__func__, TOTPAUTH_ISSUER, |
|
cd_buf, OPTIONCONTROL_HEIGHT); |
|
} |
|
|
|
// TODO: add issuer to optioncontrol |
|
snprintf(tmp, sizeof(tmp), |
|
"2fa_auth=%s%c2fa_hash=%s%c" |
|
"2fa_time=%d%c2fa_issuer=%s%c", |
|
TOTPAUTH_AUTH, FLDSEP, |
|
TOTPAUTH_HASH, FLDSEP, |
|
TOTPAUTH_TIME, FLDSEP, |
|
issuer, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
FREENULL(bin); |
|
} |
|
} |
|
|
|
if (!ok) { |
|
dame: |
|
// Only db/php/code errors should get here |
|
LOGERR("%s.failed.%s-%s", id, transfer_data(i_username), action); |
|
FREENULL(buf); |
|
return reply_event(event, "failed."); |
|
} |
|
|
|
snprintf(tmp, sizeof(tmp), "2fa_status=%s%c2fa_error=%s%c2fa_msg=%s", |
|
sfa_status, FLDSEP, sfa_error, FLDSEP, |
|
sfa_msg); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
LOGDEBUG("%s.%s-%s.%s", id, transfer_data(i_username), action, buf); |
|
return reply_event_free(event, buf); |
|
} |
|
|
|
static char *cmd_userset(PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_username, *i_passwordhash, *i_2fa, *i_rows, *i_address; |
|
K_ITEM *i_ratio, *i_payname, *i_email, *u_item, *pa_item, *old_pa_item; |
|
K_ITEM *ua_item = NULL; |
|
USERATTS *useratts = NULL; |
|
char *email, *address, *payname; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
int event = EVENT_OK; |
|
char tmp[1024]; |
|
PAYMENTADDRESSES *row, *pa; |
|
K_STORE *pa_store = NULL; |
|
K_TREE_CTX ctx[1]; |
|
USERS *users; |
|
char *reason = NULL; |
|
char *answer = NULL; |
|
char *ret = NULL; |
|
size_t len, off; |
|
int32_t ratio; |
|
int rows, i, limit; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", MIN_USERNAME, |
|
(char *)userpatt, reply, siz); |
|
if (!i_username) { |
|
// For web this message is detailed enough |
|
reason = "System error"; |
|
goto struckout; |
|
} |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
|
|
if (!u_item) { |
|
event = events_add(EVENTID_UNKATTS, trf_root); |
|
reason = "Unknown user"; |
|
goto struckout; |
|
} else { |
|
DATA_USERS(users, u_item); |
|
i_passwordhash = optional_name(trf_root, "passwordhash", |
|
64, (char *)hashpatt, |
|
reply, siz); |
|
if (*reply) { |
|
reason = "Invalid data"; |
|
goto struckout; |
|
} |
|
|
|
K_RLOCK(useratts_free); |
|
ua_item = find_useratts(users->userid, USER_MULTI_PAYOUT); |
|
K_RUNLOCK(useratts_free); |
|
if (!ua_item) |
|
limit = 1; |
|
else { |
|
DATA_USERATTS(useratts, ua_item); |
|
if (useratts->attnum > 0) |
|
limit = (int)(useratts->attnum); |
|
else |
|
limit = USER_ADDR_LIMIT; |
|
} |
|
|
|
if (!i_passwordhash) { |
|
APPEND_REALLOC_INIT(answer, off, len); |
|
snprintf(tmp, sizeof(tmp), "email=%s%c", |
|
users->emailaddress, FLDSEP); |
|
APPEND_REALLOC(answer, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "limit=%d%c", limit, FLDSEP); |
|
APPEND_REALLOC(answer, off, len, tmp); |
|
|
|
K_RLOCK(paymentaddresses_free); |
|
pa_item = find_paymentaddresses(users->userid, ctx); |
|
rows = 0; |
|
if (pa_item) { |
|
DATA_PAYMENTADDRESSES(row, pa_item); |
|
while (pa_item && CURRENT(&(row->expirydate)) && |
|
row->userid == users->userid) { |
|
snprintf(tmp, sizeof(tmp), "addr:%d=%s%c", |
|
rows, row->payaddress, FLDSEP); |
|
APPEND_REALLOC(answer, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "ratio:%d=%d%c", |
|
rows, row->payratio, FLDSEP); |
|
APPEND_REALLOC(answer, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "payname:%d=%s%c", |
|
rows, row->payname, FLDSEP); |
|
APPEND_REALLOC(answer, off, len, tmp); |
|
rows++; |
|
|
|
pa_item = prev_in_ktree(ctx); |
|
DATA_PAYMENTADDRESSES_NULL(row, pa_item); |
|
} |
|
} |
|
K_RUNLOCK(paymentaddresses_free); |
|
|
|
snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", |
|
rows, FLDSEP, |
|
"addr,ratio,payname", FLDSEP); |
|
APPEND_REALLOC(answer, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", |
|
"PaymentAddresses", FLDSEP, ""); |
|
APPEND_REALLOC(answer, off, len, tmp); |
|
} else { |
|
i_2fa = require_name(trf_root, "2fa", 1, (char *)intpatt, |
|
reply, siz); |
|
if (!i_2fa) { |
|
event = events_add(EVENTID_INV2FA, trf_root); |
|
reason = "Invalid data"; |
|
goto struckout; |
|
} |
|
|
|
if (!check_hash(users, transfer_data(i_passwordhash))) { |
|
event = events_add(EVENTID_PASSFAIL, trf_root); |
|
reason = "Incorrect password"; |
|
goto struckout; |
|
} |
|
|
|
if (USER_TOTP_ENA(users)) { |
|
uint32_t value = (int32_t)atoi(transfer_data(i_2fa)); |
|
if (!check_2fa(users, value)) { |
|
event = events_add(EVENTID_WRONG2FA, trf_root); |
|
reason = "Invalid data"; |
|
goto struckout; |
|
} |
|
} |
|
|
|
i_email = optional_name(trf_root, "email", |
|
1, (char *)mailpatt, |
|
reply, siz); |
|
if (i_email) |
|
email = transfer_data(i_email); |
|
else { |
|
if (*reply) { |
|
reason = "Invalid email"; |
|
goto struckout; |
|
} |
|
email = NULL; |
|
} |
|
|
|
// address rows |
|
i_rows = optional_name(trf_root, "rows", |
|
1, (char *)intpatt, |
|
reply, siz); |
|
if (!i_rows && *reply) { |
|
// Exists, but invalid |
|
reason = "System error"; |
|
goto struckout; |
|
} |
|
if (i_rows) { |
|
rows = atoi(transfer_data(i_rows)); |
|
if (rows < 0) { |
|
reason = "System error"; |
|
goto struckout; |
|
} |
|
if (rows > 0) { |
|
pa_store = k_new_store(paymentaddresses_free); |
|
K_WLOCK(paymentaddresses_free); |
|
// discard any extras above the limit |
|
for (i = 0; i < rows && pa_store->count < limit; i++) { |
|
snprintf(tmp, sizeof(tmp), "ratio:%d", i); |
|
i_ratio = optional_name(trf_root, tmp, |
|
1, (char *)intpatt, |
|
reply, siz); |
|
if (*reply) { |
|
K_WUNLOCK(paymentaddresses_free); |
|
reason = "Invalid ratio"; |
|
goto struckout; |
|
} |
|
if (i_ratio) |
|
ratio = atoi(transfer_data(i_ratio)); |
|
else |
|
ratio = PAYRATIODEF; |
|
|
|
/* 0 = expire/remove the address |
|
* intpatt means it will be >= 0 */ |
|
if (ratio == 0) |
|
continue; |
|
|
|
snprintf(tmp, sizeof(tmp), "address:%d", i); |
|
i_address = require_name(trf_root, tmp, |
|
ADDR_MIN_LEN, |
|
(char *)addrpatt, |
|
reply, siz); |
|
if (!i_address) { |
|
K_WUNLOCK(paymentaddresses_free); |
|
event = events_add(EVENTID_INCBTC, |
|
trf_root); |
|
reason = "Invalid address"; |
|
goto struckout; |
|
} |
|
address = transfer_data(i_address); |
|
pa_item = STORE_HEAD_NOLOCK(pa_store); |
|
while (pa_item) { |
|
DATA_PAYMENTADDRESSES(row, pa_item); |
|
if (strcmp(row->payaddress, address) == 0) { |
|
K_WUNLOCK(paymentaddresses_free); |
|
reason = "Duplicate address"; |
|
goto struckout; |
|
} |
|
pa_item = pa_item->next; |
|
} |
|
snprintf(tmp, sizeof(tmp), "payname:%d", i); |
|
i_payname = optional_name(trf_root, tmp, |
|
0, NULL, |
|
reply, siz); |
|
if (i_payname) |
|
payname = transfer_data(i_payname); |
|
else |
|
payname = EMPTY; |
|
pa_item = k_unlink_head(paymentaddresses_free); |
|
DATA_PAYMENTADDRESSES(row, pa_item); |
|
bzero(row, sizeof(*row)); |
|
STRNCPY(row->payaddress, address); |
|
row->payratio = ratio; |
|
STRNCPY(row->payname, payname); |
|
k_add_head(pa_store, pa_item); |
|
} |
|
K_WUNLOCK(paymentaddresses_free); |
|
} |
|
} |
|
/* If all addresses have a ratio of zero |
|
* pa_store->count will be 0 */ |
|
if ((email == NULL || *email == '\0') && |
|
(pa_store == NULL || pa_store->count == 0)) { |
|
reason = "Missing/Invalid value"; |
|
goto struckout; |
|
} |
|
|
|
if (pa_store && pa_store->count > 0) { |
|
pa_item = STORE_HEAD_NOLOCK(pa_store); |
|
while (pa_item) { |
|
DATA_PAYMENTADDRESSES(row, pa_item); |
|
// Only EVER validate addresses once ... for now |
|
K_RLOCK(paymentaddresses_free); |
|
old_pa_item = find_any_payaddress(row->payaddress); |
|
K_RUNLOCK(paymentaddresses_free); |
|
if (old_pa_item) { |
|
/* This test effectively means that |
|
* two users can never add the same |
|
* payout address */ |
|
DATA_PAYMENTADDRESSES(pa, old_pa_item); |
|
if (pa->userid != users->userid) { |
|
event = events_add(EVENTID_BTCUSED, |
|
trf_root); |
|
reason = "Unavailable BTC address"; |
|
goto struckout; |
|
} |
|
} else if (!btc_valid_address(row->payaddress)) { |
|
event = events_add(EVENTID_INVBTC, |
|
trf_root); |
|
reason = "Invalid BTC address"; |
|
goto struckout; |
|
} |
|
pa_item = pa_item->next; |
|
} |
|
} |
|
|
|
if (email && *email) { |
|
ok = users_update(conn, u_item, |
|
NULL, NULL, |
|
email, |
|
by, code, inet, now, |
|
trf_root, |
|
NULL, &event); |
|
if (!ok) { |
|
reason = "email error"; |
|
goto struckout; |
|
} |
|
} |
|
|
|
if (pa_store && pa_store->count > 0) { |
|
ok = paymentaddresses_set(conn, users->userid, |
|
pa_store, by, |
|
code, inet, now, |
|
trf_root); |
|
if (!ok) { |
|
reason = "address error"; |
|
goto struckout; |
|
} |
|
} |
|
answer = strdup("updated"); |
|
} |
|
} |
|
|
|
struckout: |
|
if (pa_store) { |
|
if (pa_store->count) { |
|
K_WLOCK(paymentaddresses_free); |
|
k_list_transfer_to_head(pa_store, paymentaddresses_free); |
|
K_WUNLOCK(paymentaddresses_free); |
|
} |
|
k_free_store(pa_store); |
|
pa_store = NULL; |
|
} |
|
if (reason) { |
|
char *user, *st = NULL; |
|
snprintf(reply, siz, "ERR.%s", reason); |
|
if (i_username) |
|
user = st = safe_text(transfer_data(i_username)); |
|
else |
|
user = EMPTY; |
|
LOGERR("%s.%s.%s (%s)", cmd, id, reply, user); |
|
FREENULL(st); |
|
return reply_event(event, reply); |
|
} |
|
APPEND_REALLOC_INIT(ret, off, len); |
|
APPEND_REALLOC(ret, off, len, "ok."); |
|
APPEND_REALLOC(ret, off, len, answer); |
|
free(answer); |
|
LOGDEBUG("%s.%s", id, ret); |
|
return reply_event_free(event, ret); |
|
} |
|
|
|
static char *cmd_workerset(PGconn *conn, char *cmd, char *id, tv_t *now, |
|
char *by, char *code, char *inet, tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_username, *i_workername, *i_diffdef, *i_oldworkers; |
|
K_ITEM *u_item, *ua_item, *w_item; |
|
HEARTBEATQUEUE *heartbeatqueue; |
|
K_ITEM *hq_item; |
|
char workername_buf[32]; // 'workername:' + digits |
|
char diffdef_buf[32]; // 'difficultydefault:' + digits |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
USERATTS *useratts; |
|
WORKERS *workers; |
|
USERS *users; |
|
int32_t difficultydefault; |
|
char *reason = NULL; |
|
char *answer = NULL; |
|
int workernum; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", MIN_USERNAME, |
|
(char *)userpatt, reply, siz); |
|
if (!i_username) { |
|
// For web this message is detailed enough |
|
reason = "System error"; |
|
goto struckout; |
|
} |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
|
|
if (!u_item) { |
|
reason = "Unknown user"; |
|
goto struckout; |
|
} else { |
|
DATA_USERS(users, u_item); |
|
|
|
// Default answer if no problems |
|
answer = strdup("updated"); |
|
|
|
i_oldworkers = optional_name(trf_root, "oldworkers", |
|
1, NULL, reply, siz); |
|
if (i_oldworkers) { |
|
bool update = false; |
|
int64_t new_ow = atol(transfer_data(i_oldworkers)); |
|
|
|
K_RLOCK(useratts_free); |
|
ua_item = find_useratts(users->userid, USER_OLD_WORKERS); |
|
K_RUNLOCK(useratts_free); |
|
if (!ua_item) { |
|
if (new_ow != USER_OLD_WORKERS_DEFAULT) |
|
update = true; |
|
} else { |
|
DATA_USERATTS(useratts, ua_item); |
|
if (new_ow != useratts->attnum) |
|
update = true; |
|
} |
|
if (update) { |
|
ua_item = useratts_add(conn, users->username, |
|
USER_OLD_WORKERS, EMPTY, |
|
EMPTY, EMPTY, |
|
transfer_data(i_oldworkers), |
|
EMPTY, EMPTY, EMPTY, |
|
by, code, inet, cd, |
|
trf_root, false); |
|
if (!ua_item) |
|
reason = "Invalid"; |
|
} |
|
goto kazuki; |
|
} |
|
|
|
// Loop through the list of workers and do any changes |
|
for (workernum = 0; workernum < 9999; workernum++) { |
|
snprintf(workername_buf, sizeof(workername_buf), |
|
"workername:%d", workernum); |
|
|
|
i_workername = optional_name(trf_root, workername_buf, |
|
1, NULL, reply, siz); |
|
if (!i_workername) |
|
break; |
|
|
|
w_item = find_workers(false, users->userid, |
|
transfer_data(i_workername)); |
|
// Abort if any dont exist |
|
if (!w_item) { |
|
reason = "Unknown worker"; |
|
break; |
|
} |
|
|
|
DATA_WORKERS(workers, w_item); |
|
|
|
snprintf(diffdef_buf, sizeof(diffdef_buf), |
|
"difficultydefault:%d", workernum); |
|
|
|
i_diffdef = optional_name(trf_root, diffdef_buf, |
|
1, (char *)intpatt, |
|
reply, siz); |
|
|
|
// Abort if any are invalid |
|
if (*reply) { |
|
reason = "Invalid diff"; |
|
break; |
|
} |
|
|
|
if (!i_diffdef) |
|
continue; |
|
|
|
difficultydefault = atoi(transfer_data(i_diffdef)); |
|
if (difficultydefault != 0) { |
|
if (difficultydefault < DIFFICULTYDEFAULT_MIN) |
|
difficultydefault = DIFFICULTYDEFAULT_MIN; |
|
if (difficultydefault > DIFFICULTYDEFAULT_MAX) |
|
difficultydefault = DIFFICULTYDEFAULT_MAX; |
|
} |
|
|
|
if (workers->difficultydefault != difficultydefault) { |
|
/* This uses a seperate txn per update |
|
thus will update all up to a failure |
|
Since the web then re-gets the values, |
|
it will show what was updated */ |
|
workers->difficultydefault = difficultydefault; |
|
ok = workers_update(conn, w_item, NULL, NULL, |
|
NULL, by, code, inet, |
|
now, trf_root, false); |
|
if (!ok) { |
|
reason = "DB error"; |
|
break; |
|
} |
|
|
|
/* workerset is not from a log file, |
|
so always queue it */ |
|
K_WLOCK(heartbeatqueue_free); |
|
hq_item = k_unlink_head(heartbeatqueue_free); |
|
K_WUNLOCK(heartbeatqueue_free); |
|
|
|
DATA_HEARTBEATQUEUE(heartbeatqueue, hq_item); |
|
STRNCPY(heartbeatqueue->workername, workers->workername); |
|
heartbeatqueue->difficultydefault = workers->difficultydefault; |
|
copy_tv(&(heartbeatqueue->createdate), now); |
|
|
|
K_WLOCK(heartbeatqueue_free); |
|
k_add_tail(heartbeatqueue_store, hq_item); |
|
K_WUNLOCK(heartbeatqueue_free); |
|
} |
|
} |
|
} |
|
|
|
kazuki: |
|
struckout: |
|
if (reason) { |
|
if (answer) |
|
free(answer); |
|
snprintf(reply, siz, "ERR.%s", reason); |
|
LOGERR("%s.%s.%s", cmd, id, reply); |
|
return strdup(reply); |
|
} |
|
snprintf(reply, siz, "ok.%s", answer); |
|
LOGDEBUG("%s.%s", id, answer); |
|
free(answer); |
|
return strdup(reply); |
|
} |
|
|
|
static char *cmd_poolstats_do(PGconn *conn, char *cmd, char *id, char *by, |
|
char *code, char *inet, tv_t *cd, bool igndup, |
|
K_TREE *trf_root) |
|
{ |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
K_TREE_CTX ctx[1]; |
|
bool store; |
|
|
|
// log to logfile |
|
|
|
K_ITEM *i_poolinstance, *i_elapsed, *i_users, *i_workers; |
|
K_ITEM *i_hashrate, *i_hashrate5m, *i_hashrate1hr, *i_hashrate24hr; |
|
K_ITEM look, *ps; |
|
POOLSTATS row, *poolstats; |
|
bool ok = false; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz); |
|
if (!i_poolinstance) |
|
return strdup(reply); |
|
|
|
if (poolinstance && strcmp(poolinstance, transfer_data(i_poolinstance))) { |
|
POOLINSTANCE_DATA_SET(poolstats, transfer_data(i_poolinstance)); |
|
return strdup(FAILED_PI); |
|
} |
|
|
|
i_elapsed = optional_name(trf_root, "elapsed", 1, NULL, reply, siz); |
|
if (!i_elapsed) |
|
i_elapsed = &poolstats_elapsed; |
|
|
|
i_users = require_name(trf_root, "users", 1, NULL, reply, siz); |
|
if (!i_users) |
|
return strdup(reply); |
|
|
|
i_workers = require_name(trf_root, "workers", 1, NULL, reply, siz); |
|
if (!i_workers) |
|
return strdup(reply); |
|
|
|
i_hashrate = require_name(trf_root, "hashrate", 1, NULL, reply, siz); |
|
if (!i_hashrate) |
|
return strdup(reply); |
|
|
|
i_hashrate5m = require_name(trf_root, "hashrate5m", 1, NULL, reply, siz); |
|
if (!i_hashrate5m) |
|
return strdup(reply); |
|
|
|
i_hashrate1hr = require_name(trf_root, "hashrate1hr", 1, NULL, reply, siz); |
|
if (!i_hashrate1hr) |
|
return strdup(reply); |
|
|
|
i_hashrate24hr = require_name(trf_root, "hashrate24hr", 1, NULL, reply, siz); |
|
if (!i_hashrate24hr) |
|
return strdup(reply); |
|
|
|
STRNCPY(row.poolinstance, transfer_data(i_poolinstance)); |
|
row.createdate.tv_sec = date_eot.tv_sec; |
|
row.createdate.tv_usec = date_eot.tv_usec; |
|
INIT_POOLSTATS(&look); |
|
look.data = (void *)(&row); |
|
K_RLOCK(poolstats_free); |
|
ps = find_before_in_ktree(poolstats_root, &look, ctx); |
|
K_RUNLOCK(poolstats_free); |
|
if (!ps) |
|
store = true; |
|
else { |
|
DATA_POOLSTATS(poolstats, ps); |
|
// Find last stored matching the poolinstance and less than STATS_PER old |
|
while (ps && !poolstats->stored && |
|
strcmp(row.poolinstance, poolstats->poolinstance) == 0 && |
|
tvdiff(cd, &(poolstats->createdate)) < STATS_PER) { |
|
ps = prev_in_ktree(ctx); |
|
DATA_POOLSTATS_NULL(poolstats, ps); |
|
} |
|
|
|
if (!ps || !poolstats->stored || |
|
strcmp(row.poolinstance, poolstats->poolinstance) != 0 || |
|
tvdiff(cd, &(poolstats->createdate)) >= STATS_PER) |
|
store = true; |
|
else |
|
store = false; |
|
} |
|
|
|
ok = poolstats_add(conn, store, transfer_data(i_poolinstance), |
|
transfer_data(i_elapsed), |
|
transfer_data(i_users), |
|
transfer_data(i_workers), |
|
transfer_data(i_hashrate), |
|
transfer_data(i_hashrate5m), |
|
transfer_data(i_hashrate1hr), |
|
transfer_data(i_hashrate24hr), |
|
by, code, inet, cd, igndup, trf_root); |
|
|
|
if (!ok) { |
|
if (!igndup) |
|
LOGERR("%s() %s.failed.DBE", __func__, id); |
|
return strdup("failed.DBE"); |
|
} |
|
LOGDEBUG("%s.ok.", id); |
|
snprintf(reply, siz, "ok."); |
|
return strdup(reply); |
|
} |
|
|
|
static char *cmd_poolstats(PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *notnow, char *by, |
|
char *code, char *inet, tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
bool igndup = false; |
|
|
|
/* confirm_summaries() doesn't call this |
|
* We don't care about dups during reload since poolstats_fill() |
|
* doesn't load all the data */ |
|
if (reloading) |
|
igndup = true; |
|
|
|
return cmd_poolstats_do(conn, cmd, id, by, code, inet, cd, igndup, trf_root); |
|
} |
|
|
|
static char *cmd_userstats(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *notnow, char *by, char *code, |
|
char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
|
|
// log to logfile |
|
|
|
K_ITEM *i_poolinstance, *i_elapsed, *i_username, *i_workername; |
|
K_ITEM *i_hashrate, *i_hashrate5m, *i_hashrate1hr, *i_hashrate24hr; |
|
K_ITEM *i_eos, *i_idle; |
|
bool ok = false, idle, eos; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz); |
|
if (!i_poolinstance) |
|
return strdup(reply); |
|
|
|
if (poolinstance && strcmp(poolinstance, transfer_data(i_poolinstance))) { |
|
POOLINSTANCE_DATA_SET(userstats, transfer_data(i_poolinstance)); |
|
return strdup(FAILED_PI); |
|
} |
|
|
|
i_elapsed = optional_name(trf_root, "elapsed", 1, NULL, reply, siz); |
|
if (!i_elapsed) |
|
i_elapsed = &userstats_elapsed; |
|
|
|
i_username = require_name(trf_root, "username", 1, NULL, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
i_workername = optional_name(trf_root, "workername", 1, NULL, reply, siz); |
|
if (!i_workername) |
|
i_workername = &userstats_workername; |
|
|
|
i_hashrate = require_name(trf_root, "hashrate", 1, NULL, reply, siz); |
|
if (!i_hashrate) |
|
return strdup(reply); |
|
|
|
i_hashrate5m = require_name(trf_root, "hashrate5m", 1, NULL, reply, siz); |
|
if (!i_hashrate5m) |
|
return strdup(reply); |
|
|
|
i_hashrate1hr = require_name(trf_root, "hashrate1hr", 1, NULL, reply, siz); |
|
if (!i_hashrate1hr) |
|
return strdup(reply); |
|
|
|
i_hashrate24hr = require_name(trf_root, "hashrate24hr", 1, NULL, reply, siz); |
|
if (!i_hashrate24hr) |
|
return strdup(reply); |
|
|
|
i_idle = optional_name(trf_root, "idle", 1, NULL, reply, siz); |
|
if (!i_idle) |
|
i_idle = &userstats_idle; |
|
|
|
idle = (strcasecmp(transfer_data(i_idle), TRUE_STR) == 0); |
|
|
|
i_eos = optional_name(trf_root, "eos", 1, NULL, reply, siz); |
|
if (!i_eos) |
|
i_eos = &userstats_eos; |
|
|
|
eos = (strcasecmp(transfer_data(i_eos), TRUE_STR) == 0); |
|
|
|
ok = userstats_add(transfer_data(i_poolinstance), |
|
transfer_data(i_elapsed), |
|
transfer_data(i_username), |
|
transfer_data(i_workername), |
|
transfer_data(i_hashrate), |
|
transfer_data(i_hashrate5m), |
|
transfer_data(i_hashrate1hr), |
|
transfer_data(i_hashrate24hr), |
|
idle, eos, by, code, inet, cd, trf_root); |
|
|
|
if (!ok) { |
|
LOGERR("%s() %s.failed.DATA", __func__, id); |
|
return strdup("failed.DATA"); |
|
} |
|
LOGDEBUG("%s.ok.", id); |
|
snprintf(reply, siz, "ok."); |
|
return strdup(reply); |
|
} |
|
|
|
static char *cmd_workerstats(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *notnow, char *by, char *code, |
|
char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
|
|
// log to logfile |
|
|
|
K_ITEM *i_poolinstance, *i_elapsed, *i_username, *i_workername; |
|
K_ITEM *i_hashrate, *i_hashrate5m, *i_hashrate1hr, *i_hashrate24hr; |
|
K_ITEM *i_idle, *i_instances; |
|
bool ok = false, idle; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz); |
|
if (!i_poolinstance) |
|
return strdup(reply); |
|
|
|
if (poolinstance && strcmp(poolinstance, transfer_data(i_poolinstance))) { |
|
POOLINSTANCE_DATA_SET(workerstats, transfer_data(i_poolinstance)); |
|
return strdup(FAILED_PI); |
|
} |
|
|
|
i_elapsed = require_name(trf_root, "elapsed", 1, NULL, reply, siz); |
|
if (!i_elapsed) |
|
return strdup(reply); |
|
|
|
i_username = require_name(trf_root, "username", 1, NULL, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz); |
|
if (!i_workername) |
|
return strdup(reply); |
|
|
|
i_hashrate = require_name(trf_root, "hashrate", 1, NULL, reply, siz); |
|
if (!i_hashrate) |
|
return strdup(reply); |
|
|
|
i_hashrate5m = require_name(trf_root, "hashrate5m", 1, NULL, reply, siz); |
|
if (!i_hashrate5m) |
|
return strdup(reply); |
|
|
|
i_hashrate1hr = require_name(trf_root, "hashrate1hr", 1, NULL, reply, siz); |
|
if (!i_hashrate1hr) |
|
return strdup(reply); |
|
|
|
i_hashrate24hr = require_name(trf_root, "hashrate24hr", 1, NULL, reply, siz); |
|
if (!i_hashrate24hr) |
|
return strdup(reply); |
|
|
|
i_idle = require_name(trf_root, "idle", 1, NULL, reply, siz); |
|
if (!i_idle) |
|
return strdup(reply); |
|
|
|
idle = (strcasecmp(transfer_data(i_idle), TRUE_STR) == 0); |
|
|
|
i_instances = optional_name(trf_root, "instances", 1, NULL, reply, siz); |
|
|
|
ok = workerstats_add(transfer_data(i_poolinstance), |
|
transfer_data(i_elapsed), |
|
transfer_data(i_username), |
|
transfer_data(i_workername), |
|
transfer_data(i_hashrate), |
|
transfer_data(i_hashrate5m), |
|
transfer_data(i_hashrate1hr), |
|
transfer_data(i_hashrate24hr), idle, |
|
i_instances ? transfer_data(i_instances) : NULL, |
|
by, code, inet, cd, trf_root); |
|
|
|
if (!ok) { |
|
LOGERR("%s() %s.failed.DATA", __func__, id); |
|
return strdup("failed.DATA"); |
|
} |
|
LOGDEBUG("%s.ok.", id); |
|
snprintf(reply, siz, "ok."); |
|
return strdup(reply); |
|
} |
|
|
|
static char *cmd_blocklist(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, |
|
__maybe_unused K_TREE *trf_root) |
|
{ |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM *b_item; |
|
BLOCKS *blocks; |
|
char reply[1024] = ""; |
|
char tmp[1024]; |
|
char *buf, *desc, desc_buf[64]; |
|
size_t len, off; |
|
tv_t stats_tv = {0,0}, stats_tv2 = {0,0}; |
|
int rows, srows, tot, seq; |
|
int64_t maxrows; |
|
bool has_stats; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
maxrows = sys_setting(BLOCKS_SETTING_NAME, BLOCKS_DEFAULT, now); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
|
|
redo: |
|
has_stats = check_update_blocks_stats(&stats_tv); |
|
|
|
srows = rows = 0; |
|
K_RLOCK(blocks_free); |
|
b_item = first_in_ktree(blocks_root, ctx); |
|
tot = 0; |
|
while (b_item) { |
|
DATA_BLOCKS(blocks, b_item); |
|
if (CURRENT(&(blocks->expirydate))) { |
|
if (blocks->confirmed[0] != BLOCKS_ORPHAN && |
|
blocks->confirmed[0] != BLOCKS_REJECT) |
|
tot++; |
|
} |
|
b_item = next_in_ktree(ctx); |
|
} |
|
seq = tot; |
|
b_item = last_in_ktree(blocks_root, ctx); |
|
while (b_item && rows < (int)maxrows) { |
|
DATA_BLOCKS(blocks, b_item); |
|
if (CURRENT(&(blocks->expirydate))) { |
|
if (blocks->confirmed[0] == BLOCKS_ORPHAN || |
|
blocks->confirmed[0] == BLOCKS_REJECT) { |
|
snprintf(tmp, sizeof(tmp), |
|
"seq:%d=%c%c", |
|
rows, |
|
blocks->confirmed[0] == BLOCKS_ORPHAN ? |
|
'o' : 'r', |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} else { |
|
snprintf(tmp, sizeof(tmp), |
|
"seq:%d=%d%c", |
|
rows, seq--, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
int_to_buf(blocks->height, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "height:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
dbhash2btchash(blocks->blockhash, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "blockhash:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
str_to_buf(blocks->nonce, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "nonce:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
bigint_to_buf(blocks->reward, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "reward:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
str_to_buf(blocks->workername, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "workername:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
// When block was found |
|
snprintf(tmp, sizeof(tmp), |
|
"first"CDTRF":%d=%ld%c", rows, |
|
blocks->blockcreatedate.tv_sec, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
// Last time block was updated |
|
snprintf(tmp, sizeof(tmp), |
|
CDTRF":%d=%ld%c", rows, |
|
blocks->createdate.tv_sec, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
// When previous valid block was found |
|
snprintf(tmp, sizeof(tmp), |
|
"prev"CDTRF":%d=%ld%c", rows, |
|
blocks->prevcreatedate.tv_sec, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"confirmed:%d=%s%cstatus:%d=%s%c", rows, |
|
blocks->confirmed, FLDSEP, rows, |
|
blocks_confirmed(blocks->confirmed), FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"info:%d=%s%c", rows, |
|
blocks->info, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"statsconf:%d=%s%c", rows, |
|
blocks->statsconfirmed, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(blocks->diffacc, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "diffacc:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(blocks->diffinv, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "diffinv:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(blocks->shareacc, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "shareacc:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(blocks->shareinv, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "shareinv:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
bigint_to_buf(blocks->elapsed, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "elapsed:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
if (has_stats) { |
|
snprintf(tmp, sizeof(tmp), |
|
"netdiff:%d=%.8f%cdiffratio:%d=%.8f%c" |
|
"cdf:%d=%.8f%cluck:%d=%.8f%c", |
|
rows, blocks->netdiff, FLDSEP, |
|
rows, blocks->blockdiffratio, FLDSEP, |
|
rows, blocks->blockcdf, FLDSEP, |
|
rows, blocks->blockluck, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} else { |
|
snprintf(tmp, sizeof(tmp), |
|
"netdiff:%d=?%cdiffratio:%d=?%c" |
|
"cdf:%d=?%cluck:%d=?%c", |
|
rows, FLDSEP, rows, FLDSEP, |
|
rows, FLDSEP, rows, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
|
|
rows++; |
|
} |
|
b_item = prev_in_ktree(ctx); |
|
} |
|
if (has_stats) { |
|
seq = tot; |
|
b_item = last_in_ktree(blocks_root, ctx); |
|
while (b_item) { |
|
DATA_BLOCKS(blocks, b_item); |
|
if (CURRENT(&(blocks->expirydate)) && |
|
blocks->confirmed[0] != BLOCKS_ORPHAN && |
|
blocks->confirmed[0] != BLOCKS_REJECT) { |
|
desc = NULL; |
|
if (seq == 1) { |
|
snprintf(desc_buf, sizeof(desc_buf), |
|
"All - Last %d", tot); |
|
desc = desc_buf; |
|
} else if (seq == tot - 4) { |
|
desc = "Last 5"; |
|
} else if (seq == tot - 9) { |
|
desc = "Last 10"; |
|
} else if (seq == tot - 24) { |
|
desc = "Last 25"; |
|
} else if (seq == tot - 49) { |
|
desc = "Last 50"; |
|
} else if (seq == tot - 99) { |
|
desc = "Last 100"; |
|
} else if (seq == tot - 249) { |
|
desc = "Last 250"; |
|
} else if (seq == tot - 499) { |
|
desc = "Last 500"; |
|
} else if (seq == tot - 999) { |
|
desc = "Last 1000"; |
|
} |
|
if (desc) { |
|
snprintf(tmp, sizeof(tmp), |
|
"s_seq:%d=%d%c" |
|
"s_desc:%d=%s%c" |
|
"s_height:%d=%d%c" |
|
"s_"CDTRF":%d=%ld%c" |
|
"s_prev"CDTRF":%d=%ld%c" |
|
"s_diffratio:%d=%.8f%c" |
|
"s_diffmean:%d=%.8f%c" |
|
"s_cdferl:%d=%.8f%c" |
|
"s_luck:%d=%.8f%c" |
|
"s_txmean:%d=%.8f%c", |
|
srows, seq, FLDSEP, |
|
srows, desc, FLDSEP, |
|
srows, (int)(blocks->height), FLDSEP, |
|
srows, blocks->blockcreatedate.tv_sec, FLDSEP, |
|
srows, blocks->prevcreatedate.tv_sec, FLDSEP, |
|
srows, blocks->diffratio, FLDSEP, |
|
srows, blocks->diffmean, FLDSEP, |
|
srows, blocks->cdferl, FLDSEP, |
|
srows, blocks->luck, FLDSEP, |
|
srows, blocks->txmean, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
srows++; |
|
} |
|
seq--; |
|
} |
|
b_item = prev_in_ktree(ctx); |
|
} |
|
copy_tv(&stats_tv2, &blocks_stats_time); |
|
} |
|
K_RUNLOCK(blocks_free); |
|
|
|
// Only check for a redo if we used the stats values |
|
if (has_stats) { |
|
/* If the stats changed then redo with the new corrected values |
|
* This isn't likely at all, but it guarantees the blocks |
|
* page shows correct information since any code that wants |
|
* to modify the blocks table must have it under write lock |
|
* then flag the stats as needing to be recalculated */ |
|
if (!tv_equal(&stats_tv, &stats_tv2)) { |
|
APPEND_REALLOC_RESET(buf, off); |
|
goto redo; |
|
} |
|
} |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"s_rows=%d%cs_flds=%s%c", |
|
srows, FLDSEP, |
|
"s_seq,s_desc,s_height,s_"CDTRF",s_prev"CDTRF",s_diffratio," |
|
"s_diffmean,s_cdferl,s_luck,s_txmean", |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"rows=%d%cflds=%s%c", |
|
rows, FLDSEP, |
|
"seq,height,blockhash,nonce,reward,workername,first"CDTRF"," |
|
CDTRF",prev"CDTRF",confirmed,status,info,statsconf,diffacc," |
|
"diffinv,shareacc,shareinv,elapsed,netdiff,diffratio,cdf,luck", |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Blocks,BlockStats", FLDSEP, ",s"); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
LOGDEBUG("%s.ok.%d_blocks", id, rows); |
|
return buf; |
|
} |
|
|
|
static char *cmd_blockstatus(PGconn *conn, char *cmd, char *id, tv_t *now, |
|
char *by, char *code, char *inet, |
|
__maybe_unused tv_t *cd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_height, *i_blockhash, *i_action, *i_info; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
K_ITEM *b_item; |
|
BLOCKS *blocks; |
|
int32_t height; |
|
char *action, *info, *tmp; |
|
bool ok = false; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_height = require_name(trf_root, "height", 1, NULL, reply, siz); |
|
if (!i_height) |
|
return strdup(reply); |
|
|
|
TXT_TO_INT("height", transfer_data(i_height), height); |
|
|
|
i_blockhash = require_name(trf_root, "blockhash", 1, NULL, reply, siz); |
|
if (!i_blockhash) |
|
return strdup(reply); |
|
|
|
i_action = require_name(trf_root, "action", 1, NULL, reply, siz); |
|
if (!i_action) |
|
return strdup(reply); |
|
|
|
action = transfer_data(i_action); |
|
|
|
K_RLOCK(blocks_free); |
|
b_item = find_blocks(height, transfer_data(i_blockhash), NULL); |
|
K_RUNLOCK(blocks_free); |
|
|
|
if (!b_item) { |
|
snprintf(reply, siz, "ERR.unknown block"); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
DATA_BLOCKS(blocks, b_item); |
|
|
|
// Default to previous value |
|
info = blocks->info; |
|
|
|
if (strcasecmp(action, "orphan") == 0) { |
|
switch (blocks->confirmed[0]) { |
|
case BLOCKS_NEW: |
|
case BLOCKS_CONFIRM: |
|
ok = blocks_add(conn, height, |
|
blocks->blockhash, |
|
BLOCKS_ORPHAN_STR, info, |
|
EMPTY, EMPTY, EMPTY, EMPTY, |
|
EMPTY, EMPTY, EMPTY, EMPTY, |
|
by, code, inet, now, false, id, |
|
trf_root); |
|
if (!ok) { |
|
snprintf(reply, siz, |
|
"DBE.action '%s'", |
|
action); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
// TODO: reset the share counter? |
|
break; |
|
default: |
|
snprintf(reply, siz, |
|
"ERR.invalid action '%.*s%s' for block state '%s'", |
|
CMD_SIZ, action, |
|
(strlen(action) > CMD_SIZ) ? "..." : "", |
|
blocks_confirmed(blocks->confirmed)); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
} else if (strcasecmp(action, "reject") == 0) { |
|
i_info = require_name(trf_root, "info", 0, (char *)strpatt, |
|
reply, siz); |
|
if (!i_info) |
|
return strdup(reply); |
|
tmp = transfer_data(i_info); |
|
/* Override if not empty |
|
* Thus you can't blank it out if current has a value */ |
|
if (tmp && *tmp) |
|
info = tmp; |
|
|
|
switch (blocks->confirmed[0]) { |
|
case BLOCKS_NEW: |
|
case BLOCKS_CONFIRM: |
|
case BLOCKS_ORPHAN: |
|
case BLOCKS_REJECT: |
|
ok = blocks_add(conn, height, |
|
blocks->blockhash, |
|
BLOCKS_REJECT_STR, info, |
|
EMPTY, EMPTY, EMPTY, EMPTY, |
|
EMPTY, EMPTY, EMPTY, EMPTY, |
|
by, code, inet, now, false, id, |
|
trf_root); |
|
if (!ok) { |
|
snprintf(reply, siz, |
|
"DBE.action '%s'", |
|
action); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
// TODO: reset the share counter? |
|
break; |
|
default: |
|
snprintf(reply, siz, |
|
"ERR.invalid action '%.*s%s' for block state '%s'", |
|
CMD_SIZ, action, |
|
(strlen(action) > CMD_SIZ) ? "..." : "", |
|
blocks_confirmed(blocks->confirmed)); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
} else if (strcasecmp(action, "confirm") == 0) { |
|
// Confirm a new block that wasn't confirmed due to some bug |
|
switch (blocks->confirmed[0]) { |
|
case BLOCKS_NEW: |
|
ok = blocks_add(conn, height, |
|
blocks->blockhash, |
|
BLOCKS_CONFIRM_STR, info, |
|
EMPTY, EMPTY, EMPTY, EMPTY, |
|
EMPTY, EMPTY, EMPTY, EMPTY, |
|
by, code, inet, now, false, id, |
|
trf_root); |
|
if (!ok) { |
|
snprintf(reply, siz, |
|
"DBE.action '%s'", |
|
action); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
// TODO: reset the share counter? |
|
break; |
|
default: |
|
snprintf(reply, siz, |
|
"ERR.invalid action '%.*s%s' for block state '%s'", |
|
CMD_SIZ, action, |
|
(strlen(action) > CMD_SIZ) ? "..." : "", |
|
blocks_confirmed(blocks->confirmed)); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
} else { |
|
snprintf(reply, siz, "ERR.unknown action '%s'", |
|
transfer_data(i_action)); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
snprintf(reply, siz, "ok.%s %d", transfer_data(i_action), height); |
|
LOGDEBUG("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
static char *cmd_newid(PGconn *conn, char *cmd, char *id, tv_t *now, char *by, |
|
char *code, char *inet, __maybe_unused tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
K_ITEM *i_idname, *i_idvalue; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_idname = require_name(trf_root, "idname", 3, (char *)idpatt, reply, siz); |
|
if (!i_idname) |
|
return strdup(reply); |
|
|
|
i_idvalue = require_name(trf_root, "idvalue", 1, (char *)intpatt, reply, siz); |
|
if (!i_idvalue) |
|
return strdup(reply); |
|
|
|
ok = idcontrol_add(conn, transfer_data(i_idname), |
|
transfer_data(i_idvalue), |
|
by, code, inet, now, trf_root); |
|
|
|
if (!ok) { |
|
LOGERR("%s() %s.failed.DBE", __func__, id); |
|
return strdup("failed.DBE"); |
|
} |
|
snprintf(reply, siz, "ok.added %s %s", |
|
transfer_data(i_idname), |
|
transfer_data(i_idvalue)); |
|
LOGDEBUG("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
static char *cmd_payments(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, |
|
__maybe_unused K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_username, *u_item, *p_item, *p2_item, *po_item; |
|
K_TREE_CTX ctx[1]; |
|
K_STORE *pay_store; |
|
PAYMENTS *payments, *last_payments = NULL; |
|
PAYOUTS *payouts; |
|
USERS *users; |
|
char reply[1024] = ""; |
|
char tmp[1024]; |
|
size_t siz = sizeof(reply); |
|
char *buf; |
|
size_t len, off; |
|
int rows; |
|
bool pok; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = adminuser(trf_root, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) |
|
return strdup("bad"); |
|
DATA_USERS(users, u_item); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
rows = 0; |
|
pay_store = k_new_store(payments_free); |
|
K_WLOCK(payments_free); |
|
p_item = find_first_payments(users->userid, ctx); |
|
DATA_PAYMENTS_NULL(payments, p_item); |
|
/* TODO: allow to see details of a single payoutid |
|
* if it has multiple items (percent payout user) */ |
|
while (p_item && payments->userid == users->userid) { |
|
if (CURRENT(&(payments->expirydate))) { |
|
if (!last_payments || payments->payoutid != last_payments->payoutid) { |
|
p2_item = k_unlink_head(payments_free); |
|
DATA_PAYMENTS_NULL(last_payments, p2_item); |
|
memcpy(last_payments, payments, sizeof(*last_payments)); |
|
k_add_tail(pay_store, p2_item); |
|
} else { |
|
STRNCPY(last_payments->payaddress, "*Multiple"); |
|
last_payments->amount += payments->amount; |
|
} |
|
} |
|
p_item = next_in_ktree(ctx); |
|
DATA_PAYMENTS_NULL(payments, p_item); |
|
} |
|
K_WUNLOCK(payments_free); |
|
|
|
p_item = STORE_HEAD_NOLOCK(pay_store); |
|
while (p_item) { |
|
DATA_PAYMENTS(payments, p_item); |
|
pok = false; |
|
K_RLOCK(payouts_free); |
|
po_item = find_payoutid(payments->payoutid); |
|
DATA_PAYOUTS_NULL(payouts, po_item); |
|
if (p_item && PAYGENERATED(payouts->status)) |
|
pok = true; |
|
K_RUNLOCK(payouts_free); |
|
if (pok) { |
|
bigint_to_buf(payouts->payoutid, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "payoutid:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
int_to_buf(payouts->height, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "height:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
str_to_buf(payments->payaddress, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "payaddress:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
bigint_to_buf(payments->amount, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "amount:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
tv_to_buf(&(payments->paydate), reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "paydate:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
rows++; |
|
} |
|
p_item = p_item->next; |
|
} |
|
|
|
snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", |
|
rows, FLDSEP, |
|
"payoutid,height,payaddress,amount,paydate", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Payments", FLDSEP, ""); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); |
|
return buf; |
|
} |
|
|
|
static char *cmd_percent(char *cmd, char *id, tv_t *now, USERS *users) |
|
{ |
|
K_ITEM w_look, *w_item, us_look, *us_item, *ws_item; |
|
K_TREE_CTX w_ctx[1], pay_ctx[1]; |
|
WORKERS lookworkers, *workers; |
|
WORKERSTATUS *workerstatus; |
|
USERSTATS *userstats; |
|
char tmp[1024]; |
|
char *buf; |
|
size_t len, off; |
|
int rows; |
|
|
|
double t_hashrate5m = 0, t_hashrate1hr = 0; |
|
double t_hashrate24hr = 0; |
|
double t_diffacc = 0, t_diffinv = 0; |
|
double t_diffsta = 0, t_diffdup = 0; |
|
double t_diffhi = 0, t_diffrej = 0; |
|
double t_shareacc = 0, t_shareinv = 0; |
|
double t_sharesta = 0, t_sharedup = 0; |
|
double t_sharehi = 0, t_sharerej = 0; |
|
|
|
K_ITEM *pa_item; |
|
PAYMENTADDRESSES *pa; |
|
int64_t paytotal; |
|
double ratio; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
snprintf(tmp, sizeof(tmp), "blockacc=%.1f%c", |
|
pool.diffacc, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "blockreward=%"PRId64"%c", |
|
pool.reward, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
INIT_WORKERS(&w_look); |
|
INIT_USERSTATS(&us_look); |
|
|
|
// Add up all user's worker stats to be divided into payout percentages |
|
lookworkers.userid = users->userid; |
|
lookworkers.workername[0] = '\0'; |
|
DATE_ZERO(&(lookworkers.expirydate)); |
|
w_look.data = (void *)(&lookworkers); |
|
K_RLOCK(workers_free); |
|
w_item = find_after_in_ktree(workers_root, &w_look, w_ctx); |
|
K_RUNLOCK(workers_free); |
|
DATA_WORKERS_NULL(workers, w_item); |
|
while (w_item && workers->userid == users->userid) { |
|
if (CURRENT(&(workers->expirydate))) { |
|
K_RLOCK(workerstatus_free); |
|
ws_item = find_workerstatus(true, users->userid, |
|
workers->workername); |
|
if (ws_item) { |
|
DATA_WORKERSTATUS(workerstatus, ws_item); |
|
t_diffacc += workerstatus->block_diffacc; |
|
t_diffinv += workerstatus->block_diffinv; |
|
t_diffsta += workerstatus->block_diffsta; |
|
t_diffdup += workerstatus->block_diffdup; |
|
t_diffhi += workerstatus->block_diffhi; |
|
t_diffrej += workerstatus->block_diffrej; |
|
t_shareacc += workerstatus->block_shareacc; |
|
t_shareinv += workerstatus->block_shareinv; |
|
t_sharesta += workerstatus->block_sharesta; |
|
t_sharedup += workerstatus->block_sharedup; |
|
t_sharehi += workerstatus->block_sharehi; |
|
t_sharerej += workerstatus->block_sharerej; |
|
} |
|
K_RUNLOCK(workerstatus_free); |
|
|
|
/* TODO: workers_root userid+worker is ordered |
|
* so no 'find' should be needed - |
|
* just cmp to last 'unused us_item' userid+worker |
|
* then step it forward to be the next ready 'unused' */ |
|
K_RLOCK(userstats_free); |
|
us_item = find_userstats(users->userid, workers->workername); |
|
if (us_item) { |
|
DATA_USERSTATS(userstats, us_item); |
|
if (tvdiff(now, &(userstats->statsdate)) < USERSTATS_PER_S) { |
|
t_hashrate5m += userstats->hashrate5m; |
|
t_hashrate1hr += userstats->hashrate1hr; |
|
t_hashrate24hr += userstats->hashrate24hr; |
|
} |
|
} |
|
K_RUNLOCK(userstats_free); |
|
} |
|
K_RLOCK(workers_free); |
|
w_item = next_in_ktree(w_ctx); |
|
K_RUNLOCK(workers_free); |
|
DATA_WORKERS_NULL(workers, w_item); |
|
} |
|
|
|
// Calculate total payratio |
|
paytotal = 0; |
|
K_RLOCK(paymentaddresses_free); |
|
pa_item = find_paymentaddresses(users->userid, pay_ctx); |
|
DATA_PAYMENTADDRESSES(pa, pa_item); |
|
while (pa_item && CURRENT(&(pa->expirydate)) && |
|
pa->userid == users->userid) { |
|
paytotal += pa->payratio; |
|
pa_item = prev_in_ktree(pay_ctx); |
|
DATA_PAYMENTADDRESSES_NULL(pa, pa_item); |
|
} |
|
if (paytotal == 0) |
|
paytotal = 1; |
|
|
|
// Divide totals into payout percentages |
|
rows = 0; |
|
pa_item = find_paymentaddresses(users->userid, pay_ctx); |
|
DATA_PAYMENTADDRESSES_NULL(pa, pa_item); |
|
while (pa_item && CURRENT(&(pa->expirydate)) && |
|
pa->userid == users->userid) { |
|
ratio = (double)(pa->payratio) / (double)paytotal; |
|
|
|
snprintf(tmp, sizeof(tmp), "payaddress:%d=%s%c", |
|
rows, pa->payaddress, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "payratio:%d=%"PRId32"%c", |
|
rows, pa->payratio, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "paypercent:%d=%.6f%c", |
|
rows, ratio * 100.0, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "payname:%d=%s%c", |
|
rows, pa->payname, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "p_hashrate5m:%d=%.1f%c", rows, |
|
(double)t_hashrate5m * ratio, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "p_hashrate1hr:%d=%.1f%c", rows, |
|
(double)t_hashrate1hr * ratio, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "p_hashrate24hr:%d=%.1f%c", rows, |
|
(double)t_hashrate24hr * ratio, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "p_diffacc:%d=%.1f%c", rows, |
|
(double)t_diffacc * ratio, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "p_diffinv:%d=%.1f%c", rows, |
|
(double)t_diffinv * ratio, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "p_diffsta:%d=%.1f%c", rows, |
|
(double)t_diffsta * ratio, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "p_diffdup:%d=%.1f%c", rows, |
|
(double)t_diffdup * ratio, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "p_diffhi:%d=%.1f%c", rows, |
|
(double)t_diffhi * ratio, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "p_diffrej:%d=%.1f%c", rows, |
|
(double)t_diffrej * ratio, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "p_shareacc:%d=%.1f%c", rows, |
|
(double)t_shareacc * ratio, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "p_shareinv:%d=%.1f%c", rows, |
|
(double)t_shareinv * ratio, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "p_sharesta:%d=%.1f%c", rows, |
|
(double)t_sharesta * ratio, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "p_sharedup:%d=%.1f%c", rows, |
|
(double)t_sharedup * ratio, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "p_sharehi:%d=%.1f%c", rows, |
|
(double)t_sharehi * ratio, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "p_sharerej:%d=%.1f%c", rows, |
|
(double)t_sharerej * ratio, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
rows++; |
|
|
|
pa_item = prev_in_ktree(pay_ctx); |
|
DATA_PAYMENTADDRESSES_NULL(pa, pa_item); |
|
} |
|
K_RUNLOCK(paymentaddresses_free); |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"rows=%d%cflds=%s%c", |
|
rows, FLDSEP, |
|
"payaddress,payratio,paypercent,payname," |
|
"p_hashrate5m,p_hashrate1hr,p_hashrate24hr," |
|
"p_diffacc,p_diffinv," |
|
"p_diffsta,p_diffdup,p_diffhi,p_diffrej," |
|
"p_shareacc,p_shareinv," |
|
"p_sharesta,p_sharedup,p_sharehi,p_sharerej", |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Percents", FLDSEP, ""); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
LOGDEBUG("%s.ok.%s", id, users->username); |
|
return buf; |
|
} |
|
|
|
static char *cmd_workers(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_username, *i_stats, *i_percent, w_look, *u_item, *w_item; |
|
K_ITEM *ua_item, *us_item, *ws_item; |
|
K_TREE_CTX w_ctx[1]; |
|
WORKERS lookworkers, *workers; |
|
WORKERSTATUS *workerstatus; |
|
USERSTATS *userstats; |
|
USERATTS *useratts; |
|
USERS *users; |
|
char reply[1024] = ""; |
|
char tmp[1024]; |
|
int64_t oldworkers = USER_OLD_WORKERS_DEFAULT; |
|
size_t siz = sizeof(reply); |
|
tv_t last_share; |
|
char *buf; |
|
size_t len, off; |
|
bool stats, percent; |
|
int rows; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = adminuser(trf_root, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) |
|
return strdup("bad"); |
|
DATA_USERS(users, u_item); |
|
|
|
i_stats = optional_name(trf_root, "stats", 1, NULL, reply, siz); |
|
if (!i_stats) |
|
stats = false; |
|
else |
|
stats = (strcasecmp(transfer_data(i_stats), TRUE_STR) == 0); |
|
|
|
percent = false; |
|
K_RLOCK(useratts_free); |
|
ua_item = find_useratts(users->userid, USER_MULTI_PAYOUT); |
|
K_RUNLOCK(useratts_free); |
|
if (ua_item) { |
|
i_percent = optional_name(trf_root, "percent", 1, NULL, reply, siz); |
|
if (i_percent) |
|
percent = (strcasecmp(transfer_data(i_stats), TRUE_STR) == 0); |
|
} |
|
|
|
if (percent) |
|
return cmd_percent(cmd, id, now, users); |
|
|
|
K_RLOCK(useratts_free); |
|
ua_item = find_useratts(users->userid, USER_OLD_WORKERS); |
|
K_RUNLOCK(useratts_free); |
|
if (ua_item) { |
|
DATA_USERATTS(useratts, ua_item); |
|
oldworkers = useratts->attnum; |
|
} |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
snprintf(tmp, sizeof(tmp), "blockacc=%.1f%c", |
|
pool.diffacc, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "blockreward=%"PRId64"%c", |
|
pool.reward, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "oldworkers=%"PRId64"%c", |
|
oldworkers, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
if (oldworkers > 0) |
|
oldworkers *= 24L * 60L * 60L; |
|
else |
|
oldworkers = now->tv_sec + 1; |
|
|
|
INIT_WORKERS(&w_look); |
|
|
|
lookworkers.userid = users->userid; |
|
lookworkers.workername[0] = '\0'; |
|
DATE_ZERO(&(lookworkers.expirydate)); |
|
w_look.data = (void *)(&lookworkers); |
|
K_RLOCK(workers_free); |
|
w_item = find_after_in_ktree(workers_root, &w_look, w_ctx); |
|
K_RUNLOCK(workers_free); |
|
DATA_WORKERS_NULL(workers, w_item); |
|
rows = 0; |
|
while (w_item && workers->userid == users->userid) { |
|
if (CURRENT(&(workers->expirydate))) { |
|
K_RLOCK(workerstatus_free); |
|
ws_item = find_workerstatus(true, users->userid, |
|
workers->workername); |
|
if (ws_item) { |
|
DATA_WORKERSTATUS(workerstatus, ws_item); |
|
// good or bad - either means active |
|
copy_tv(&last_share, &(workerstatus->last_share)); |
|
} else |
|
DATE_ZERO(&last_share); |
|
K_RUNLOCK(workerstatus_free); |
|
|
|
if (tvdiff(now, &last_share) < oldworkers) { |
|
str_to_buf(workers->workername, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "workername:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
int_to_buf(workers->difficultydefault, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "difficultydefault:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
str_to_buf(workers->idlenotificationenabled, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "idlenotificationenabled:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
int_to_buf(workers->idlenotificationtime, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "idlenotificationtime:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
if (stats) { |
|
double w_hashrate5m, w_hashrate1hr; |
|
double w_hashrate24hr; |
|
int64_t w_elapsed; |
|
tv_t w_lastshare; |
|
tv_t w_lastshareacc; |
|
double w_lastdiffacc, w_diffacc; |
|
double w_diffinv; |
|
double w_diffsta, w_diffdup; |
|
double w_diffhi, w_diffrej; |
|
double w_shareacc, w_shareinv; |
|
double w_sharesta, w_sharedup; |
|
double w_sharehi, w_sharerej; |
|
double w_active_diffacc; |
|
tv_t w_active_start; |
|
int w_instances; |
|
|
|
w_hashrate5m = w_hashrate1hr = |
|
w_hashrate24hr = 0.0; |
|
w_elapsed = -1; |
|
w_instances = NO_INSTANCE_DATA; |
|
|
|
if (!ws_item) { |
|
w_lastshare.tv_sec = |
|
w_lastshareacc.tv_sec = 0L; |
|
w_lastdiffacc = w_diffacc = |
|
w_diffinv = w_diffsta = |
|
w_diffdup = w_diffhi = |
|
w_diffrej = w_shareacc = |
|
w_shareinv = w_sharesta = |
|
w_sharedup = w_sharehi = |
|
w_sharerej = w_active_diffacc = 0; |
|
w_active_start.tv_sec = 0L; |
|
} else { |
|
DATA_WORKERSTATUS(workerstatus, ws_item); |
|
// It's bad to read possibly changing data |
|
K_RLOCK(workerstatus_free); |
|
w_lastshare.tv_sec = workerstatus->last_share.tv_sec; |
|
w_lastshareacc.tv_sec = workerstatus->last_share_acc.tv_sec; |
|
w_lastdiffacc = workerstatus->last_diff_acc; |
|
w_diffacc = workerstatus->block_diffacc; |
|
w_diffinv = workerstatus->block_diffinv; |
|
w_diffsta = workerstatus->block_diffsta; |
|
w_diffdup = workerstatus->block_diffdup; |
|
w_diffhi = workerstatus->block_diffhi; |
|
w_diffrej = workerstatus->block_diffrej; |
|
w_shareacc = workerstatus->block_shareacc; |
|
w_shareinv = workerstatus->block_shareinv; |
|
w_sharesta = workerstatus->block_sharesta; |
|
w_sharedup = workerstatus->block_sharedup; |
|
w_sharehi = workerstatus->block_sharehi; |
|
w_sharerej = workerstatus->block_sharerej; |
|
w_active_diffacc = workerstatus->active_diffacc; |
|
w_active_start.tv_sec = workerstatus->active_start.tv_sec; |
|
K_RUNLOCK(workerstatus_free); |
|
} |
|
|
|
/* TODO: workers_root userid+worker is ordered |
|
* so no 'find' should be needed - |
|
* just cmp to last 'unused us_item' userid+worker |
|
* then step it forward to be the next ready 'unused' */ |
|
K_RLOCK(userstats_free); |
|
us_item = find_userstats(users->userid, workers->workername); |
|
if (us_item) { |
|
DATA_USERSTATS(userstats, us_item); |
|
if (tvdiff(now, &(userstats->statsdate)) < USERSTATS_PER_S) { |
|
w_hashrate5m += userstats->hashrate5m; |
|
w_hashrate1hr += userstats->hashrate1hr; |
|
w_hashrate24hr += userstats->hashrate24hr; |
|
if (w_elapsed == -1 || w_elapsed > userstats->elapsed) |
|
w_elapsed = userstats->elapsed; |
|
if (userstats->instances != NO_INSTANCE_DATA) { |
|
if (w_instances == NO_INSTANCE_DATA) |
|
w_instances = 0; |
|
w_instances += userstats->instances; |
|
} |
|
} |
|
} |
|
K_RUNLOCK(userstats_free); |
|
|
|
double_to_buf(w_hashrate5m, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_hashrate5m:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_hashrate1hr, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_hashrate1hr:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_hashrate24hr, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_hashrate24hr:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
bigint_to_buf(w_elapsed, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_elapsed:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
int_to_buf((int)(w_lastshare.tv_sec), reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_lastshare:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
int_to_buf((int)(w_lastshareacc.tv_sec), reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_lastshareacc:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_lastdiffacc, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_lastdiff:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_diffacc, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_diffacc:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_diffinv, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_diffinv:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_diffsta, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_diffsta:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_diffdup, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_diffdup:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_diffhi, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_diffhi:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_diffrej, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_diffrej:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_shareacc, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_shareacc:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_shareinv, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_shareinv:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_sharesta, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_sharesta:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_sharedup, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_sharedup:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_sharehi, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_sharehi:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_sharerej, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_sharerej:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(w_active_diffacc, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_active_diffacc:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
int_to_buf((int)(w_active_start.tv_sec), reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_active_start:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
int_to_buf(w_instances, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "w_instances:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
rows++; |
|
} |
|
} |
|
K_RLOCK(workers_free); |
|
w_item = next_in_ktree(w_ctx); |
|
K_RUNLOCK(workers_free); |
|
DATA_WORKERS_NULL(workers, w_item); |
|
} |
|
snprintf(tmp, sizeof(tmp), |
|
"rows=%d%cflds=%s%s%c", |
|
rows, FLDSEP, |
|
"workername,difficultydefault,idlenotificationenabled," |
|
"idlenotificationtime", |
|
stats ? ",w_hashrate5m,w_hashrate1hr,w_hashrate24hr," |
|
"w_elapsed,w_lastshare,w_lastshareacc," |
|
"w_lastdiff,w_diffacc,w_diffinv," |
|
"w_diffsta,w_diffdup,w_diffhi,w_diffrej," |
|
"w_shareacc,w_shareinv," |
|
"w_sharesta,w_sharedup,w_sharehi,w_sharerej," |
|
"w_active_diffacc,w_active_start,w_instances" : "", |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Workers", FLDSEP, ""); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); |
|
return buf; |
|
} |
|
|
|
static char *cmd_allusers(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, |
|
__maybe_unused K_TREE *trf_root) |
|
{ |
|
K_STORE *usu_store = k_new_store(userstats_free); |
|
K_ITEM *us_item, *usu_item, *u_item; |
|
K_TREE_CTX us_ctx[1]; |
|
USERSTATS *userstats, *userstats_u = NULL; |
|
USERS *users; |
|
char reply[1024] = ""; |
|
char tmp[1024]; |
|
char *buf; |
|
size_t len, off; |
|
int rows; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
/* Sum up all recent userstats without workername |
|
* i.e. userstasts per username */ |
|
K_WLOCK(userstats_free); |
|
us_item = first_in_ktree(userstats_root, us_ctx); |
|
while (us_item) { |
|
DATA_USERSTATS(userstats, us_item); |
|
if (tvdiff(now, &(userstats->statsdate)) < ALLUSERS_LIMIT_S) { |
|
if (!userstats_u || userstats->userid != userstats_u->userid) { |
|
usu_item = k_unlink_head(userstats_free); |
|
DATA_USERSTATS(userstats_u, usu_item); |
|
|
|
userstats_u->userid = userstats->userid; |
|
/* Remember the first workername for if we ever |
|
* get the missing user LOGERR message below */ |
|
STRNCPY(userstats_u->workername, userstats->workername); |
|
userstats_u->hashrate5m = userstats->hashrate5m; |
|
userstats_u->hashrate1hr = userstats->hashrate1hr; |
|
userstats_u->instances = userstats->instances; |
|
|
|
k_add_head(usu_store, usu_item); |
|
} else { |
|
userstats_u->hashrate5m += userstats->hashrate5m; |
|
userstats_u->hashrate1hr += userstats->hashrate1hr; |
|
if (userstats->instances != NO_INSTANCE_DATA) { |
|
if ( userstats_u->instances == NO_INSTANCE_DATA) |
|
userstats_u->instances = 0; |
|
userstats_u->instances += userstats->instances; |
|
} |
|
} |
|
} |
|
us_item = next_in_ktree(us_ctx); |
|
} |
|
K_WUNLOCK(userstats_free); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
rows = 0; |
|
usu_item = STORE_HEAD_NOLOCK(usu_store); |
|
while (usu_item) { |
|
DATA_USERSTATS(userstats_u, usu_item); |
|
K_RLOCK(users_free); |
|
u_item = find_userid(userstats_u->userid); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
LOGERR("%s() userstats, but not users, " |
|
"ignored %"PRId64"/%s", |
|
__func__, userstats_u->userid, |
|
userstats_u->workername); |
|
} else { |
|
DATA_USERS(users, u_item); |
|
str_to_buf(users->username, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "username:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
bigint_to_buf(users->userid, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "userid:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(userstats_u->hashrate5m, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "u_hashrate5m:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(userstats_u->hashrate1hr, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "u_hashrate1hr:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
int_to_buf(userstats_u->instances, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "u_instances:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
rows++; |
|
} |
|
usu_item = usu_item->next; |
|
} |
|
|
|
K_WLOCK(userstats_free); |
|
k_list_transfer_to_head(usu_store, userstats_free); |
|
K_WUNLOCK(userstats_free); |
|
k_free_store(usu_store); |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"rows=%d%cflds=%s%c", |
|
rows, FLDSEP, |
|
"username,userid,u_hashrate5m,u_hashrate1hr,u_instances", |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Users", FLDSEP, ""); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
LOGDEBUG("%s.ok.allusers", id); |
|
return buf; |
|
} |
|
|
|
static char *cmd_sharelog(PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *notnow, char *by, |
|
char *code, char *inet, tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
int64_t workinfoid; |
|
|
|
// log to logfile with processing success/failure code |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
if (strcasecmp(cmd, STR_WORKINFO) == 0) { |
|
K_ITEM *i_workinfoid, *i_poolinstance, *i_transactiontree, *i_merklehash; |
|
K_ITEM *i_prevhash, *i_coinbase1, *i_coinbase2, *i_version, *i_bits; |
|
K_ITEM *i_ntime, *i_reward; |
|
bool igndup = false; |
|
|
|
i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz); |
|
if (!i_poolinstance) |
|
return strdup(reply); |
|
|
|
if (poolinstance && strcmp(poolinstance, transfer_data(i_poolinstance))){ |
|
POOLINSTANCE_DATA_SET(workinfo, transfer_data(i_poolinstance)); |
|
return strdup(FAILED_PI); |
|
} |
|
|
|
i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz); |
|
if (!i_workinfoid) |
|
return strdup(reply); |
|
|
|
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); |
|
|
|
if (reloading && !confirm_sharesummary) { |
|
if (workinfoid <= dbstatus.newest_workinfoid) |
|
igndup = true; |
|
} |
|
|
|
if (confirm_sharesummary) { |
|
if (workinfoid < confirm_first_workinfoid || |
|
workinfoid > confirm_last_workinfoid) |
|
goto wiconf; |
|
} |
|
|
|
i_transactiontree = require_name(trf_root, "transactiontree", 0, NULL, reply, siz); |
|
if (!i_transactiontree) |
|
return strdup(reply); |
|
|
|
i_merklehash = require_name(trf_root, "merklehash", 0, NULL, reply, siz); |
|
if (!i_merklehash) |
|
return strdup(reply); |
|
|
|
i_prevhash = require_name(trf_root, "prevhash", 1, NULL, reply, siz); |
|
if (!i_prevhash) |
|
return strdup(reply); |
|
|
|
i_coinbase1 = require_name(trf_root, "coinbase1", 1, NULL, reply, siz); |
|
if (!i_coinbase1) |
|
return strdup(reply); |
|
|
|
i_coinbase2 = require_name(trf_root, "coinbase2", 1, NULL, reply, siz); |
|
if (!i_coinbase2) |
|
return strdup(reply); |
|
|
|
i_version = require_name(trf_root, "version", 1, NULL, reply, siz); |
|
if (!i_version) |
|
return strdup(reply); |
|
|
|
i_bits = require_name(trf_root, "bits", 1, NULL, reply, siz); |
|
if (!i_bits) |
|
return strdup(reply); |
|
|
|
i_ntime = require_name(trf_root, "ntime", 1, NULL, reply, siz); |
|
if (!i_ntime) |
|
return strdup(reply); |
|
|
|
i_reward = require_name(trf_root, "reward", 1, NULL, reply, siz); |
|
if (!i_reward) |
|
return strdup(reply); |
|
|
|
workinfoid = workinfo_add(conn, transfer_data(i_workinfoid), |
|
transfer_data(i_poolinstance), |
|
transfer_data(i_transactiontree), |
|
transfer_data(i_merklehash), |
|
transfer_data(i_prevhash), |
|
transfer_data(i_coinbase1), |
|
transfer_data(i_coinbase2), |
|
transfer_data(i_version), |
|
transfer_data(i_bits), |
|
transfer_data(i_ntime), |
|
transfer_data(i_reward), |
|
by, code, inet, cd, igndup, trf_root); |
|
|
|
if (workinfoid == -1) { |
|
LOGERR("%s(%s) %s.failed.DBE", __func__, cmd, id); |
|
return strdup("failed.DBE"); |
|
} else { |
|
// Only flag a successful workinfo |
|
ck_wlock(&last_lock); |
|
setnow(&last_workinfo); |
|
ck_wunlock(&last_lock); |
|
} |
|
LOGDEBUG("%s.ok.added %"PRId64, id, workinfoid); |
|
wiconf: |
|
snprintf(reply, siz, "ok.%"PRId64, workinfoid); |
|
return strdup(reply); |
|
} else if (strcasecmp(cmd, STR_SHARES) == 0) { |
|
K_ITEM *i_workinfoid, *i_username, *i_workername, *i_clientid, *i_errn; |
|
K_ITEM *i_enonce1, *i_nonce2, *i_nonce, *i_diff, *i_sdiff; |
|
K_ITEM *i_secondaryuserid; |
|
bool ok; |
|
|
|
i_nonce = require_name(trf_root, "nonce", 1, NULL, reply, siz); |
|
if (!i_nonce) |
|
return strdup(reply); |
|
|
|
i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz); |
|
if (!i_workinfoid) |
|
return strdup(reply); |
|
|
|
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); |
|
|
|
if (reloading && !confirm_sharesummary) { |
|
/* ISDR (Ignored shares during reload) |
|
* This will discard any shares older than the newest |
|
* workinfoidend of any workmarker - including ready |
|
* but not processed workmarkers |
|
* This means that if a workmarker needs re-processing |
|
* and all of it's shares need to be redone, that will |
|
* require a seperate procedure to the reload |
|
* This would be the (as yet non-existant) |
|
* confirm_markersummary which will replace the |
|
* now unusable confirm_sharesummary code |
|
* However, if the workmarker simply just needs to be |
|
* flagged as processed, this avoids the problem of |
|
* duplicating shares before flagging it |
|
*/ |
|
if (workinfoid <= dbstatus.newest_workmarker_workinfoid) |
|
return NULL; |
|
} |
|
|
|
if (confirm_sharesummary) { |
|
if (workinfoid < confirm_first_workinfoid || |
|
workinfoid > confirm_last_workinfoid) |
|
goto sconf; |
|
} |
|
|
|
i_username = require_name(trf_root, "username", 1, NULL, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz); |
|
if (!i_workername) |
|
return strdup(reply); |
|
|
|
i_clientid = require_name(trf_root, "clientid", 1, NULL, reply, siz); |
|
if (!i_clientid) |
|
return strdup(reply); |
|
|
|
i_errn = require_name(trf_root, "errn", 1, NULL, reply, siz); |
|
if (!i_errn) |
|
return strdup(reply); |
|
|
|
i_enonce1 = require_name(trf_root, "enonce1", 1, NULL, reply, siz); |
|
if (!i_enonce1) |
|
return strdup(reply); |
|
|
|
i_nonce2 = require_name(trf_root, "nonce2", 1, NULL, reply, siz); |
|
if (!i_nonce2) |
|
return strdup(reply); |
|
|
|
i_diff = require_name(trf_root, "diff", 1, NULL, reply, siz); |
|
if (!i_diff) |
|
return strdup(reply); |
|
|
|
i_sdiff = require_name(trf_root, "sdiff", 1, NULL, reply, siz); |
|
if (!i_sdiff) |
|
return strdup(reply); |
|
|
|
i_secondaryuserid = optional_name(trf_root, "secondaryuserid", |
|
1, NULL, reply, siz); |
|
if (!i_secondaryuserid) |
|
i_secondaryuserid = &shares_secondaryuserid; |
|
|
|
ok = shares_add(conn, transfer_data(i_workinfoid), |
|
transfer_data(i_username), |
|
transfer_data(i_workername), |
|
transfer_data(i_clientid), |
|
transfer_data(i_errn), |
|
transfer_data(i_enonce1), |
|
transfer_data(i_nonce2), |
|
transfer_data(i_nonce), |
|
transfer_data(i_diff), |
|
transfer_data(i_sdiff), |
|
transfer_data(i_secondaryuserid), |
|
by, code, inet, cd, trf_root); |
|
|
|
if (!ok) { |
|
LOGERR("%s(%s) %s.failed.DATA", __func__, cmd, id); |
|
return strdup("failed.DATA"); |
|
} else { |
|
// Only flag a successful share |
|
int32_t errn; |
|
TXT_TO_INT("errn", transfer_data(i_errn), errn); |
|
ck_wlock(&last_lock); |
|
setnow(&last_share); |
|
if (errn == SE_NONE) |
|
copy_tv(&last_share_acc, &last_share); |
|
else |
|
copy_tv(&last_share_inv, &last_share); |
|
ck_wunlock(&last_lock); |
|
} |
|
LOGDEBUG("%s.ok.added %s", id, transfer_data(i_nonce)); |
|
sconf: |
|
snprintf(reply, siz, "ok.added %s", transfer_data(i_nonce)); |
|
return strdup(reply); |
|
} else if (strcasecmp(cmd, STR_SHAREERRORS) == 0) { |
|
K_ITEM *i_workinfoid, *i_username, *i_workername, *i_clientid, *i_errn; |
|
K_ITEM *i_error, *i_secondaryuserid; |
|
bool ok; |
|
|
|
i_username = require_name(trf_root, "username", 1, NULL, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz); |
|
if (!i_workinfoid) |
|
return strdup(reply); |
|
|
|
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); |
|
|
|
if (reloading && !confirm_sharesummary) { |
|
// See comment 'ISDR' above for shares |
|
if (workinfoid <= dbstatus.newest_workmarker_workinfoid) |
|
return NULL; |
|
} |
|
if (confirm_sharesummary) { |
|
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); |
|
|
|
if (workinfoid < confirm_first_workinfoid || |
|
workinfoid > confirm_last_workinfoid) |
|
goto seconf; |
|
} |
|
|
|
i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz); |
|
if (!i_workername) |
|
return strdup(reply); |
|
|
|
i_clientid = require_name(trf_root, "clientid", 1, NULL, reply, siz); |
|
if (!i_clientid) |
|
return strdup(reply); |
|
|
|
i_errn = require_name(trf_root, "errn", 1, NULL, reply, siz); |
|
if (!i_errn) |
|
return strdup(reply); |
|
|
|
i_error = require_name(trf_root, "error", 1, NULL, reply, siz); |
|
if (!i_error) |
|
return strdup(reply); |
|
|
|
i_secondaryuserid = optional_name(trf_root, "secondaryuserid", |
|
1, NULL, reply, siz); |
|
if (!i_secondaryuserid) |
|
i_secondaryuserid = &shareerrors_secondaryuserid; |
|
|
|
ok = shareerrors_add(conn, transfer_data(i_workinfoid), |
|
transfer_data(i_username), |
|
transfer_data(i_workername), |
|
transfer_data(i_clientid), |
|
transfer_data(i_errn), |
|
transfer_data(i_error), |
|
transfer_data(i_secondaryuserid), |
|
by, code, inet, cd, trf_root); |
|
if (!ok) { |
|
LOGERR("%s(%s) %s.failed.DATA", __func__, cmd, id); |
|
return strdup("failed.DATA"); |
|
} |
|
LOGDEBUG("%s.ok.added %s", id, transfer_data(i_username)); |
|
seconf: |
|
snprintf(reply, siz, "ok.added %s", transfer_data(i_username)); |
|
return strdup(reply); |
|
} else if (strcasecmp(cmd, STR_AGEWORKINFO) == 0) { |
|
K_ITEM *i_workinfoid, *i_poolinstance; |
|
int64_t ss_count, s_count, s_diff; |
|
tv_t ss_first, ss_last; |
|
bool ok; |
|
|
|
i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz); |
|
if (!i_poolinstance) |
|
return strdup(reply); |
|
|
|
if (poolinstance && strcmp(poolinstance, transfer_data(i_poolinstance))) { |
|
POOLINSTANCE_DATA_SET(ageworkinfo, transfer_data(i_poolinstance)); |
|
return strdup(FAILED_PI); |
|
} |
|
|
|
i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz); |
|
if (!i_workinfoid) |
|
return strdup(reply); |
|
|
|
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); |
|
|
|
if (reloading && !confirm_sharesummary) { |
|
// This excludes any already summarised |
|
if (workinfoid <= dbstatus.newest_workmarker_workinfoid) |
|
return NULL; |
|
} |
|
|
|
if (confirm_sharesummary) { |
|
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); |
|
|
|
if (workinfoid < confirm_first_workinfoid || |
|
workinfoid > confirm_last_workinfoid) |
|
goto awconf; |
|
} |
|
|
|
ok = workinfo_age(workinfoid, transfer_data(i_poolinstance), |
|
by, code, inet, cd, &ss_first, &ss_last, |
|
&ss_count, &s_count, &s_diff); |
|
if (!ok) { |
|
LOGERR("%s(%s) %s.failed.DATA", __func__, cmd, id); |
|
return strdup("failed.DATA"); |
|
} else { |
|
/* Don't slow down the reload - do them later */ |
|
if (!reloading) { |
|
// Aging is a queued item thus the reply is ignored |
|
auto_age_older(workinfoid, |
|
transfer_data(i_poolinstance), |
|
by, code, inet, cd); |
|
} |
|
} |
|
LOGDEBUG("%s.ok.aged %"PRId64, id, workinfoid); |
|
awconf: |
|
snprintf(reply, siz, "ok.%"PRId64, workinfoid); |
|
return strdup(reply); |
|
} |
|
|
|
LOGERR("%s.bad.cmd %s", id, cmd); |
|
return strdup("bad.cmd"); |
|
} |
|
|
|
// TODO: the confirm update: identify block changes from workinfo height? |
|
static char *cmd_blocks_do(PGconn *conn, char *cmd, int32_t height, char *id, |
|
char *by, char *code, char *inet, tv_t *cd, |
|
bool igndup, K_TREE *trf_root) |
|
{ |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
K_ITEM *i_blockhash, *i_confirmed, *i_workinfoid, *i_username; |
|
K_ITEM *i_workername, *i_clientid, *i_enonce1, *i_nonce2, *i_nonce, *i_reward; |
|
TRANSFER *transfer; |
|
char *msg; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_blockhash = require_name(trf_root, "blockhash", 1, NULL, reply, siz); |
|
if (!i_blockhash) |
|
return strdup(reply); |
|
|
|
i_confirmed = require_name(trf_root, "confirmed", 1, NULL, reply, siz); |
|
if (!i_confirmed) |
|
return strdup(reply); |
|
|
|
DATA_TRANSFER(transfer, i_confirmed); |
|
transfer->mvalue[0] = tolower(transfer->mvalue[0]); |
|
switch(transfer->mvalue[0]) { |
|
case BLOCKS_NEW: |
|
i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz); |
|
if (!i_workinfoid) |
|
return strdup(reply); |
|
|
|
i_username = require_name(trf_root, "username", 1, NULL, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz); |
|
if (!i_workername) |
|
return strdup(reply); |
|
|
|
i_clientid = require_name(trf_root, "clientid", 1, NULL, reply, siz); |
|
if (!i_clientid) |
|
return strdup(reply); |
|
|
|
i_enonce1 = require_name(trf_root, "enonce1", 1, NULL, reply, siz); |
|
if (!i_enonce1) |
|
return strdup(reply); |
|
|
|
i_nonce2 = require_name(trf_root, "nonce2", 1, NULL, reply, siz); |
|
if (!i_nonce2) |
|
return strdup(reply); |
|
|
|
i_nonce = require_name(trf_root, "nonce", 1, NULL, reply, siz); |
|
if (!i_nonce) |
|
return strdup(reply); |
|
|
|
i_reward = require_name(trf_root, "reward", 1, NULL, reply, siz); |
|
if (!i_reward) |
|
return strdup(reply); |
|
|
|
msg = "added"; |
|
ok = blocks_add(conn, height, |
|
transfer_data(i_blockhash), |
|
transfer_data(i_confirmed), |
|
EMPTY, |
|
transfer_data(i_workinfoid), |
|
transfer_data(i_username), |
|
transfer_data(i_workername), |
|
transfer_data(i_clientid), |
|
transfer_data(i_enonce1), |
|
transfer_data(i_nonce2), |
|
transfer_data(i_nonce), |
|
transfer_data(i_reward), |
|
by, code, inet, cd, igndup, id, |
|
trf_root); |
|
break; |
|
case BLOCKS_CONFIRM: |
|
msg = "confirmed"; |
|
ok = blocks_add(conn, height, |
|
transfer_data(i_blockhash), |
|
transfer_data(i_confirmed), |
|
EMPTY, |
|
EMPTY, EMPTY, EMPTY, EMPTY, |
|
EMPTY, EMPTY, EMPTY, EMPTY, |
|
by, code, inet, cd, igndup, id, |
|
trf_root); |
|
break; |
|
default: |
|
LOGERR("%s(): %s.failed.invalid confirm='%s'", |
|
__func__, id, transfer_data(i_confirmed)); |
|
return strdup("failed.DATA"); |
|
} |
|
|
|
if (!ok) { |
|
/* Ignore during startup, |
|
* another error should have shown if it matters */ |
|
if (startup_complete) |
|
LOGERR("%s() %s.failed.DBE", __func__, id); |
|
return strdup("failed.DBE"); |
|
} |
|
|
|
LOGDEBUG("%s.ok.blocks %s", id, msg); |
|
snprintf(reply, siz, "ok.%s", msg); |
|
return strdup(reply); |
|
} |
|
|
|
static char *cmd_blocks(PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *notnow, char *by, |
|
char *code, char *inet, tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
bool igndup = false; |
|
K_ITEM *i_height; |
|
int32_t height; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_height = require_name(trf_root, "height", 1, NULL, reply, siz); |
|
if (!i_height) |
|
return strdup(reply); |
|
|
|
TXT_TO_INT("height", transfer_data(i_height), height); |
|
|
|
// confirm_summaries() doesn't call this |
|
if (reloading) { |
|
// Since they're blocks, just try them all |
|
if (height <= dbstatus.newest_height_blocks) |
|
igndup = true; |
|
} |
|
|
|
return cmd_blocks_do(conn, cmd, height, id, by, code, inet, cd, igndup, trf_root); |
|
} |
|
|
|
static char *cmd_auth_do(PGconn *conn, char *cmd, char *id, char *by, |
|
char *code, char *inet, tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
K_ITEM tmp_poolinstance_item; |
|
TRANSFER tmp_poolinstance; |
|
K_TREE_CTX ctx[1]; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
int event = EVENT_OK; |
|
K_ITEM *i_poolinstance, *i_username, *i_workername, *i_clientid; |
|
K_ITEM *i_enonce1, *i_useragent, *i_preauth, *u_item, *oc_item, *w_item; |
|
USERS *users = NULL; |
|
char *username; |
|
WORKERS *workers = NULL; |
|
OPTIONCONTROL *optioncontrol; |
|
size_t len, off; |
|
char *buf; |
|
bool ok = true, first; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_poolinstance = optional_name(trf_root, "poolinstance", 1, NULL, |
|
reply, siz); |
|
if (!i_poolinstance) { |
|
if (poolinstance) { |
|
STRNCPY(tmp_poolinstance.name, "poolinstance"); |
|
STRNCPY(tmp_poolinstance.svalue, poolinstance); |
|
tmp_poolinstance.mvalue = tmp_poolinstance.svalue; |
|
tmp_poolinstance_item.name = Transfer; |
|
tmp_poolinstance_item.prev = NULL; |
|
tmp_poolinstance_item.next = NULL; |
|
tmp_poolinstance_item.data = (void *)(&tmp_poolinstance); |
|
i_poolinstance = &tmp_poolinstance_item; |
|
} else |
|
i_poolinstance = &auth_poolinstance; |
|
} else { |
|
if (poolinstance && strcmp(poolinstance, transfer_data(i_poolinstance))) { |
|
POOLINSTANCE_DATA_SET(auth, transfer_data(i_poolinstance)); |
|
return strdup(FAILED_PI); |
|
} |
|
} |
|
|
|
i_username = require_name(trf_root, "username", 1, NULL, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
username = transfer_data(i_username); |
|
|
|
i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz); |
|
if (!i_workername) |
|
return strdup(reply); |
|
|
|
i_clientid = require_name(trf_root, "clientid", 1, NULL, reply, siz); |
|
if (!i_clientid) |
|
return strdup(reply); |
|
|
|
i_enonce1 = require_name(trf_root, "enonce1", 1, NULL, reply, siz); |
|
if (!i_enonce1) |
|
return strdup(reply); |
|
|
|
i_useragent = require_name(trf_root, "useragent", 0, NULL, reply, siz); |
|
if (!i_useragent) |
|
return strdup(reply); |
|
|
|
i_preauth = optional_name(trf_root, "preauth", 1, NULL, reply, siz); |
|
if (!i_preauth) |
|
i_preauth = &auth_preauth; |
|
|
|
oc_item = find_optioncontrol(OPTIONCONTROL_AUTOADDUSER, cd, pool.height); |
|
if (oc_item) { |
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
event = events_add(EVENTID_AUTOACC, trf_root); |
|
if (event == EVENT_OK) { |
|
DATA_OPTIONCONTROL(optioncontrol, oc_item); |
|
u_item = users_add(conn, username, EMPTY, |
|
optioncontrol->optionvalue, |
|
0, by, code, inet, cd, |
|
trf_root); |
|
} else |
|
ok = false; |
|
} |
|
} |
|
|
|
if (ok) { |
|
ok = auths_add(conn, transfer_data(i_poolinstance), |
|
username, |
|
transfer_data(i_workername), |
|
transfer_data(i_clientid), |
|
transfer_data(i_enonce1), |
|
transfer_data(i_useragent), |
|
transfer_data(i_preauth), |
|
by, code, inet, cd, trf_root, false, |
|
&users, &workers, &event); |
|
} |
|
|
|
if (!ok) { |
|
LOGDEBUG("%s() %s.failed.DBE", __func__, id); |
|
return reply_event(event, "failed.DBE"); |
|
} |
|
|
|
// Only flag a successful auth |
|
ck_wlock(&last_lock); |
|
setnow(&last_auth); |
|
ck_wunlock(&last_lock); |
|
|
|
if (switch_state < SWITCH_STATE_AUTHWORKERS) { |
|
snprintf(reply, siz, |
|
"ok.authorise={\"secondaryuserid\":\"%s\"," |
|
"\"difficultydefault\":%d}", |
|
users->secondaryuserid, workers->difficultydefault); |
|
LOGDEBUG("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
snprintf(reply, siz, |
|
"ok.authorise={\"secondaryuserid\":\"%s\"," |
|
"\"workers\":[", |
|
users->secondaryuserid); |
|
APPEND_REALLOC(buf, off, len, reply); |
|
first = true; |
|
K_RLOCK(workers_free); |
|
w_item = first_workers(users->userid, ctx); |
|
DATA_WORKERS_NULL(workers, w_item); |
|
while (w_item && workers->userid == users->userid) { |
|
if (CURRENT(&(workers->expirydate))) { |
|
snprintf(reply, siz, |
|
"%s{\"workername\":\"%s\"," |
|
"\"difficultydefault\":%"PRId32"}", |
|
first ? EMPTY : ",", |
|
workers->workername, |
|
workers->difficultydefault); |
|
APPEND_REALLOC(buf, off, len, reply); |
|
first = false; |
|
} |
|
w_item = next_in_ktree(ctx); |
|
DATA_WORKERS_NULL(workers, w_item); |
|
} |
|
K_RUNLOCK(workers_free); |
|
APPEND_REALLOC(buf, off, len, "]}"); |
|
|
|
LOGDEBUG("%s.%s", id, buf); |
|
return buf; |
|
} |
|
|
|
static char *cmd_auth(PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, char *by, |
|
char *code, char *inet, tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
return cmd_auth_do(conn, cmd, id, by, code, inet, cd, trf_root); |
|
} |
|
|
|
static char *cmd_addrauth_do(PGconn *conn, char *cmd, char *id, char *by, |
|
char *code, char *inet, tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
K_ITEM tmp_poolinstance_item; |
|
TRANSFER tmp_poolinstance; |
|
K_TREE_CTX ctx[1]; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
int event = EVENT_OK; |
|
K_ITEM *i_poolinstance, *i_username, *i_workername, *i_clientid; |
|
K_ITEM *i_enonce1, *i_useragent, *i_preauth, *w_item; |
|
USERS *users = NULL; |
|
WORKERS *workers = NULL; |
|
size_t len, off; |
|
char *buf; |
|
bool ok, first; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_poolinstance = optional_name(trf_root, "poolinstance", 1, NULL, |
|
reply, siz); |
|
if (!i_poolinstance) { |
|
if (poolinstance) { |
|
STRNCPY(tmp_poolinstance.name, "poolinstance"); |
|
STRNCPY(tmp_poolinstance.svalue, poolinstance); |
|
tmp_poolinstance.mvalue = tmp_poolinstance.svalue; |
|
tmp_poolinstance_item.name = Transfer; |
|
tmp_poolinstance_item.prev = NULL; |
|
tmp_poolinstance_item.next = NULL; |
|
tmp_poolinstance_item.data = (void *)(&tmp_poolinstance); |
|
i_poolinstance = &tmp_poolinstance_item; |
|
} else |
|
i_poolinstance = &auth_poolinstance; |
|
} else { |
|
if (poolinstance && strcmp(poolinstance, transfer_data(i_poolinstance))) { |
|
POOLINSTANCE_DATA_SET(addrauth, transfer_data(i_poolinstance)); |
|
return strdup(FAILED_PI); |
|
} |
|
} |
|
|
|
i_username = require_name(trf_root, "username", 1, NULL, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz); |
|
if (!i_workername) |
|
return strdup(reply); |
|
|
|
i_clientid = require_name(trf_root, "clientid", 1, NULL, reply, siz); |
|
if (!i_clientid) |
|
return strdup(reply); |
|
|
|
i_enonce1 = require_name(trf_root, "enonce1", 1, NULL, reply, siz); |
|
if (!i_enonce1) |
|
return strdup(reply); |
|
|
|
i_useragent = require_name(trf_root, "useragent", 0, NULL, reply, siz); |
|
if (!i_useragent) |
|
return strdup(reply); |
|
|
|
i_preauth = require_name(trf_root, "preauth", 1, NULL, reply, siz); |
|
if (!i_preauth) |
|
return strdup(reply); |
|
|
|
ok = auths_add(conn, transfer_data(i_poolinstance), |
|
transfer_data(i_username), |
|
transfer_data(i_workername), |
|
transfer_data(i_clientid), |
|
transfer_data(i_enonce1), |
|
transfer_data(i_useragent), |
|
transfer_data(i_preauth), |
|
by, code, inet, cd, trf_root, true, |
|
&users, &workers, &event); |
|
|
|
if (!ok) { |
|
LOGDEBUG("%s() %s.failed.DBE", __func__, id); |
|
return reply_event(event, "failed.DBE"); |
|
} |
|
|
|
// Only flag a successful auth |
|
ck_wlock(&last_lock); |
|
setnow(&last_auth); |
|
ck_wunlock(&last_lock); |
|
|
|
if (switch_state < SWITCH_STATE_AUTHWORKERS) { |
|
snprintf(reply, siz, |
|
"ok.addrauth={\"secondaryuserid\":\"%s\"," |
|
"\"difficultydefault\":%d}", |
|
users->secondaryuserid, workers->difficultydefault); |
|
LOGDEBUG("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
snprintf(reply, siz, |
|
"ok.addrauth={\"secondaryuserid\":\"%s\"," |
|
"\"workers\":[", |
|
users->secondaryuserid); |
|
APPEND_REALLOC(buf, off, len, reply); |
|
first = true; |
|
K_RLOCK(workers_free); |
|
w_item = first_workers(users->userid, ctx); |
|
DATA_WORKERS_NULL(workers, w_item); |
|
while (w_item && workers->userid == users->userid) { |
|
if (CURRENT(&(workers->expirydate))) { |
|
snprintf(reply, siz, |
|
"%s{\"workername\":\"%s\"," |
|
"\"difficultydefault\":%"PRId32"}", |
|
first ? EMPTY : ",", |
|
workers->workername, |
|
workers->difficultydefault); |
|
APPEND_REALLOC(buf, off, len, reply); |
|
first = false; |
|
} |
|
w_item = next_in_ktree(ctx); |
|
DATA_WORKERS_NULL(workers, w_item); |
|
} |
|
K_RUNLOCK(workers_free); |
|
APPEND_REALLOC(buf, off, len, "]}"); |
|
|
|
LOGDEBUG("%s.%s", id, buf); |
|
return buf; |
|
} |
|
|
|
static char *cmd_addrauth(PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, char *by, |
|
char *code, char *inet, tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
return cmd_addrauth_do(conn, cmd, id, by, code, inet, cd, trf_root); |
|
} |
|
|
|
static char *cmd_heartbeat(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *cd, |
|
__maybe_unused K_TREE *trf_root) |
|
{ |
|
HEARTBEATQUEUE *heartbeatqueue; |
|
K_STORE *hq_store; |
|
K_ITEM *hq_item; |
|
char reply[1024], tmp[1024], *buf; |
|
size_t siz = sizeof(reply); |
|
size_t len, off; |
|
bool first; |
|
|
|
// Wait until startup is complete, we get a heartbeat every second |
|
if (!startup_complete) |
|
goto pulse; |
|
|
|
ck_wlock(&last_lock); |
|
setnow(&last_heartbeat); |
|
ck_wunlock(&last_lock); |
|
|
|
K_WLOCK(heartbeatqueue_free); |
|
if (heartbeatqueue_store->count == 0) { |
|
K_WUNLOCK(heartbeatqueue_free); |
|
goto pulse; |
|
} |
|
|
|
hq_store = k_new_store(heartbeatqueue_free); |
|
k_list_transfer_to_head(heartbeatqueue_store, hq_store); |
|
K_WUNLOCK(heartbeatqueue_free); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok.heartbeat={\"diffchange\":["); |
|
hq_item = STORE_TAIL_NOLOCK(hq_store); |
|
first = true; |
|
while (hq_item) { |
|
DATA_HEARTBEATQUEUE(heartbeatqueue, hq_item); |
|
tvs_to_buf(&last_bc, reply, siz); |
|
snprintf(tmp, sizeof(tmp), |
|
"%s{\"workername\":\"%s\"," |
|
"\"difficultydefault\":%d," |
|
"\""CDTRF"\":\"%ld,%ld\"}", |
|
first ? "" : ",", |
|
heartbeatqueue->workername, |
|
heartbeatqueue->difficultydefault, |
|
heartbeatqueue->createdate.tv_sec, |
|
heartbeatqueue->createdate.tv_usec); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
hq_item = hq_item->prev; |
|
first = false; |
|
} |
|
APPEND_REALLOC(buf, off, len, "]}"); |
|
|
|
K_WLOCK(heartbeatqueue_free); |
|
k_list_transfer_to_head(hq_store, heartbeatqueue_free); |
|
K_WUNLOCK(heartbeatqueue_free); |
|
hq_store = k_free_store(hq_store); |
|
|
|
LOGDEBUG("%s.%s.%s", cmd, id, buf); |
|
return buf; |
|
pulse: |
|
snprintf(reply, siz, "ok.pulse"); |
|
LOGDEBUG("%s.%s.%s", cmd, id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
static char *cmd_homepage(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_username, *u_item, *b_item, *p_item, *us_item, look; |
|
K_ITEM *ua_item, *pa_item; |
|
double u_hashrate5m, u_hashrate1hr; |
|
char reply[1024], tmp[1024], *buf; |
|
size_t siz = sizeof(reply); |
|
USERSTATS lookuserstats, *userstats; |
|
POOLSTATS *poolstats; |
|
BLOCKS *blocks; |
|
USERS *users; |
|
int64_t u_elapsed; |
|
int u_instances; |
|
K_TREE_CTX ctx[1]; |
|
size_t len, off; |
|
bool has_uhr; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = optional_name(trf_root, "username", 1, NULL, reply, siz); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
|
|
// N.B. cmd_homepage isn't called until startup_complete |
|
ftv_to_buf(now, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "now=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
ck_rlock(&last_lock); |
|
ftv_to_buf(&last_heartbeat, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "lasthb=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
ftv_to_buf(&last_workinfo, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "lastwi=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
ftv_to_buf(&last_share, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "lastsh=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
ftv_to_buf(&last_share_acc, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "lastshacc=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
ftv_to_buf(&last_share_inv, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "lastshinv=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
ftv_to_buf(&last_auth, reply, siz); |
|
ck_runlock(&last_lock); |
|
snprintf(tmp, sizeof(tmp), "lastau=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
if (last_bc.tv_sec) { |
|
tvs_to_buf(&last_bc, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "lastbc=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
K_RLOCK(workinfo_free); |
|
if (workinfo_current) { |
|
WORKINFO *wic; |
|
DATA_WORKINFO(wic, workinfo_current); |
|
snprintf(tmp, sizeof(tmp), "lastheight=%d%c", |
|
wic->height-1, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} else { |
|
snprintf(tmp, sizeof(tmp), "lastheight=?%c", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
K_RUNLOCK(workinfo_free); |
|
} else { |
|
snprintf(tmp, sizeof(tmp), "lastbc=?%clastheight=?%c", |
|
FLDSEP, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
|
|
if (current_ndiff) { |
|
snprintf(tmp, sizeof(tmp), "currndiff=%.1f%c", current_ndiff, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} else { |
|
snprintf(tmp, sizeof(tmp), "currndiff=?%c", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
|
|
// TODO: handle orphans |
|
K_RLOCK(blocks_free); |
|
b_item = last_in_ktree(blocks_root, ctx); |
|
K_RUNLOCK(blocks_free); |
|
if (b_item) { |
|
DATA_BLOCKS(blocks, b_item); |
|
tvs_to_buf(&(blocks->createdate), reply, siz); |
|
snprintf(tmp, sizeof(tmp), "lastblock=%s%cconfirmed=%s%c", |
|
reply, FLDSEP, |
|
blocks->confirmed, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "lastblockheight=%d%c", |
|
blocks->height, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} else { |
|
snprintf(tmp, sizeof(tmp), "lastblock=?%cconfirmed=?%c" |
|
"lastblockheight=?%c", |
|
FLDSEP, FLDSEP, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
|
|
|
|
snprintf(tmp, sizeof(tmp), "blockacc=%.1f%c", |
|
pool.diffacc, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "blockerr=%.1f%c", |
|
pool.diffinv, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "blockshareacc=%.1f%c", |
|
pool.shareacc, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "blockshareinv=%.1f%c", |
|
pool.shareinv, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
// TODO: DB only has one poolinstance with -i |
|
K_RLOCK(poolstats_free); |
|
p_item = last_in_ktree(poolstats_root, ctx); |
|
K_RUNLOCK(poolstats_free); |
|
if (p_item) { |
|
DATA_POOLSTATS(poolstats, p_item); |
|
int_to_buf(poolstats->users, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "users=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
int_to_buf(poolstats->workers, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "workers=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(poolstats->hashrate, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "p_hashrate=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(poolstats->hashrate5m, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "p_hashrate5m=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(poolstats->hashrate1hr, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "p_hashrate1hr=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(poolstats->hashrate24hr, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "p_hashrate24hr=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
bigint_to_buf(poolstats->elapsed, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "p_elapsed=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
tvs_to_buf(&(poolstats->createdate), reply, siz); |
|
snprintf(tmp, sizeof(tmp), "p_statsdate=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} else { |
|
snprintf(tmp, sizeof(tmp), "users=?%cworkers=?%cp_hashrate=?%c" |
|
"p_hashrate5m=?%cp_hashrate1hr=?%c" |
|
"p_hashrate24hr=?%cp_elapsed=?%c" |
|
"p_statsdate=?%c", |
|
FLDSEP, FLDSEP, FLDSEP, FLDSEP, |
|
FLDSEP, FLDSEP, FLDSEP, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
|
|
// Don't bother with locking - it's just an FYI web stat |
|
int psync = pool_workqueue_store->count; |
|
int csync = cmd_workqueue_store->count; |
|
int bsync = btc_workqueue_store->count; |
|
snprintf(tmp, sizeof(tmp), "psync=%d%c", psync, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "csync=%d%c", csync, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "bsync=%d%c", bsync, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "sync=%d%c", psync + csync + bsync, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
u_item = NULL; |
|
if (i_username) { |
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
} |
|
|
|
// User info to add to or affect the web site display |
|
if (u_item) { |
|
DATA_USERS(users, u_item); |
|
K_RLOCK(useratts_free); |
|
ua_item = find_useratts(users->userid, USER_MULTI_PAYOUT); |
|
K_RUNLOCK(useratts_free); |
|
if (ua_item) { |
|
snprintf(tmp, sizeof(tmp), |
|
"u_multiaddr=1%c", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
if (!(*(users->emailaddress))) { |
|
snprintf(tmp, sizeof(tmp), |
|
"u_noemail=1%c", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
K_RLOCK(paymentaddresses_free); |
|
pa_item = find_paymentaddresses(users->userid, ctx); |
|
K_RUNLOCK(paymentaddresses_free); |
|
if (!pa_item) { |
|
snprintf(tmp, sizeof(tmp), |
|
"u_nopayaddr=1%c", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
} |
|
|
|
has_uhr = false; |
|
if (p_item && u_item) { |
|
u_hashrate5m = u_hashrate1hr = 0.0; |
|
u_elapsed = -1; |
|
u_instances = NO_INSTANCE_DATA; |
|
/* find last matching userid record - before userid+1 |
|
* Use 'before' in case there is (unexpectedly) a userstats |
|
* with an empty workername */ |
|
lookuserstats.userid = users->userid+1; |
|
STRNCPY(lookuserstats.workername, EMPTY); |
|
INIT_USERSTATS(&look); |
|
look.data = (void *)(&lookuserstats); |
|
K_RLOCK(userstats_free); |
|
us_item = find_before_in_ktree(userstats_root, &look, ctx); |
|
DATA_USERSTATS_NULL(userstats, us_item); |
|
while (us_item && userstats->userid == users->userid) { |
|
if (tvdiff(now, &(userstats->statsdate)) < USERSTATS_PER_S) { |
|
u_hashrate5m += userstats->hashrate5m; |
|
u_hashrate1hr += userstats->hashrate1hr; |
|
if (u_elapsed == -1 || u_elapsed > userstats->elapsed) |
|
u_elapsed = userstats->elapsed; |
|
if (userstats->instances != NO_INSTANCE_DATA) { |
|
if (u_instances == NO_INSTANCE_DATA) |
|
u_instances = 0; |
|
u_instances += userstats->instances; |
|
} |
|
has_uhr = true; |
|
} |
|
us_item = prev_in_ktree(ctx); |
|
DATA_USERSTATS_NULL(userstats, us_item); |
|
} |
|
K_RUNLOCK(userstats_free); |
|
} |
|
|
|
if (has_uhr) { |
|
double_to_buf(u_hashrate5m, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "u_hashrate5m=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(u_hashrate1hr, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "u_hashrate1hr=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
bigint_to_buf(u_elapsed, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "u_elapsed=%s", reply); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
int_to_buf(u_instances, reply, siz); |
|
snprintf(tmp, sizeof(tmp), "u_instances=%s", reply); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} else { |
|
snprintf(tmp, sizeof(tmp), |
|
"u_hashrate5m=?%cu_hashrate1hr=?%cu_elapsed=?%cu_instances=?", |
|
FLDSEP, FLDSEP, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
|
|
LOGDEBUG("%s.ok.home,user=%s", id, |
|
i_username ? transfer_data(i_username): "N"); |
|
return buf; |
|
} |
|
|
|
/* Return the list of useratts for the given username=value |
|
* Format is attlist=attname.element,attname.element,... |
|
* Replies will be attname.element=value |
|
* The 2 date fields, date and date2, have a secondary element name |
|
* dateexp and date2exp |
|
* This will return Y or N depending upon if the date has expired as: |
|
* attname.dateexp=N (or Y) and attname.date2exp=N (or Y) |
|
* Expired means the date is <= now |
|
*/ |
|
static char *cmd_getatts(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_username, *i_attlist, *u_item, *ua_item; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
int event = EVENT_OK; |
|
char tmp[1024]; |
|
USERATTS *useratts; |
|
USERS *users; |
|
char *reason = NULL; |
|
char *answer = NULL; |
|
char *attlist = NULL, *ptr, *comma, *dot; |
|
size_t len, off; |
|
bool first; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", MIN_USERNAME, |
|
(char *)userpatt, reply, siz); |
|
if (!i_username) { |
|
// Shouldn't happen except with a code problem no event required |
|
reason = "Missing username"; |
|
goto nuts; |
|
} |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
|
|
if (!u_item) { |
|
// page_api.php without a valid username |
|
event = events_add(EVENTID_UNKATTS, trf_root); |
|
reason = "Unknown user"; |
|
goto nuts; |
|
} else { |
|
DATA_USERS(users, u_item); |
|
i_attlist = require_name(trf_root, "attlist", 1, NULL, reply, siz); |
|
if (!i_attlist) { |
|
reason = "Missing attlist"; |
|
goto nuts; |
|
} |
|
|
|
APPEND_REALLOC_INIT(answer, off, len); |
|
attlist = ptr = strdup(transfer_data(i_attlist)); |
|
first = true; |
|
while (ptr && *ptr) { |
|
comma = strchr(ptr, ','); |
|
if (comma) |
|
*(comma++) = '\0'; |
|
dot = strchr(ptr, '.'); |
|
if (!dot) { |
|
reason = "Missing element"; |
|
goto nuts; |
|
} |
|
*(dot++) = '\0'; |
|
K_RLOCK(useratts_free); |
|
ua_item = find_useratts(users->userid, ptr); |
|
K_RUNLOCK(useratts_free); |
|
/* web code must check the existance of the attname |
|
* in the reply since it will be missing if it doesn't |
|
* exist in the DB */ |
|
if (ua_item) { |
|
char num_buf[BIGINT_BUFSIZ]; |
|
char ctv_buf[CDATE_BUFSIZ]; |
|
char *ans; |
|
DATA_USERATTS(useratts, ua_item); |
|
if (strcmp(dot, "str") == 0) { |
|
ans = useratts->attstr; |
|
} else if (strcmp(dot, "str2") == 0) { |
|
ans = useratts->attstr2; |
|
} else if (strcmp(dot, "num") == 0) { |
|
bigint_to_buf(useratts->attnum, |
|
num_buf, |
|
sizeof(num_buf)); |
|
ans = num_buf; |
|
} else if (strcmp(dot, "num2") == 0) { |
|
bigint_to_buf(useratts->attnum2, |
|
num_buf, |
|
sizeof(num_buf)); |
|
ans = num_buf; |
|
} else if (strcmp(dot, "date") == 0) { |
|
ctv_to_buf(&(useratts->attdate), |
|
ctv_buf, |
|
sizeof(num_buf)); |
|
ans = ctv_buf; |
|
} else if (strcmp(dot, "dateexp") == 0) { |
|
// Y/N if date is <= now (expired) |
|
if (tv_newer(&(useratts->attdate), now)) |
|
ans = TRUE_STR; |
|
else |
|
ans = FALSE_STR; |
|
} else if (strcmp(dot, "date2") == 0) { |
|
ctv_to_buf(&(useratts->attdate2), |
|
ctv_buf, |
|
sizeof(num_buf)); |
|
ans = ctv_buf; |
|
} else if (strcmp(dot, "date2exp") == 0) { |
|
// Y/N if date2 is <= now (expired) |
|
if (tv_newer(&(useratts->attdate2), now)) |
|
ans = TRUE_STR; |
|
else |
|
ans = FALSE_STR; |
|
} else { |
|
reason = "Unknown element"; |
|
goto nuts; |
|
} |
|
snprintf(tmp, sizeof(tmp), "%s%s.%s=%s", |
|
first ? EMPTY : FLDSEPSTR, |
|
ptr, dot, ans); |
|
APPEND_REALLOC(answer, off, len, tmp); |
|
first = false; |
|
} |
|
ptr = comma; |
|
} |
|
} |
|
nuts: |
|
if (attlist) |
|
free(attlist); |
|
|
|
if (reason) { |
|
if (answer) |
|
free(answer); |
|
snprintf(reply, siz, "ERR.%s", reason); |
|
LOGERR("%s.%s.%s", cmd, id, reply); |
|
return reply_event(event, reply); |
|
} |
|
snprintf(reply, siz, "ok.%s", answer); |
|
LOGDEBUG("%s.%s", id, answer); |
|
free(answer); |
|
return strdup(reply); |
|
} |
|
|
|
static void att_to_date(tv_t *date, char *data, tv_t *now) |
|
{ |
|
int add; |
|
|
|
if (strncasecmp(data, "now+", 4) == 0) { |
|
add = atoi(data+4); |
|
copy_tv(date, now); |
|
date->tv_sec += add; |
|
} else if (strcasecmp(data, "now") == 0) { |
|
copy_tv(date, now); |
|
} else { |
|
txt_to_ctv("date", data, date, sizeof(*date)); |
|
} |
|
} |
|
|
|
/* Store useratts in the DB for the given username=value |
|
* Format is 1 or more: ua_attname.element=value |
|
* i.e. each starts with the constant "ua_" |
|
* attname cannot contain Tab . or = |
|
* element is per the coded list below, which also cannot contain Tab . or = |
|
* Any matching useratts attnames found currently in the DB are expired |
|
* Transfer will sort them so that any of the same attname |
|
* will be next to each other |
|
* thus will combine multiple elements for the same attname |
|
* into one single useratts record (as is mandatory) |
|
* The 2 date fields date and date2 require either epoch values sec,usec |
|
* (usec is optional and defaults to 0) or one of: now or now+NNN |
|
* now is the current epoch value and now+NNN is the epoch + NNN seconds |
|
* See att_to_date() above |
|
* */ |
|
static char *cmd_setatts(PGconn *conn, char *cmd, char *id, |
|
tv_t *now, char *by, char *code, char *inet, |
|
__maybe_unused tv_t *notcd, K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
bool conned = false; |
|
K_ITEM *i_username, *t_item, *u_item, *ua_item = NULL; |
|
K_TREE_CTX ctx[1]; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
TRANSFER *transfer; |
|
USERATTS *useratts = NULL; |
|
USERS *users; |
|
char attname[sizeof(useratts->attname)*2]; |
|
char *reason = NULL; |
|
char *dot, *data; |
|
bool begun = false; |
|
int set = 0, db = 0; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", MIN_USERNAME, |
|
(char *)userpatt, reply, siz); |
|
if (!i_username) { |
|
reason = "Missing user"; |
|
goto bats; |
|
} |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
|
|
if (!u_item) { |
|
reason = "Unknown user"; |
|
goto bats; |
|
} else { |
|
DATA_USERS(users, u_item); |
|
t_item = first_in_ktree_nolock(trf_root, ctx); |
|
while (t_item) { |
|
DATA_TRANSFER(transfer, t_item); |
|
if (strncmp(transfer->name, "ua_", 3) == 0) { |
|
data = transfer_data(t_item); |
|
STRNCPY(attname, transfer->name + 3); |
|
dot = strchr(attname, '.'); |
|
if (!dot) { |
|
reason = "Missing element"; |
|
goto bats; |
|
} |
|
*(dot++) = '\0'; |
|
// If we already had a different one, save it to the DB |
|
if (ua_item && strcmp(useratts->attname, attname) != 0) { |
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
if (!begun) { |
|
// Beginning of a write txn |
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
reason = "DBERR"; |
|
goto bats; |
|
} |
|
begun = true; |
|
} |
|
if (useratts_item_add(conn, ua_item, now, begun)) { |
|
ua_item = NULL; |
|
db++; |
|
} else { |
|
reason = "DBERR"; |
|
goto rollback; |
|
} |
|
} |
|
if (!ua_item) { |
|
K_WLOCK(useratts_free); |
|
ua_item = k_unlink_head(useratts_free); |
|
K_WUNLOCK(useratts_free); |
|
DATA_USERATTS(useratts, ua_item); |
|
bzero(useratts, sizeof(*useratts)); |
|
useratts->userid = users->userid; |
|
STRNCPY(useratts->attname, attname); |
|
HISTORYDATEINIT(useratts, now, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, useratts); |
|
} |
|
// List of valid element names for storage |
|
if (strcmp(dot, "str") == 0) { |
|
STRNCPY(useratts->attstr, data); |
|
set++; |
|
} else if (strcmp(dot, "str2") == 0) { |
|
STRNCPY(useratts->attstr2, data); |
|
set++; |
|
} else if (strcmp(dot, "num") == 0) { |
|
TXT_TO_BIGINT("num", data, useratts->attnum); |
|
set++; |
|
} else if (strcmp(dot, "num2") == 0) { |
|
TXT_TO_BIGINT("num2", data, useratts->attnum2); |
|
set++; |
|
} else if (strcmp(dot, "date") == 0) { |
|
att_to_date(&(useratts->attdate), data, now); |
|
set++; |
|
} else if (strcmp(dot, "date2") == 0) { |
|
att_to_date(&(useratts->attdate2), data, now); |
|
set++; |
|
} else { |
|
reason = "Unknown element"; |
|
goto bats; |
|
} |
|
} |
|
t_item = next_in_ktree(ctx); |
|
} |
|
if (ua_item) { |
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
if (!begun) { |
|
// Beginning of a write txn |
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
reason = "DBERR"; |
|
goto bats; |
|
} |
|
begun = true; |
|
} |
|
if (!useratts_item_add(conn, ua_item, now, begun)) { |
|
reason = "DBERR"; |
|
goto rollback; |
|
} |
|
db++; |
|
} |
|
} |
|
rollback: |
|
if (!reason) |
|
res = PQexec(conn, "Commit", CKPQ_WRITE); |
|
else |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
|
|
PQclear(res); |
|
bats: |
|
if (conned) |
|
PQfinish(conn); |
|
if (reason) { |
|
if (ua_item) { |
|
K_WLOCK(useratts_free); |
|
k_add_head(useratts_free, ua_item); |
|
K_WUNLOCK(useratts_free); |
|
} |
|
snprintf(reply, siz, "ERR.%s", reason); |
|
LOGERR("%s.%s.%s", cmd, id, reply); |
|
return strdup(reply); |
|
} |
|
snprintf(reply, siz, "ok.set %d,%d", db, set); |
|
LOGDEBUG("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
/* Expire the list of useratts for the given username=value |
|
* Format is attlist=attname,attname,... |
|
* Each matching DB attname record will have it's expirydate set to now |
|
* thus an attempt to access it with getatts will not find it and |
|
* return nothing for that attname |
|
*/ |
|
static char *cmd_expatts(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_username, *i_attlist, *u_item, *ua_item; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
USERATTS *useratts; |
|
USERS *users; |
|
char *reason = NULL; |
|
char *attlist, *ptr, *comma; |
|
int db = 0, mis = 0; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", MIN_USERNAME, |
|
(char *)userpatt, reply, siz); |
|
if (!i_username) { |
|
reason = "Missing username"; |
|
goto rats; |
|
} |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
|
|
if (!u_item) { |
|
reason = "Unknown user"; |
|
goto rats; |
|
} else { |
|
DATA_USERS(users, u_item); |
|
i_attlist = require_name(trf_root, "attlist", 1, NULL, reply, siz); |
|
if (!i_attlist) { |
|
reason = "Missing attlist"; |
|
goto rats; |
|
} |
|
|
|
attlist = ptr = strdup(transfer_data(i_attlist)); |
|
while (ptr && *ptr) { |
|
comma = strchr(ptr, ','); |
|
if (comma) |
|
*(comma++) = '\0'; |
|
K_RLOCK(useratts_free); |
|
ua_item = find_useratts(users->userid, ptr); |
|
K_RUNLOCK(useratts_free); |
|
if (!ua_item) |
|
mis++; |
|
else { |
|
DATA_USERATTS(useratts, ua_item); |
|
HISTORYDATEINIT(useratts, now, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, useratts); |
|
/* Since we are expiring records, don't bother |
|
* with combining them all into a single |
|
* transaction and don't abort on error |
|
* Thus if an error is returned, retry would be |
|
* necessary, but some may also have been |
|
* expired successfully */ |
|
if (!useratts_item_expire(conn, ua_item, now)) |
|
reason = "DBERR"; |
|
else |
|
db++; |
|
} |
|
ptr = comma; |
|
} |
|
free(attlist); |
|
} |
|
rats: |
|
if (reason) { |
|
snprintf(reply, siz, "ERR.%s", reason); |
|
LOGERR("%s.%s.%s", cmd, id, reply); |
|
return strdup(reply); |
|
} |
|
snprintf(reply, siz, "ok.exp %d,%d", db, mis); |
|
LOGDEBUG("%s.%s.%s", cmd, id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
/* Return the list of optioncontrols |
|
* Format is optlist=optionname,optionname,optionname,... |
|
* Replies will be optionname=value |
|
* Any optionnames not in the DB or not yet active will be missing |
|
*/ |
|
static char *cmd_getopts(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_optlist, *oc_item; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
char tmp[1024]; |
|
OPTIONCONTROL *optioncontrol; |
|
char *reason = NULL; |
|
char *answer = NULL; |
|
char *optlist = NULL, *ptr, *comma; |
|
size_t len, off; |
|
bool first; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_optlist = require_name(trf_root, "optlist", 1, NULL, reply, siz); |
|
if (!i_optlist) { |
|
reason = "Missing optlist"; |
|
goto ruts; |
|
} |
|
|
|
APPEND_REALLOC_INIT(answer, off, len); |
|
optlist = ptr = strdup(transfer_data(i_optlist)); |
|
first = true; |
|
while (ptr && *ptr) { |
|
comma = strchr(ptr, ','); |
|
if (comma) |
|
*(comma++) = '\0'; |
|
oc_item = find_optioncontrol(ptr, now, pool.height); |
|
/* web code must check the existance of the optionname |
|
* in the reply since it will be missing if it doesn't |
|
* exist in the DB */ |
|
if (oc_item) { |
|
DATA_OPTIONCONTROL(optioncontrol, oc_item); |
|
snprintf(tmp, sizeof(tmp), "%s%s=%s", |
|
first ? EMPTY : FLDSEPSTR, |
|
optioncontrol->optionname, |
|
optioncontrol->optionvalue); |
|
APPEND_REALLOC(answer, off, len, tmp); |
|
first = false; |
|
} |
|
ptr = comma; |
|
} |
|
ruts: |
|
if (optlist) |
|
free(optlist); |
|
|
|
if (reason) { |
|
if (answer) |
|
free(answer); |
|
snprintf(reply, siz, "ERR.%s", reason); |
|
LOGERR("%s.%s.%s", cmd, id, reply); |
|
return strdup(reply); |
|
} |
|
snprintf(reply, siz, "ok.%s", answer); |
|
LOGDEBUG("%s.%s", id, answer); |
|
free(answer); |
|
return strdup(reply); |
|
} |
|
|
|
// This is the same as att_set_date() for now |
|
#define opt_set_date(_date, _data, _now) att_set_date(_date, _data, _now) |
|
|
|
/* Store optioncontrols in the DB |
|
* Format is 1 or more: oc_optionname.fld=value |
|
* i.e. each starts with the constant "oc_" |
|
* optionname cannot contain Tab . or = |
|
* fld is one of the 3: value, date, height |
|
* value must exist |
|
* None, one or both of date and height can exist |
|
* If a matching optioncontrol (same name, date and height) exists, |
|
* it will have it's expiry date set to now and be replaced with the new value |
|
* The date field requires either an epoch sec,usec |
|
* (usec is optional and defaults to 0) or one of: now or now+NNN |
|
* now is the current epoch value and now+NNN is the epoch + NNN seconds |
|
* See opt_set_date() above */ |
|
static char *cmd_setopts(PGconn *conn, char *cmd, char *id, |
|
tv_t *now, char *by, char *code, char *inet, |
|
__maybe_unused tv_t *notcd, K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
bool conned = false; |
|
K_ITEM *t_item, *oc_item = NULL, *ok = NULL; |
|
K_TREE_CTX ctx[1]; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
TRANSFER *transfer; |
|
OPTIONCONTROL *optioncontrol; |
|
char optionname[sizeof(optioncontrol->optionname)*2]; |
|
char *reason = NULL; |
|
char *dot, *data; |
|
bool begun = false, gotvalue = false; |
|
int db = 0; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
t_item = first_in_ktree_nolock(trf_root, ctx); |
|
while (t_item) { |
|
DATA_TRANSFER(transfer, t_item); |
|
if (strncmp(transfer->name, "oc_", 3) == 0) { |
|
data = transfer_data(t_item); |
|
STRNCPY(optionname, transfer->name + 3); |
|
dot = strchr(optionname, '.'); |
|
if (!dot) { |
|
reason = "Missing field"; |
|
goto rollback; |
|
} |
|
*(dot++) = '\0'; |
|
// If we already had a different one, save it to the DB |
|
if (oc_item && strcmp(optioncontrol->optionname, optionname) != 0) { |
|
if (!gotvalue) { |
|
reason = "Missing value"; |
|
goto rollback; |
|
} |
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
if (!begun) { |
|
// Beginning of a write txn |
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
reason = "DBERR"; |
|
goto rollback; |
|
} |
|
begun = true; |
|
} |
|
ok = optioncontrol_item_add(conn, oc_item, now, begun); |
|
oc_item = NULL; |
|
if (ok) |
|
db++; |
|
else { |
|
reason = "DBERR"; |
|
goto rollback; |
|
} |
|
} |
|
if (!oc_item) { |
|
K_WLOCK(optioncontrol_free); |
|
oc_item = k_unlink_head(optioncontrol_free); |
|
K_WUNLOCK(optioncontrol_free); |
|
DATA_OPTIONCONTROL(optioncontrol, oc_item); |
|
bzero(optioncontrol, sizeof(*optioncontrol)); |
|
STRNCPY(optioncontrol->optionname, optionname); |
|
optioncontrol->activationheight = OPTIONCONTROL_HEIGHT; |
|
HISTORYDATEINIT(optioncontrol, now, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, optioncontrol); |
|
gotvalue = false; |
|
} |
|
if (strcmp(dot, "value") == 0) { |
|
DUP_POINTER(optioncontrol_free, |
|
optioncontrol->optionvalue, data); |
|
gotvalue = true; |
|
} else if (strcmp(dot, "date") == 0) { |
|
att_to_date(&(optioncontrol->activationdate), data, now); |
|
} else if (strcmp(dot, "height") == 0) { |
|
TXT_TO_INT("height", data, optioncontrol->activationheight); |
|
} else { |
|
reason = "Unknown field"; |
|
goto rollback; |
|
} |
|
} |
|
t_item = next_in_ktree(ctx); |
|
} |
|
if (oc_item) { |
|
if (!gotvalue) { |
|
reason = "Missing value"; |
|
goto rollback; |
|
} |
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
if (!begun) { |
|
// Beginning of a write txn |
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
reason = "DBERR"; |
|
goto rollback; |
|
} |
|
begun = true; |
|
} |
|
ok = optioncontrol_item_add(conn, oc_item, now, begun); |
|
oc_item = NULL; |
|
if (ok) |
|
db++; |
|
else { |
|
reason = "DBERR"; |
|
goto rollback; |
|
} |
|
} |
|
rollback: |
|
if (begun) { |
|
if (reason) |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
else |
|
res = PQexec(conn, "Commit", CKPQ_WRITE); |
|
|
|
PQclear(res); |
|
} |
|
|
|
if (conned) |
|
PQfinish(conn); |
|
if (reason) { |
|
snprintf(reply, siz, "ERR.%s", reason); |
|
LOGERR("%s.%s.%s", cmd, id, reply); |
|
return strdup(reply); |
|
} |
|
snprintf(reply, siz, "ok.set %d", db); |
|
LOGDEBUG("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
/* Kept for reference/comparison to cmd_pplns2() |
|
* This will get different results due to the fact that it uses the current |
|
* contents of the payoutaddresses table |
|
* However, the only differences should be the addresses, |
|
* and the breakdown for percent address users, |
|
* the totals per user and per payout should still be the same */ |
|
static char *cmd_pplns(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, K_TREE *trf_root) |
|
{ |
|
char reply[1024], tmp[1024], *buf; |
|
char *block_extra, *share_status = EMPTY, *marks_status = EMPTY; |
|
size_t siz = sizeof(reply); |
|
K_ITEM *i_height, *i_difftimes, *i_diffadd, *i_allowaged; |
|
K_ITEM b_look, ss_look, *b_item, *w_item, *ss_item; |
|
K_ITEM wm_look, *wm_item, ms_look, *ms_item; |
|
K_ITEM *mu_item, *wb_item, *u_item; |
|
SHARESUMMARY looksharesummary, *sharesummary; |
|
WORKMARKERS lookworkmarkers, *workmarkers; |
|
MARKERSUMMARY lookmarkersummary, *markersummary; |
|
MININGPAYOUTS *miningpayouts; |
|
WORKINFO *workinfo; |
|
TRANSFER *transfer; |
|
BLOCKS lookblocks, *blocks; |
|
K_TREE *mu_root; |
|
K_STORE *mu_store; |
|
USERS *users; |
|
int32_t height; |
|
int64_t block_workinfoid, end_workinfoid; |
|
int64_t begin_workinfoid; |
|
int64_t total_share_count, acc_share_count; |
|
int64_t ss_count, wm_count, ms_count; |
|
char tv_buf[DATE_BUFSIZ]; |
|
tv_t cd, begin_tv, block_tv, end_tv; |
|
K_TREE_CTX ctx[1], wm_ctx[1], ms_ctx[1], pay_ctx[1]; |
|
double ndiff, total_diff, elapsed; |
|
double diff_times = 1.0; |
|
double diff_add = 0.0; |
|
double diff_want; |
|
bool allow_aged = false, countbacklimit; |
|
size_t len, off; |
|
int rows; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
if (sharesummary_marks_limit) |
|
marks_status = "ckdb -w load value means pplns may be incorrect"; |
|
|
|
i_height = require_name(trf_root, "height", 1, NULL, reply, siz); |
|
if (!i_height) |
|
return strdup(reply); |
|
TXT_TO_INT("height", transfer_data(i_height), height); |
|
|
|
i_difftimes = optional_name(trf_root, "diff_times", 1, NULL, reply, siz); |
|
if (i_difftimes) |
|
TXT_TO_DOUBLE("diff_times", transfer_data(i_difftimes), diff_times); |
|
|
|
i_diffadd = optional_name(trf_root, "diff_add", 1, NULL, reply, siz); |
|
if (i_diffadd) |
|
TXT_TO_DOUBLE("diff_add", transfer_data(i_diffadd), diff_add); |
|
|
|
i_allowaged = optional_name(trf_root, "allow_aged", 1, NULL, reply, siz); |
|
if (i_allowaged) { |
|
DATA_TRANSFER(transfer, i_allowaged); |
|
if (toupper(transfer->mvalue[0]) == TRUE_STR[0]) |
|
allow_aged = true; |
|
} |
|
|
|
LOGDEBUG("%s(): height %"PRId32, __func__, height); |
|
|
|
DATE_ZERO(&block_tv); |
|
DATE_ZERO(&cd); |
|
lookblocks.height = height + 1; |
|
lookblocks.blockhash[0] = '\0'; |
|
INIT_BLOCKS(&b_look); |
|
b_look.data = (void *)(&lookblocks); |
|
K_RLOCK(blocks_free); |
|
b_item = find_before_in_ktree(blocks_root, &b_look, ctx); |
|
if (!b_item) { |
|
K_RUNLOCK(blocks_free); |
|
snprintf(reply, siz, "ERR.no block height %d", height); |
|
return strdup(reply); |
|
} |
|
DATA_BLOCKS_NULL(blocks, b_item); |
|
while (b_item && blocks->height == height) { |
|
if (blocks->confirmed[0] == BLOCKS_NEW) { |
|
copy_tv(&block_tv, &(blocks->createdate)); |
|
copy_tv(&end_tv, &(blocks->createdate)); |
|
} |
|
// Allow any state, but report it |
|
if (CURRENT(&(blocks->expirydate))) |
|
break; |
|
b_item = prev_in_ktree(ctx); |
|
DATA_BLOCKS_NULL(blocks, b_item); |
|
} |
|
K_RUNLOCK(blocks_free); |
|
if (!b_item || blocks->height != height) { |
|
snprintf(reply, siz, "ERR.no CURRENT block %d", height); |
|
return strdup(reply); |
|
} |
|
if (block_tv.tv_sec == 0) { |
|
snprintf(reply, siz, "ERR.block %d missing '%s' record", |
|
height, |
|
blocks_confirmed(BLOCKS_NEW_STR)); |
|
return strdup(reply); |
|
} |
|
LOGDEBUG("%s(): block %"PRId32"/%"PRId64"/%s/%s/%"PRId64, |
|
__func__, blocks->height, blocks->workinfoid, |
|
blocks->workername, blocks->confirmed, blocks->reward); |
|
switch (blocks->confirmed[0]) { |
|
case BLOCKS_NEW: |
|
block_extra = "Can't be paid out yet"; |
|
break; |
|
case BLOCKS_ORPHAN: |
|
case BLOCKS_REJECT: |
|
block_extra = "Can't be paid out"; |
|
break; |
|
default: |
|
block_extra = EMPTY; |
|
break; |
|
} |
|
block_workinfoid = blocks->workinfoid; |
|
w_item = find_workinfo(block_workinfoid, NULL); |
|
if (!w_item) { |
|
snprintf(reply, siz, "ERR.missing workinfo %"PRId64, block_workinfoid); |
|
return strdup(reply); |
|
} |
|
DATA_WORKINFO(workinfo, w_item); |
|
|
|
ndiff = workinfo->diff_target; |
|
diff_want = ndiff * diff_times + diff_add; |
|
if (diff_want < 1.0) { |
|
snprintf(reply, siz, |
|
"ERR.invalid diff_want result %f", |
|
diff_want); |
|
return strdup(reply); |
|
} |
|
|
|
if (blocks->height > FIVExSTT) |
|
countbacklimit = true; |
|
else |
|
countbacklimit = false; |
|
LOGDEBUG("%s(): ndiff %.0f limit=%s", |
|
__func__, ndiff, |
|
countbacklimit ? "true" : "false"); |
|
begin_workinfoid = end_workinfoid = 0; |
|
total_share_count = acc_share_count = 0; |
|
total_diff = 0; |
|
ss_count = wm_count = ms_count = 0; |
|
|
|
mu_store = k_new_store(miningpayouts_free); |
|
mu_root = new_ktree_auto("OldMPU", cmp_mu, miningpayouts_free); |
|
|
|
looksharesummary.workinfoid = block_workinfoid; |
|
looksharesummary.userid = MAXID; |
|
looksharesummary.workername = EMPTY; |
|
INIT_SHARESUMMARY(&ss_look); |
|
ss_look.data = (void *)(&looksharesummary); |
|
K_WLOCK(miningpayouts_free); |
|
K_RLOCK(sharesummary_free); |
|
K_RLOCK(workmarkers_free); |
|
K_RLOCK(markersummary_free); |
|
ss_item = find_before_in_ktree(sharesummary_workinfoid_root, |
|
&ss_look, ctx); |
|
DATA_SHARESUMMARY_NULL(sharesummary, ss_item); |
|
if (ss_item) |
|
end_workinfoid = sharesummary->workinfoid; |
|
/* add up all sharesummaries until >= diff_want |
|
* also record the latest lastshareacc - that will be the end pplns time |
|
* which will be >= block_tv */ |
|
while (total_diff < diff_want && ss_item) { |
|
switch (sharesummary->complete[0]) { |
|
case SUMMARY_CONFIRM: |
|
break; |
|
case SUMMARY_COMPLETE: |
|
if (allow_aged) |
|
break; |
|
default: |
|
share_status = "Not ready1"; |
|
} |
|
|
|
// Stop before FIVExWID if necessary |
|
if (countbacklimit && sharesummary->workinfoid <= FIVExWID) |
|
break; |
|
|
|
ss_count++; |
|
total_share_count += sharesummary->sharecount; |
|
acc_share_count += sharesummary->shareacc; |
|
total_diff += (int64_t)(sharesummary->diffacc); |
|
begin_workinfoid = sharesummary->workinfoid; |
|
if (tv_newer(&end_tv, &(sharesummary->lastshareacc))) |
|
copy_tv(&end_tv, &(sharesummary->lastshareacc)); |
|
upd_add_mu(mu_root, mu_store, sharesummary->userid, |
|
(int64_t)(sharesummary->diffacc)); |
|
ss_item = prev_in_ktree(ctx); |
|
DATA_SHARESUMMARY_NULL(sharesummary, ss_item); |
|
} |
|
|
|
// include all the rest of the sharesummaries with begin_workinfoid |
|
while (ss_item && sharesummary->workinfoid == begin_workinfoid) { |
|
switch (sharesummary->complete[0]) { |
|
case SUMMARY_CONFIRM: |
|
break; |
|
case SUMMARY_COMPLETE: |
|
if (allow_aged) |
|
break; |
|
default: |
|
if (share_status == EMPTY) |
|
share_status = "Not ready2"; |
|
else |
|
share_status = "Not ready1+2"; |
|
} |
|
ss_count++; |
|
total_share_count += sharesummary->sharecount; |
|
acc_share_count += sharesummary->shareacc; |
|
total_diff += (int64_t)(sharesummary->diffacc); |
|
upd_add_mu(mu_root, mu_store, sharesummary->userid, |
|
(int64_t)(sharesummary->diffacc)); |
|
ss_item = prev_in_ktree(ctx); |
|
DATA_SHARESUMMARY_NULL(sharesummary, ss_item); |
|
} |
|
LOGDEBUG("%s(): ss %"PRId64" total %.0f want %.0f", |
|
__func__, ss_count, total_diff, diff_want); |
|
/* If we haven't met or exceeded the required N, |
|
* move on to the markersummaries */ |
|
if (total_diff < diff_want) { |
|
lookworkmarkers.expirydate.tv_sec = default_expiry.tv_sec; |
|
lookworkmarkers.expirydate.tv_usec = default_expiry.tv_usec; |
|
if (begin_workinfoid != 0) |
|
lookworkmarkers.workinfoidend = begin_workinfoid; |
|
else |
|
lookworkmarkers.workinfoidend = block_workinfoid + 1; |
|
INIT_WORKMARKERS(&wm_look); |
|
wm_look.data = (void *)(&lookworkmarkers); |
|
wm_item = find_before_in_ktree(workmarkers_workinfoid_root, |
|
&wm_look, wm_ctx); |
|
DATA_WORKMARKERS_NULL(workmarkers, wm_item); |
|
LOGDEBUG("%s(): workmarkers < %"PRId64, __func__, lookworkmarkers.workinfoidend); |
|
while (total_diff < diff_want && wm_item && CURRENT(&(workmarkers->expirydate))) { |
|
if (WMPROCESSED(workmarkers->status)) { |
|
// Stop before FIVExWID if necessary |
|
if (countbacklimit && workmarkers->workinfoidstart <= FIVExWID) |
|
break; |
|
|
|
wm_count++; |
|
lookmarkersummary.markerid = workmarkers->markerid; |
|
lookmarkersummary.userid = MAXID; |
|
lookmarkersummary.workername = EMPTY; |
|
INIT_MARKERSUMMARY(&ms_look); |
|
ms_look.data = (void *)(&lookmarkersummary); |
|
ms_item = find_before_in_ktree(markersummary_root, |
|
&ms_look, ms_ctx); |
|
DATA_MARKERSUMMARY_NULL(markersummary, ms_item); |
|
// add the whole markerid |
|
while (ms_item && markersummary->markerid == workmarkers->markerid) { |
|
if (end_workinfoid == 0) |
|
end_workinfoid = workmarkers->workinfoidend; |
|
ms_count++; |
|
total_share_count += markersummary->sharecount; |
|
acc_share_count += markersummary->shareacc; |
|
total_diff += (int64_t)(markersummary->diffacc); |
|
begin_workinfoid = workmarkers->workinfoidstart; |
|
if (tv_newer(&end_tv, &(markersummary->lastshareacc))) |
|
copy_tv(&end_tv, &(markersummary->lastshareacc)); |
|
upd_add_mu(mu_root, mu_store, markersummary->userid, |
|
(int64_t)(markersummary->diffacc)); |
|
ms_item = prev_in_ktree(ms_ctx); |
|
DATA_MARKERSUMMARY_NULL(markersummary, ms_item); |
|
} |
|
} |
|
wm_item = prev_in_ktree(wm_ctx); |
|
DATA_WORKMARKERS_NULL(workmarkers, wm_item); |
|
} |
|
LOGDEBUG("%s(): wm %"PRId64" ms %"PRId64" total %.0f want %.0f", |
|
__func__, wm_count, ms_count, total_diff, diff_want); |
|
} |
|
K_RUNLOCK(markersummary_free); |
|
K_RUNLOCK(workmarkers_free); |
|
K_RUNLOCK(sharesummary_free); |
|
K_WUNLOCK(miningpayouts_free); |
|
|
|
LOGDEBUG("%s(): total %.0f want %.0f", __func__, total_diff, diff_want); |
|
if (total_diff == 0.0) { |
|
snprintf(reply, siz, |
|
"ERR.total share diff 0 before workinfo %"PRId64, |
|
block_workinfoid); |
|
goto shazbot; |
|
} |
|
|
|
wb_item = find_workinfo(begin_workinfoid, NULL); |
|
if (!wb_item) { |
|
snprintf(reply, siz, "ERR.missing begin workinfo record! %"PRId64, block_workinfoid); |
|
goto shazbot; |
|
} |
|
DATA_WORKINFO(workinfo, wb_item); |
|
|
|
copy_tv(&begin_tv, &(workinfo->createdate)); |
|
/* Elapsed is from the start of the first workinfoid used, |
|
* to the time of the last share counted - |
|
* which can be after the block, but must have the same workinfoid as |
|
* the block, if it is after the block |
|
* All shares accepted in all workinfoids after the block's workinfoid |
|
* will not be creditied to this block no matter what the height |
|
* of their workinfoid is - but will be candidates for the next block */ |
|
elapsed = tvdiff(&end_tv, &begin_tv); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
snprintf(tmp, sizeof(tmp), "block=%d%c", height, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "block_hash=%s%c", blocks->blockhash, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "block_reward=%"PRId64"%c", blocks->reward, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "block_status=%s%c", |
|
blocks_confirmed(blocks->confirmed), FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "block_extra=%s%c", block_extra, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "share_status=%s%c", share_status, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "marks_status=%s%c", marks_status, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "workername=%s%c", blocks->workername, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "nonce=%s%c", blocks->nonce, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "begin_workinfoid=%"PRId64"%c", begin_workinfoid, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "block_workinfoid=%"PRId64"%c", block_workinfoid, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "end_workinfoid=%"PRId64"%c", end_workinfoid, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "diffacc_total=%.0f%c", total_diff, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "pplns_elapsed=%f%c", elapsed, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
rows = 0; |
|
mu_item = first_in_ktree(mu_root, ctx); |
|
while (mu_item) { |
|
DATA_MININGPAYOUTS(miningpayouts, mu_item); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_userid(miningpayouts->userid); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
snprintf(reply, siz, |
|
"ERR.unknown userid %"PRId64, |
|
miningpayouts->userid); |
|
goto shazbot; |
|
} |
|
|
|
DATA_USERS(users, u_item); |
|
|
|
K_ITEM *pa_item; |
|
PAYMENTADDRESSES *pa; |
|
int64_t paytotal; |
|
double amount; |
|
int count; |
|
|
|
K_RLOCK(paymentaddresses_free); |
|
pa_item = find_paymentaddresses(miningpayouts->userid, pay_ctx); |
|
if (pa_item) { |
|
paytotal = 0; |
|
DATA_PAYMENTADDRESSES(pa, pa_item); |
|
while (pa_item && CURRENT(&(pa->expirydate)) && |
|
pa->userid == miningpayouts->userid) { |
|
paytotal += pa->payratio; |
|
pa_item = prev_in_ktree(pay_ctx); |
|
DATA_PAYMENTADDRESSES_NULL(pa, pa_item); |
|
} |
|
count = 0; |
|
pa_item = find_paymentaddresses(miningpayouts->userid, pay_ctx); |
|
DATA_PAYMENTADDRESSES_NULL(pa, pa_item); |
|
while (pa_item && CURRENT(&(pa->expirydate)) && |
|
pa->userid == miningpayouts->userid) { |
|
amount = (double)(miningpayouts->amount) * |
|
(double)pa->payratio / (double)paytotal; |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"user:%d=%s.%d%cpayaddress:%d=%s%c", |
|
rows, users->username, ++count, FLDSEP, |
|
rows, pa->payaddress, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"diffacc_user:%d=%.1f%c", |
|
rows, amount, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
rows++; |
|
|
|
pa_item = prev_in_ktree(pay_ctx); |
|
DATA_PAYMENTADDRESSES_NULL(pa, pa_item); |
|
} |
|
K_RUNLOCK(paymentaddresses_free); |
|
} else { |
|
K_RUNLOCK(paymentaddresses_free); |
|
snprintf(tmp, sizeof(tmp), |
|
"user:%d=%s.0%cpayaddress:%d=%s%c", |
|
rows, users->username, FLDSEP, |
|
rows, "none", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"diffacc_user:%d=%"PRId64"%c", |
|
rows, |
|
miningpayouts->amount, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
rows++; |
|
} |
|
mu_item = next_in_ktree(ctx); |
|
} |
|
snprintf(tmp, sizeof(tmp), |
|
"rows=%d%cflds=%s%c", |
|
rows, FLDSEP, |
|
"user,diffacc_user,payaddress", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s%c", |
|
"Users", FLDSEP, "", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
tv_to_buf(&begin_tv, tv_buf, sizeof(tv_buf)); |
|
snprintf(tmp, sizeof(tmp), "begin_stamp=%s%c", tv_buf, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "begin_epoch=%ld%c", begin_tv.tv_sec, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
tv_to_buf(&block_tv, tv_buf, sizeof(tv_buf)); |
|
snprintf(tmp, sizeof(tmp), "block_stamp=%s%c", tv_buf, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "block_epoch=%ld%c", block_tv.tv_sec, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
tv_to_buf(&end_tv, tv_buf, sizeof(tv_buf)); |
|
snprintf(tmp, sizeof(tmp), "end_stamp=%s%c", tv_buf, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "end_epoch=%ld%c", end_tv.tv_sec, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "block_ndiff=%f%c", ndiff, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "diff_times=%f%c", diff_times, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "diff_add=%f%c", diff_add, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "diff_want=%f%c", diff_want, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "acc_share_count=%"PRId64"%c", |
|
acc_share_count, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "total_share_count=%"PRId64"%c", |
|
total_share_count, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "ss_count=%"PRId64"%c", ss_count, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "wm_count=%"PRId64"%c", wm_count, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "ms_count=%"PRId64"%c", ms_count, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
// So web can always verify it received all data |
|
APPEND_REALLOC(buf, off, len, "pplns_last=1"); |
|
|
|
free_ktree(mu_root, NULL); |
|
K_WLOCK(miningpayouts_free); |
|
k_list_transfer_to_head(mu_store, miningpayouts_free); |
|
K_WUNLOCK(miningpayouts_free); |
|
mu_store = k_free_store(mu_store); |
|
|
|
LOGDEBUG("%s.ok.pplns.%s", id, buf); |
|
return buf; |
|
|
|
shazbot: |
|
|
|
free_ktree(mu_root, NULL); |
|
K_WLOCK(miningpayouts_free); |
|
k_list_transfer_to_head(mu_store, miningpayouts_free); |
|
K_WUNLOCK(miningpayouts_free); |
|
mu_store = k_free_store(mu_store); |
|
|
|
return strdup(reply); |
|
} |
|
|
|
// Generated from the payouts, miningpayouts and payments data |
|
static char *cmd_pplns2(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, K_TREE *trf_root) |
|
{ |
|
char reply[1024], tmp[1024], *buf; |
|
char *block_extra, *marks_status = EMPTY; |
|
size_t siz = sizeof(reply); |
|
K_ITEM *i_height; |
|
K_ITEM b_look, *b_item, *p_item, *mp_item, *pay_item, *u_item; |
|
K_ITEM *w_item; |
|
MININGPAYOUTS *miningpayouts; |
|
PAYMENTS *payments; |
|
PAYOUTS *payouts; |
|
BLOCKS lookblocks, *blocks; |
|
WORKINFO *bworkinfo, *workinfo; |
|
USERS *users; |
|
int32_t height; |
|
K_TREE_CTX b_ctx[1], mp_ctx[1], pay_ctx[1]; |
|
char tv_buf[DATE_BUFSIZ]; |
|
size_t len, off; |
|
int rows; |
|
bool pok; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
if (sharesummary_marks_limit) |
|
marks_status = "ckdb -w load value means pplns may be incorrect"; |
|
|
|
i_height = require_name(trf_root, "height", 1, NULL, reply, siz); |
|
if (!i_height) |
|
return strdup(reply); |
|
TXT_TO_INT("height", transfer_data(i_height), height); |
|
|
|
LOGDEBUG("%s(): height %"PRId32, __func__, height); |
|
|
|
lookblocks.height = height; |
|
lookblocks.blockhash[0] = '\0'; |
|
INIT_BLOCKS(&b_look); |
|
b_look.data = (void *)(&lookblocks); |
|
K_RLOCK(blocks_free); |
|
b_item = find_after_in_ktree(blocks_root, &b_look, b_ctx); |
|
K_RUNLOCK(blocks_free); |
|
if (!b_item) { |
|
snprintf(reply, siz, "ERR.no block height >= %"PRId32, height); |
|
return strdup(reply); |
|
} |
|
DATA_BLOCKS(blocks, b_item); |
|
if (!b_item || blocks->height != height) { |
|
snprintf(reply, siz, "ERR.no block height %"PRId32, height); |
|
return strdup(reply); |
|
} |
|
if (blocks->blockcreatedate.tv_sec == 0) { |
|
snprintf(reply, siz, "ERR.block %"PRId32" has 0 blockcreatedate", |
|
height); |
|
return strdup(reply); |
|
} |
|
if (!CURRENT(&(blocks->expirydate))) { |
|
snprintf(reply, siz, "ERR.no CURRENT block %d"PRId32, height); |
|
return strdup(reply); |
|
} |
|
LOGDEBUG("%s(): block %"PRId32"/%"PRId64"/%s/%s/%"PRId64, |
|
__func__, blocks->height, blocks->workinfoid, |
|
blocks->workername, blocks->confirmed, blocks->reward); |
|
switch (blocks->confirmed[0]) { |
|
case BLOCKS_NEW: |
|
block_extra = "Can't be paid out yet"; |
|
break; |
|
case BLOCKS_ORPHAN: |
|
case BLOCKS_REJECT: |
|
block_extra = "Can't be paid out"; |
|
break; |
|
default: |
|
block_extra = EMPTY; |
|
break; |
|
} |
|
|
|
w_item = find_workinfo(blocks->workinfoid, NULL); |
|
if (!w_item) { |
|
snprintf(reply, siz, "ERR.missing block workinfo record!" |
|
" %"PRId64, |
|
blocks->workinfoid); |
|
return strdup(reply); |
|
} |
|
DATA_WORKINFO(bworkinfo, w_item); |
|
|
|
pok = false; |
|
K_RLOCK(payouts_free); |
|
p_item = find_payouts(height, blocks->blockhash); |
|
DATA_PAYOUTS_NULL(payouts, p_item); |
|
if (p_item && PAYGENERATED(payouts->status)) |
|
pok = true; |
|
K_RUNLOCK(payouts_free); |
|
if (!p_item) { |
|
snprintf(reply, siz, "ERR.no payout for %"PRId32"/%s", |
|
height, blocks->blockhash); |
|
return strdup(reply); |
|
} |
|
if (!pok) { |
|
snprintf(reply, siz, "ERR.payout %"PRId64" status=%s " |
|
"for %"PRId32"/%s", |
|
payouts->payoutid, payouts->status, height, |
|
blocks->blockhash); |
|
return strdup(reply); |
|
} |
|
|
|
LOGDEBUG("%s(): total %.1f want %.1f", |
|
__func__, payouts->diffused, payouts->diffwanted); |
|
|
|
w_item = find_workinfo(payouts->workinfoidstart, NULL); |
|
if (!w_item) { |
|
snprintf(reply, siz, "ERR.missing begin workinfo record!" |
|
" %"PRId64, |
|
payouts->workinfoidstart); |
|
return strdup(reply); |
|
} |
|
DATA_WORKINFO(workinfo, w_item); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
snprintf(tmp, sizeof(tmp), "block=%d%c", height, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "block_hash=%s%c", blocks->blockhash, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "block_reward=%"PRId64"%c", blocks->reward, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "miner_reward=%"PRId64"%c", payouts->minerreward, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "block_status=%s%c", |
|
blocks_confirmed(blocks->confirmed), FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "block_extra=%s%c", block_extra, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "marks_status=%s%c", marks_status, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "workername=%s%c", blocks->workername, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "nonce=%s%c", blocks->nonce, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "begin_workinfoid=%"PRId64"%c", payouts->workinfoidstart, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "block_workinfoid=%"PRId64"%c", blocks->workinfoid, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "end_workinfoid=%"PRId64"%c", payouts->workinfoidend, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "diffacc_total=%.1f%c", payouts->diffused, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "pplns_elapsed=%"PRId64"%c", payouts->elapsed, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
rows = 0; |
|
K_RLOCK(miningpayouts_free); |
|
mp_item = first_miningpayouts(payouts->payoutid, mp_ctx); |
|
K_RUNLOCK(miningpayouts_free); |
|
DATA_MININGPAYOUTS_NULL(miningpayouts, mp_item); |
|
while (mp_item && miningpayouts->payoutid == payouts->payoutid) { |
|
if (CURRENT(&(miningpayouts->expirydate))) { |
|
int out = 0; |
|
K_RLOCK(users_free); |
|
u_item = find_userid(miningpayouts->userid); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
snprintf(reply, siz, |
|
"ERR.unknown userid %"PRId64, |
|
miningpayouts->userid); |
|
goto shazbot; |
|
} |
|
DATA_USERS(users, u_item); |
|
|
|
K_RLOCK(payments_free); |
|
pay_item = find_first_paypayid(miningpayouts->userid, |
|
payouts->payoutid, |
|
pay_ctx); |
|
DATA_PAYMENTS_NULL(payments, pay_item); |
|
while (pay_item && |
|
payments->userid == miningpayouts->userid && |
|
payments->payoutid == payouts->payoutid) { |
|
if (CURRENT(&(payments->expirydate))) { |
|
snprintf(tmp, sizeof(tmp), |
|
"user:%d=%s%c" |
|
"payaddress:%d=%s%c" |
|
"amount:%d=%"PRId64"%c" |
|
"diffacc:%d=%.1f%c", |
|
rows, payments->subname, FLDSEP, |
|
rows, payments->payaddress, FLDSEP, |
|
rows, payments->amount, FLDSEP, |
|
rows, payments->diffacc, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
rows++; |
|
out++; |
|
} |
|
pay_item = next_in_ktree(pay_ctx); |
|
DATA_PAYMENTS_NULL(payments, pay_item); |
|
} |
|
K_RUNLOCK(payments_free); |
|
if (out == 0) { |
|
snprintf(tmp, sizeof(tmp), |
|
"user:%d=%s.0%c" |
|
"payaddress:%d=%s%c" |
|
"amount:%d=%"PRId64"%c" |
|
"diffacc:%d=%.1f%c", |
|
rows, users->username, FLDSEP, |
|
rows, "none", FLDSEP, |
|
rows, miningpayouts->amount, FLDSEP, |
|
rows, miningpayouts->diffacc, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
rows++; |
|
} |
|
} |
|
K_RLOCK(miningpayouts_free); |
|
mp_item = next_in_ktree(mp_ctx); |
|
K_RUNLOCK(miningpayouts_free); |
|
DATA_MININGPAYOUTS_NULL(miningpayouts, mp_item); |
|
} |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"rows=%d%cflds=%s%c", |
|
rows, FLDSEP, |
|
"user,payaddress,amount,diffacc", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s%c", |
|
"Users", FLDSEP, "", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
tv_to_buf(&(workinfo->createdate), tv_buf, sizeof(tv_buf)); |
|
snprintf(tmp, sizeof(tmp), "begin_stamp=%s%c", tv_buf, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "begin_epoch=%ld%c", |
|
workinfo->createdate.tv_sec, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
tv_to_buf(&(blocks->blockcreatedate), tv_buf, sizeof(tv_buf)); |
|
snprintf(tmp, sizeof(tmp), "block_stamp=%s%c", tv_buf, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "block_epoch=%ld%c", |
|
blocks->blockcreatedate.tv_sec, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
tv_to_buf(&(payouts->lastshareacc), tv_buf, sizeof(tv_buf)); |
|
snprintf(tmp, sizeof(tmp), "end_stamp=%s%c", tv_buf, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "end_epoch=%ld%c", |
|
payouts->lastshareacc.tv_sec, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "%s%c", payouts->stats, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "block_ndiff=%f%c", |
|
bworkinfo->diff_target, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "diff_want=%.1f%c", |
|
payouts->diffwanted, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "acc_share_count=%.0f%c", |
|
payouts->shareacc, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
// So web can always verify it received all data |
|
APPEND_REALLOC(buf, off, len, "pplns_last=1"); |
|
|
|
LOGDEBUG("%s.ok.pplns.%s", id, buf); |
|
return buf; |
|
|
|
shazbot: |
|
return strdup(reply); |
|
} |
|
|
|
static char *cmd_payouts(PGconn *conn, char *cmd, char *id, tv_t *now, |
|
char *by, char *code, char *inet, |
|
__maybe_unused tv_t *cd, K_TREE *trf_root) |
|
{ |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
char msg[1024] = ""; |
|
K_ITEM *i_action, *i_payoutid, *i_height, *i_blockhash, *i_addrdate; |
|
K_ITEM *p_item, *p2_item, *old_p2_item; |
|
PAYOUTS *payouts, *payouts2, *old_payouts2; |
|
char *action; |
|
int64_t payoutid = -1; |
|
int32_t height = 0; |
|
char blockhash[TXT_BIG+1]; |
|
tv_t addrdate; |
|
bool ok = true; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_action = require_name(trf_root, "action", 1, NULL, reply, siz); |
|
if (!i_action) |
|
return strdup(reply); |
|
action = transfer_data(i_action); |
|
|
|
if (strcasecmp(action, "generated") == 0) { |
|
/* Change the status of a processing payout to generated |
|
* Require payoutid |
|
* Use this if the payout process completed but the end txn, |
|
* that only updates the payout to generated, failed */ |
|
i_payoutid = require_name(trf_root, "payoutid", 1, |
|
(char *)intpatt, reply, siz); |
|
if (!i_payoutid) |
|
return strdup(reply); |
|
TXT_TO_BIGINT("payoutid", transfer_data(i_payoutid), payoutid); |
|
|
|
K_WLOCK(payouts_free); |
|
p_item = find_payoutid(payoutid); |
|
if (!p_item) { |
|
K_WUNLOCK(payouts_free); |
|
snprintf(reply, siz, |
|
"no payout with id %"PRId64, payoutid); |
|
return strdup(reply); |
|
} |
|
DATA_PAYOUTS(payouts, p_item); |
|
if (!PAYPROCESSING(payouts->status)) { |
|
K_WUNLOCK(payouts_free); |
|
snprintf(reply, siz, |
|
"status !processing (%s) for payout %"PRId64, |
|
payouts->status, payoutid); |
|
return strdup(reply); |
|
} |
|
p2_item = k_unlink_head(payouts_free); |
|
K_WUNLOCK(payouts_free); |
|
|
|
/* There is a risk of the p_item changing while it's unlocked, |
|
* but since this is a manual interface it's not really likely |
|
* and there'll be an error if something goes wrong |
|
* It reports the old and new status */ |
|
DATA_PAYOUTS(payouts2, p2_item); |
|
bzero(payouts2, sizeof(*payouts2)); |
|
payouts2->payoutid = payouts->payoutid; |
|
payouts2->height = payouts->height; |
|
STRNCPY(payouts2->blockhash, payouts->blockhash); |
|
payouts2->minerreward = payouts->minerreward; |
|
payouts2->workinfoidstart = payouts->workinfoidstart; |
|
payouts2->workinfoidend = payouts->workinfoidend; |
|
payouts2->elapsed = payouts->elapsed; |
|
STRNCPY(payouts2->status, PAYOUTS_GENERATED_STR); |
|
payouts2->diffwanted = payouts->diffwanted; |
|
payouts2->diffused = payouts->diffused; |
|
payouts2->shareacc = payouts->shareacc; |
|
copy_tv(&(payouts2->lastshareacc), &(payouts->lastshareacc)); |
|
DUP_POINTER(payouts_free, payouts2->stats, payouts->stats); |
|
|
|
ok = payouts_add(conn, true, p2_item, &old_p2_item, |
|
by, code, inet, now, NULL, false); |
|
if (!ok) { |
|
snprintf(reply, siz, "failed payout %"PRId64, payoutid); |
|
return strdup(reply); |
|
} |
|
// Original wasn't generated, so reward it |
|
reward_shifts(payouts2, 1); |
|
DATA_PAYOUTS(payouts2, p2_item); |
|
DATA_PAYOUTS(old_payouts2, old_p2_item); |
|
snprintf(msg, sizeof(msg), |
|
"payout %"PRId64" changed from '%s' to '%s' for " |
|
"%"PRId32"/%s", |
|
payoutid, old_payouts2->status, payouts2->status, |
|
payouts2->height, payouts2->blockhash); |
|
} else if (strcasecmp(action, "orphan") == 0 || |
|
strcasecmp(action, "reject") == 0) { |
|
/* Change the status of a generated payout to orphaned |
|
* or rejected |
|
* Require payoutid |
|
* Use this if the orphan or reject process didn't |
|
* automatically update a generated payout |
|
* TODO: get orphaned and rejected blocks to automatically do this */ |
|
i_payoutid = require_name(trf_root, "payoutid", 1, |
|
(char *)intpatt, reply, siz); |
|
if (!i_payoutid) |
|
return strdup(reply); |
|
TXT_TO_BIGINT("payoutid", transfer_data(i_payoutid), payoutid); |
|
|
|
K_WLOCK(payouts_free); |
|
p_item = find_payoutid(payoutid); |
|
if (!p_item) { |
|
K_WUNLOCK(payouts_free); |
|
snprintf(reply, siz, |
|
"no payout with id %"PRId64, payoutid); |
|
return strdup(reply); |
|
} |
|
DATA_PAYOUTS(payouts, p_item); |
|
if (!PAYGENERATED(payouts->status)) { |
|
K_WUNLOCK(payouts_free); |
|
snprintf(reply, siz, |
|
"status !generated (%s) for payout %"PRId64, |
|
payouts->status, payoutid); |
|
return strdup(reply); |
|
} |
|
p2_item = k_unlink_head(payouts_free); |
|
K_WUNLOCK(payouts_free); |
|
|
|
/* There is a risk of the p_item changing while it's unlocked, |
|
* but since this is a manual interface it's not really likely |
|
* and there'll be an error if something goes wrong |
|
* It reports the old and new status */ |
|
DATA_PAYOUTS(payouts2, p2_item); |
|
bzero(payouts2, sizeof(*payouts2)); |
|
payouts2->payoutid = payouts->payoutid; |
|
payouts2->height = payouts->height; |
|
STRNCPY(payouts2->blockhash, payouts->blockhash); |
|
payouts2->minerreward = payouts->minerreward; |
|
payouts2->workinfoidstart = payouts->workinfoidstart; |
|
payouts2->workinfoidend = payouts->workinfoidend; |
|
payouts2->elapsed = payouts->elapsed; |
|
if (strcasecmp(action, "orphan") == 0) |
|
STRNCPY(payouts2->status, PAYOUTS_ORPHAN_STR); |
|
else |
|
STRNCPY(payouts2->status, PAYOUTS_REJECT_STR); |
|
payouts2->diffwanted = payouts->diffwanted; |
|
payouts2->diffused = payouts->diffused; |
|
payouts2->shareacc = payouts->shareacc; |
|
copy_tv(&(payouts2->lastshareacc), &(payouts->lastshareacc)); |
|
DUP_POINTER(payouts_free, payouts2->stats, payouts->stats); |
|
|
|
ok = payouts_add(conn, true, p2_item, &old_p2_item, |
|
by, code, inet, now, NULL, false); |
|
if (!ok) { |
|
snprintf(reply, siz, "failed payout %"PRId64, payoutid); |
|
return strdup(reply); |
|
} |
|
// Original was generated, so undo the reward |
|
reward_shifts(payouts2, -1); |
|
DATA_PAYOUTS(payouts2, p2_item); |
|
DATA_PAYOUTS(old_payouts2, old_p2_item); |
|
snprintf(msg, sizeof(msg), |
|
"payout %"PRId64" changed from '%s' to '%s' for " |
|
"%"PRId32"/%s", |
|
payoutid, old_payouts2->status, payouts2->status, |
|
payouts2->height, payouts2->blockhash); |
|
} else if (strcasecmp(action, "expire") == 0) { |
|
/* Expire the payout - effectively deletes it |
|
* Require payoutid |
|
* TODO: If any payments are paid then don't allow it */ |
|
i_payoutid = require_name(trf_root, "payoutid", 1, |
|
(char *)intpatt, reply, siz); |
|
if (!i_payoutid) |
|
return strdup(reply); |
|
TXT_TO_BIGINT("payoutid", transfer_data(i_payoutid), payoutid); |
|
|
|
// payouts_full_expire updates the shift rewards |
|
p_item = payouts_full_expire(conn, payoutid, now, true); |
|
if (!p_item) { |
|
snprintf(reply, siz, "failed payout %"PRId64, payoutid); |
|
return strdup(reply); |
|
} |
|
DATA_PAYOUTS(payouts, p_item); |
|
snprintf(msg, sizeof(msg), |
|
"payout %"PRId64" block %"PRId32" reward %"PRId64 |
|
" status '%s'", |
|
payouts->payoutid, payouts->height, |
|
payouts->minerreward, payouts->status); |
|
} else if (strcasecmp(action, "process") == 0) { |
|
/* Generate a payout |
|
* Require height, blockhash and addrdate |
|
* addrdate is an epoch integer |
|
* and 0 means uses the default = block NEW createdate |
|
* this is the date to use to determine payoutaddresses |
|
* Check the console for processing messages */ |
|
i_height = require_name(trf_root, "height", 6, |
|
(char *)intpatt, reply, siz); |
|
if (!i_height) |
|
return strdup(reply); |
|
TXT_TO_INT("height", transfer_data(i_height), height); |
|
|
|
i_blockhash = require_name(trf_root, "blockhash", 64, |
|
(char *)hashpatt, reply, siz); |
|
if (!i_blockhash) |
|
return strdup(reply); |
|
TXT_TO_STR("blockhash", transfer_data(i_blockhash), blockhash); |
|
|
|
i_addrdate = require_name(trf_root, "addrdate", 1, |
|
(char *)intpatt, reply, siz); |
|
if (!i_addrdate) |
|
return strdup(reply); |
|
TXT_TO_CTV("addrdate", transfer_data(i_addrdate), addrdate); |
|
|
|
// process_pplns updates the shift rewards |
|
if (addrdate.tv_sec == 0) |
|
ok = process_pplns(height, blockhash, NULL); |
|
else |
|
ok = process_pplns(height, blockhash, &addrdate); |
|
|
|
} else if (strcasecmp(action, "genon") == 0) { |
|
/* Turn on auto payout generation |
|
* and report the before/after status |
|
* No parameters */ |
|
bool old = genpayout_auto; |
|
genpayout_auto = true; |
|
snprintf(msg, sizeof(msg), "payout generation state was %s," |
|
" now %s", |
|
old ? "On" : "Off", |
|
genpayout_auto ? "On" : "Off"); |
|
ok = true; |
|
} else if (strcasecmp(action, "genoff") == 0) { |
|
/* Turn off auto payout generation |
|
* and report the before/after status |
|
* No parameters */ |
|
bool old = genpayout_auto; |
|
genpayout_auto = false; |
|
snprintf(msg, sizeof(msg), "payout generation state was %s," |
|
" now %s", |
|
old ? "On" : "Off", |
|
genpayout_auto ? "On" : "Off"); |
|
ok = true; |
|
} else { |
|
snprintf(reply, siz, "unknown action '%s'", action); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
snprintf(reply, siz, "%s.%s%s%s", |
|
ok ? "ok" : "ERR", |
|
action, |
|
msg[0] ? " " : EMPTY, |
|
msg[0] ? msg : EMPTY); |
|
LOGWARNING("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
static char *cmd_mpayouts(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, |
|
__maybe_unused K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_username, *u_item, *mp_item, *po_item; |
|
K_TREE_CTX ctx[1]; |
|
MININGPAYOUTS *mp; |
|
PAYOUTS *payouts; |
|
USERS *users; |
|
char reply[1024] = ""; |
|
char tmp[1024]; |
|
size_t siz = sizeof(reply); |
|
char *buf; |
|
size_t len, off; |
|
int rows; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = adminuser(trf_root, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) |
|
return strdup("bad"); |
|
DATA_USERS(users, u_item); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
rows = 0; |
|
K_RLOCK(payouts_free); |
|
po_item = last_in_ktree(payouts_root, ctx); |
|
DATA_PAYOUTS_NULL(payouts, po_item); |
|
/* TODO: allow to see details of a single payoutid |
|
* if it has multiple items (percent payout user) */ |
|
while (po_item) { |
|
if (CURRENT(&(payouts->expirydate)) && |
|
PAYGENERATED(payouts->status)) { |
|
K_RLOCK(miningpayouts_free); |
|
mp_item = find_miningpayouts(payouts->payoutid, |
|
users->userid); |
|
if (mp_item) { |
|
DATA_MININGPAYOUTS(mp, mp_item); |
|
|
|
bigint_to_buf(payouts->payoutid, reply, |
|
sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "payoutid:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
int_to_buf(payouts->height, reply, |
|
sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "height:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"block"CDTRF":%d=%ld%c", rows, |
|
payouts->blockcreatedate.tv_sec, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
bigint_to_buf(payouts->elapsed, reply, |
|
sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "elapsed:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
bigint_to_buf(mp->amount, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "amount:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(mp->diffacc, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "diffacc:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
bigint_to_buf(payouts->minerreward, reply, |
|
sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "minerreward:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(payouts->diffused, reply, |
|
sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "diffused:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
str_to_buf(payouts->status, reply, |
|
sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "status:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
rows++; |
|
} |
|
K_RUNLOCK(miningpayouts_free); |
|
} |
|
po_item = prev_in_ktree(ctx); |
|
DATA_PAYOUTS_NULL(payouts, po_item); |
|
} |
|
K_RUNLOCK(payouts_free); |
|
|
|
snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", |
|
rows, FLDSEP, |
|
"payoutid,height,block"CDTRF",elapsed,amount,diffacc,minerreward,diffused,status", |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "MiningPayouts", FLDSEP, ""); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); |
|
return buf; |
|
} |
|
|
|
typedef struct worker_match { |
|
char *worker; |
|
bool match; |
|
size_t len; |
|
bool used; |
|
bool everused; |
|
} WM; |
|
|
|
static char *worker_offset(char *workername) |
|
{ |
|
char *c1, *c2; |
|
|
|
/* Find the start of the workername including the SEP */ |
|
c1 = strchr(workername, WORKSEP1); |
|
c2 = strchr(workername, WORKSEP2); |
|
if (c1 || c2) { |
|
if (!c1 || (c1 && c2 && (c2 < c1))) |
|
c1 = c2; |
|
} |
|
// No workername after the username |
|
if (!c1) |
|
c1 = WORKERS_EMPTY; |
|
|
|
return c1; |
|
} |
|
|
|
/* Some arbitrarily large limit, increase it if needed |
|
(doesn't need to be very large) */ |
|
#define SELECT_LIMIT 63 |
|
|
|
/* select is a string of workernames separated by WORKERS_SEL_SEP |
|
* Setup the wm array of workers with select broken up |
|
* The wm array is terminated by workers = NULL |
|
* and will have 0 elements if select is NULL/empty |
|
* The count of the first occurrence of WORKERS_ALL is returned, |
|
* or -1 if WORKERS_ALL isn't found */ |
|
static int select_list(WM *wm, char *select) |
|
{ |
|
int count, all_count = -1; |
|
size_t len, offset; |
|
char *end; |
|
|
|
if (select == NULL || *select == '\0') |
|
return all_count; |
|
|
|
len = strlen(select); |
|
count = 0; |
|
offset = 0; |
|
while (offset < len) { |
|
if (select[offset] == WORKERS_SEL_SEP) |
|
offset++; |
|
else { |
|
wm[count].worker = select + offset; |
|
wm[count+1].worker = NULL; |
|
end = strchr(wm[count].worker, WORKERS_SEL_SEP); |
|
if (end != NULL) { |
|
offset = 1 + end - select; |
|
*end = '\0'; |
|
} |
|
|
|
if (all_count == -1 && |
|
strcasecmp(wm[count].worker, WORKERS_ALL) == 0) { |
|
all_count = count; |
|
} |
|
|
|
if (end == NULL || ++count > SELECT_LIMIT) |
|
break; |
|
} |
|
} |
|
return all_count; |
|
} |
|
|
|
static char *cmd_shifts(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_username, *i_select; |
|
K_ITEM *u_item, *p_item, *m_item, ms_look, *wm_item, *ms_item, *wi_item; |
|
K_TREE_CTX wm_ctx[1], ms_ctx[1]; |
|
WORKMARKERS *wm; |
|
WORKINFO *wi; |
|
MARKERSUMMARY markersummary, *ms, ms_add[SELECT_LIMIT+1]; |
|
PAYOUTS *payouts; |
|
USERS *users; |
|
MARKS *marks = NULL; |
|
char reply[1024] = ""; |
|
char tmp[1024]; |
|
size_t siz = sizeof(reply); |
|
char *select = NULL; |
|
WM workm[SELECT_LIMIT+1]; |
|
char *buf = NULL, *work, *st = NULL; |
|
size_t len, off; |
|
tv_t marker_end = { 0L, 0L }; |
|
int rows, want, i, where_all; |
|
int64_t maxrows; |
|
double wm_count, d; |
|
int64_t last_payout_start = 0; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = adminuser(trf_root, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) |
|
return strdup("bad"); |
|
DATA_USERS(users, u_item); |
|
|
|
maxrows = user_sys_setting(users->userid, SHIFTS_SETTING_NAME, |
|
SHIFTS_DEFAULT, now); |
|
|
|
K_RLOCK(payouts_free); |
|
p_item = find_last_payouts(); |
|
K_RUNLOCK(payouts_free); |
|
if (p_item) { |
|
DATA_PAYOUTS(payouts, p_item); |
|
wm_count = payout_stats(payouts, "wm_count"); |
|
wm_count *= 1.42; |
|
if (maxrows < wm_count) |
|
maxrows = wm_count; |
|
last_payout_start = payouts->workinfoidstart; |
|
} |
|
|
|
i_select = optional_name(trf_root, "select", 1, NULL, reply, siz); |
|
if (i_select) |
|
select = strdup(transfer_data(i_select)); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
snprintf(tmp, sizeof(tmp), " select='%s'", |
|
select ? st = safe_text_nonull(select) : "null"); |
|
FREENULL(st); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
bzero(workm, sizeof(workm)); |
|
where_all = select_list(&(workm[0]), select); |
|
// Nothing selected = all |
|
if (workm[0].worker == NULL) { |
|
where_all = 0; |
|
workm[0].worker = WORKERS_ALL; |
|
APPEND_REALLOC(buf, off, len, " no workers"); |
|
} else { |
|
for (i = 0; workm[i].worker; i++) { |
|
// N.B. len is only used if match is true |
|
len = workm[i].len = strlen(workm[i].worker); |
|
// If at least 3 characters and last is '*' |
|
if (len > 2 && workm[i].worker[len-1] == '*') { |
|
workm[i].worker[len-1] = '\0'; |
|
workm[i].match = true; |
|
workm[i].len--; |
|
} |
|
snprintf(tmp, sizeof(tmp), " workm[%d]=%s,%s,%d", |
|
i, st = safe_text_nonull(workm[i].worker), |
|
workm[i].match ? "Y" : "N", |
|
(int)(workm[i].len)); |
|
FREENULL(st); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
} |
|
|
|
if (where_all >= 0) |
|
workm[where_all].used = true; |
|
|
|
snprintf(tmp, sizeof(tmp), " where_all=%d", where_all); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
LOGDEBUG("%s() user=%"PRId64"/%s' %s", |
|
__func__, users->userid, users->username, buf+1); |
|
FREENULL(buf); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
INIT_MARKERSUMMARY(&ms_look); |
|
ms_look.data = (void *)(&markersummary); |
|
rows = 0; |
|
K_RLOCK(workmarkers_free); |
|
wm_item = last_in_ktree(workmarkers_workinfoid_root, wm_ctx); |
|
DATA_WORKMARKERS_NULL(wm, wm_item); |
|
/* TODO: allow to see details of a single payoutid |
|
* if it has multiple items (percent payout user) */ |
|
while (rows < (maxrows - 1) && wm_item) { |
|
if (CURRENT(&(wm->expirydate)) && WMPROCESSED(wm->status)) { |
|
K_RUNLOCK(workmarkers_free); |
|
|
|
K_RLOCK(marks_free); |
|
m_item = find_marks(wm->workinfoidend); |
|
K_RUNLOCK(marks_free); |
|
DATA_MARKS_NULL(marks, m_item); |
|
if (m_item == NULL) { |
|
// Log it but keep going |
|
LOGERR("%s() missing mark for markerid " |
|
"%"PRId64"/%s widend %"PRId64, |
|
__func__, wm->markerid, |
|
wm->description, |
|
wm->workinfoidend); |
|
} |
|
|
|
// Zero everything for this shift |
|
bzero(ms_add, sizeof(ms_add)); |
|
for (i = 0; workm[i].worker; i++) { |
|
if (i != where_all) |
|
workm[i].used = false; |
|
} |
|
|
|
markersummary.markerid = wm->markerid; |
|
markersummary.userid = users->userid; |
|
markersummary.workername = EMPTY; |
|
K_RLOCK(markersummary_free); |
|
ms_item = find_after_in_ktree(markersummary_root, |
|
&ms_look, ms_ctx); |
|
DATA_MARKERSUMMARY_NULL(ms, ms_item); |
|
while (ms_item && ms->markerid == wm->markerid && |
|
ms->userid == users->userid) { |
|
work = worker_offset(ms->workername); |
|
for (want = 0; workm[want].worker; want++) { |
|
if ((want == where_all) || |
|
(workm[want].match && strncmp(work, workm[want].worker, workm[want].len) == 0) || |
|
(!(workm[want].match) && strcmp(workm[want].worker, work) == 0)) { |
|
workm[want].used = true; |
|
workm[want].everused = true; |
|
ms_add[want].diffacc += ms->diffacc; |
|
ms_add[want].diffsta += ms->diffsta; |
|
ms_add[want].diffdup += ms->diffdup; |
|
ms_add[want].diffhi += ms->diffhi; |
|
ms_add[want].diffrej += ms->diffrej; |
|
ms_add[want].shareacc += ms->shareacc; |
|
ms_add[want].sharesta += ms->sharesta; |
|
ms_add[want].sharedup += ms->sharedup; |
|
ms_add[want].sharehi += ms->sharehi; |
|
ms_add[want].sharerej += ms->sharerej; |
|
} |
|
} |
|
ms_item = next_in_ktree(ms_ctx); |
|
DATA_MARKERSUMMARY_NULL(ms, ms_item); |
|
} |
|
K_RUNLOCK(markersummary_free); |
|
|
|
for (i = 0; i <= SELECT_LIMIT; i++) { |
|
if (workm[i].used) { |
|
double_to_buf(ms_add[i].diffacc, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "%d_diffacc:%d=%s%c", |
|
i, rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
d = ms_add[i].diffsta + ms_add[i].diffdup + |
|
ms_add[i].diffhi + ms_add[i].diffrej; |
|
double_to_buf(d, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "%d_diffinv:%d=%s%c", |
|
i, rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(ms_add[i].shareacc, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "%d_shareacc:%d=%s%c", |
|
i, rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
d = ms_add[i].sharesta + ms_add[i].sharedup + |
|
ms_add[i].sharehi + ms_add[i].sharerej; |
|
double_to_buf(d, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "%d_shareinv:%d=%s%c", |
|
i, rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
} |
|
|
|
if (marker_end.tv_sec == 0L) { |
|
wi_item = next_workinfo(wm->workinfoidend, NULL); |
|
if (!wi_item) { |
|
/* There's no workinfo after this shift |
|
* Unexpected ... estimate last wid+30s */ |
|
wi_item = find_workinfo(wm->workinfoidend, NULL); |
|
if (!wi_item) { |
|
// Nothing is currently locked |
|
LOGERR("%s() workmarker %"PRId64"/%s." |
|
" missing widend %"PRId64, |
|
__func__, wm->markerid, |
|
wm->description, |
|
wm->workinfoidend); |
|
snprintf(reply, siz, "data error 1"); |
|
free(buf); |
|
return(strdup(reply)); |
|
} |
|
DATA_WORKINFO(wi, wi_item); |
|
copy_tv(&marker_end, &(wi->createdate)); |
|
marker_end.tv_sec += 30; |
|
} else { |
|
DATA_WORKINFO(wi, wi_item); |
|
copy_tv(&marker_end, &(wi->createdate)); |
|
} |
|
} |
|
|
|
wi_item = find_workinfo(wm->workinfoidstart, NULL); |
|
if (!wi_item) { |
|
// Nothing is currently locked |
|
LOGERR("%s() workmarker %"PRId64"/%s. missing " |
|
"widstart %"PRId64, |
|
__func__, wm->markerid, wm->description, |
|
wm->workinfoidstart); |
|
snprintf(reply, siz, "data error 2"); |
|
free(buf); |
|
return(strdup(reply)); |
|
} |
|
DATA_WORKINFO(wi, wi_item); |
|
|
|
bigint_to_buf(wm->markerid, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "markerid:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
str_to_buf(wm->description, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "shift:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "endmarkextra:%d=%s%c", |
|
rows, |
|
m_item ? marks->extra : EMPTY, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
ftv_to_buf(&(wi->createdate), reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "start:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
ftv_to_buf(&marker_end, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "end:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "rewards:%d=%d%c", |
|
rows, wm->rewards, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
// Use %.15e -> 16 non-leading-zero decimal places |
|
snprintf(tmp, sizeof(tmp), "ppsvalue:%d=%.15f%c", |
|
rows, wm->pps_value, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
// Use %.15e -> 16 non-leading-zero decimal places |
|
snprintf(tmp, sizeof(tmp), "ppsrewarded:%d=%.15e%c", |
|
rows, wm->rewarded, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "lastpayoutstart:%d=%s%c", |
|
rows, |
|
(wm->workinfoidstart == |
|
last_payout_start) ? |
|
"Y" : EMPTY, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
rows++; |
|
|
|
// Setup for next shift |
|
copy_tv(&marker_end, &(wi->createdate)); |
|
|
|
K_RLOCK(workmarkers_free); |
|
} |
|
wm_item = prev_in_ktree(wm_ctx); |
|
DATA_WORKMARKERS_NULL(wm, wm_item); |
|
} |
|
K_RUNLOCK(workmarkers_free); |
|
|
|
for (i = 0; workm[i].worker; i++) { |
|
if (workm[i].everused) { |
|
snprintf(tmp, sizeof(tmp), |
|
"%d_worker=%s%s%c", |
|
i, workm[i].worker, |
|
workm[i].match ? "*" : EMPTY, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"%d_flds=%s%c", i, |
|
"diffacc,diffinv,shareacc,shareinv", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
} |
|
|
|
// Missing if all isn't selected |
|
if (where_all >= 0) { |
|
snprintf(tmp, sizeof(tmp), "prefix_all=%d_%c", |
|
where_all, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
|
|
/* rows is an upper limit of rows in each worker |
|
* 'all' starts at 0 and finishes at rows-1 |
|
* other workers start >= 0 and finish <= rows-1 */ |
|
snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", |
|
rows, FLDSEP, |
|
"markerid,shift,start,end,rewards," |
|
"ppsvalue,ppsrewarded,lastpayoutstart", |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "arn=%s", "Shifts"); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
for (i = 0; workm[i].worker; i++) { |
|
if (workm[i].everused) { |
|
snprintf(tmp, sizeof(tmp), ",Worker_%d", i); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
} |
|
|
|
snprintf(tmp, sizeof(tmp), "%carp=", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
for (i = 0; workm[i].worker; i++) { |
|
if (workm[i].everused) { |
|
snprintf(tmp, sizeof(tmp), ",%d_", i); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
} |
|
|
|
LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); |
|
return(buf); |
|
} |
|
|
|
static char *cmd_dsp(__maybe_unused PGconn *conn, __maybe_unused char *cmd, |
|
char *id, __maybe_unused tv_t *now, |
|
__maybe_unused char *by, __maybe_unused char *code, |
|
__maybe_unused char *inet, __maybe_unused tv_t *notcd, |
|
__maybe_unused K_TREE *trf_root) |
|
{ |
|
__maybe_unused K_ITEM *i_file; |
|
__maybe_unused char reply[1024] = ""; |
|
__maybe_unused size_t siz = sizeof(reply); |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
// WARNING: This is a gaping security hole - only use in development |
|
LOGDEBUG("%s.disabled.dsp", id); |
|
return strdup("disabled.dsp"); |
|
/* |
|
i_file = require_name(trf_root, "file", 1, NULL, reply, siz); |
|
if (!i_file) |
|
return strdup(reply); |
|
|
|
dsp_ktree(blocks_free, blocks_root, transfer_data(i_file), NULL); |
|
|
|
dsp_ktree(transfer_free, trf_root, transfer_data(i_file), NULL); |
|
|
|
dsp_ktree(paymentaddresses_free, paymentaddresses_root, |
|
transfer_data(i_file), NULL); |
|
|
|
dsp_ktree(paymentaddresses_create_free, paymentaddresses_root, |
|
transfer_data(i_file), NULL); |
|
|
|
dsp_ktree(sharesummary_free, sharesummary_root, |
|
transfer_data(i_file), NULL); |
|
|
|
dsp_ktree(userstats_free, userstats_root, |
|
transfer_data(i_file), NULL); |
|
|
|
dsp_ktree(markersummary_free, markersummary_root, |
|
transfer_data(i_file), NULL); |
|
|
|
dsp_ktree(workmarkers_free, workmarkers_root, |
|
transfer_data(i_file), NULL); |
|
|
|
LOGDEBUG("%s.ok.dsp.file='%s'", id, transfer_data(i_file)); |
|
return strdup("ok.dsp"); |
|
*/ |
|
} |
|
|
|
static char *cmd_stats(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, __maybe_unused K_TREE *trf_root) |
|
{ |
|
char tmp[1024], *buf; |
|
const char *name; |
|
size_t len, off; |
|
uint64_t ram, ram2, tot = 0; |
|
K_LIST *klist; |
|
K_LISTS *klists; |
|
int rows = 0; |
|
bool istree; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
|
|
/* All but temporary lists are in klist_all |
|
* All trees are there also since all trees have a node klist */ |
|
ck_wlock(&lock_check_lock); |
|
klists = all_klists; |
|
while (klists) { |
|
klist = klists->klist; |
|
|
|
ram = sizeof(*klist); |
|
if (klist->name == tree_node_list_name) { |
|
ram += sizeof(K_TREE); |
|
istree = true; |
|
name = klist->name2; |
|
} else { |
|
istree = false; |
|
name = klist->name; |
|
} |
|
if (klist->lock) |
|
ram += sizeof(*(klist->lock)); |
|
// List of item lists |
|
ram += klist->item_mem_count * sizeof(*(klist->item_memory)); |
|
// items |
|
ram += klist->total * sizeof(K_ITEM); |
|
// List of data lists |
|
ram += klist->data_mem_count * sizeof(*(klist->data_memory)); |
|
// data |
|
ram += klist->total * klist->siz; |
|
|
|
// stores |
|
ram += klist->stores * sizeof(K_STORE); |
|
|
|
ram2 = klist->ram; |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"name:%d=%s%s%s%cinitial:%d=%d%callocated:%d=%d%c" |
|
"instore:%d=%d%cram:%d=%"PRIu64"%c" |
|
"ram2:%d=%"PRIu64"%ccull:%d=%d%c", |
|
rows, name, istree ? " (tree)" : "", |
|
klist->is_lock_only ? " (lock)" : "", FLDSEP, |
|
rows, klist->allocate, FLDSEP, |
|
rows, klist->total, FLDSEP, |
|
rows, klist->total - klist->count, FLDSEP, |
|
rows, ram, FLDSEP, |
|
rows, ram2, FLDSEP, |
|
rows, klist->cull_count, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
tot += ram + ram2; |
|
rows++; |
|
|
|
klists = klists->next; |
|
} |
|
ck_wunlock(&lock_check_lock); |
|
|
|
snprintf(tmp, sizeof(tmp), "totalram=%"PRIu64"%c", tot, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"rows=%d%cflds=%s%c", |
|
rows, FLDSEP, |
|
"name,initial,allocated,instore,ram,cull", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Stats", FLDSEP, ""); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
LOGDEBUG("%s.ok.%s...", id, cmd); |
|
return buf; |
|
} |
|
|
|
// TODO: add to heartbeat to disable the miner if active and status != "" |
|
static char *cmd_userstatus(PGconn *conn, char *cmd, char *id, tv_t *now, char *by, |
|
char *code, char *inet, __maybe_unused tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
K_ITEM *i_username, *i_userid, *i_status, *u_item; |
|
int64_t userid; |
|
char *status; |
|
USERS *users; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = optional_name(trf_root, "username", MIN_USERNAME, |
|
(char *)userpatt, reply, siz); |
|
i_userid = optional_name(trf_root, "userid", 1, (char *)intpatt, reply, siz); |
|
// Either username or userid |
|
if (!i_username && !i_userid) { |
|
snprintf(reply, siz, "failed.invalid/missing userinfo"); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
// A zero length status re-enables it |
|
i_status = require_name(trf_root, "status", 0, NULL, reply, siz); |
|
if (!i_status) |
|
return strdup(reply); |
|
status = transfer_data(i_status); |
|
|
|
K_RLOCK(users_free); |
|
if (i_username) |
|
u_item = find_users(transfer_data(i_username)); |
|
else { |
|
TXT_TO_BIGINT("userid", transfer_data(i_userid), userid); |
|
u_item = find_userid(userid); |
|
} |
|
K_RUNLOCK(users_free); |
|
|
|
if (!u_item) |
|
ok = false; |
|
else { |
|
ok = users_update(conn, u_item, |
|
NULL, NULL, |
|
NULL, |
|
by, code, inet, now, |
|
trf_root, |
|
status, NULL); |
|
} |
|
|
|
if (!ok) { |
|
LOGERR("%s() %s.failed.DBE", __func__, id); |
|
return strdup("failed.DBE"); |
|
} |
|
DATA_USERS(users, u_item); |
|
snprintf(reply, siz, "ok.updated %"PRId64" %s status %s", |
|
users->userid, |
|
users->username, |
|
status[0] ? "disabled" : "enabled"); |
|
LOGWARNING("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
/* Socket interface to the functions that will be used later to automatically |
|
* create marks, workmarkers and process the workmarkers and sharesummaries |
|
* to generate markersummaries */ |
|
static char *cmd_marks(PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, char *by, |
|
char *code, char *inet, tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
char tmp[1024] = ""; |
|
char msg[1024] = ""; |
|
K_ITEM *i_action, *i_workinfoid, *i_marktype, *i_description; |
|
K_ITEM *i_height, *i_status, *i_extra, *m_item, *b_item, *w_item; |
|
K_ITEM *wm_item, *wm_item_prev, *i_markerid; |
|
WORKINFO *workinfo = NULL; |
|
WORKMARKERS *workmarkers; |
|
K_TREE_CTX ctx[1]; |
|
BLOCKS *blocks; |
|
MARKS *marks; |
|
char *action; |
|
int64_t workinfoid = -1, markerid = -1; |
|
char *marktype; |
|
int32_t height = 0; |
|
char description[TXT_BIG+1] = { '\0' }; |
|
char extra[TXT_BIG+1] = { '\0' }; |
|
char status[TXT_FLAG+1] = { MARK_READY, '\0' }; |
|
bool ok = false, pps; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_action = require_name(trf_root, "action", 1, NULL, reply, siz); |
|
if (!i_action) |
|
return strdup(reply); |
|
action = transfer_data(i_action); |
|
|
|
if (strcasecmp(action, "add") == 0) { |
|
/* Add a mark, -m/genon will automatically do this |
|
* Require marktype |
|
* Require workinfoid for all but 'b' |
|
* If marktype is 'b' or 'p' then require height/block (number) |
|
* If marktype is 'o' or 'f' then require description |
|
* Status optional - default READY */ |
|
i_marktype = require_name(trf_root, "marktype", |
|
1, NULL, |
|
reply, siz); |
|
if (!i_marktype) |
|
return strdup(reply); |
|
marktype = transfer_data(i_marktype); |
|
|
|
if (marktype[0] != MARKTYPE_BLOCK) { |
|
i_workinfoid = require_name(trf_root, "workinfoid", |
|
1, (char *)intpatt, |
|
reply, siz); |
|
if (!i_workinfoid) |
|
return strdup(reply); |
|
TXT_TO_BIGINT("workinfoid", |
|
transfer_data(i_workinfoid), |
|
workinfoid); |
|
} |
|
|
|
switch (marktype[0]) { |
|
case MARKTYPE_BLOCK: |
|
case MARKTYPE_PPLNS: |
|
i_height = require_name(trf_root, |
|
"height", |
|
1, (char *)intpatt, |
|
reply, siz); |
|
if (!i_height) |
|
return strdup(reply); |
|
TXT_TO_INT("height", transfer_data(i_height), |
|
height); |
|
K_RLOCK(blocks_free); |
|
b_item = find_prev_blocks(height+1, NULL); |
|
K_RUNLOCK(blocks_free); |
|
if (b_item) { |
|
DATA_BLOCKS(blocks, b_item); |
|
if (blocks->height != height) |
|
b_item = NULL; |
|
} |
|
if (!b_item) { |
|
snprintf(reply, siz, |
|
"no blocks with height %"PRId32, height); |
|
return strdup(reply); |
|
} |
|
if (marktype[0] == MARKTYPE_BLOCK) |
|
workinfoid = blocks->workinfoid; |
|
|
|
if (!marks_description(description, sizeof(description), |
|
marktype, height, NULL, NULL)) |
|
goto dame; |
|
break; |
|
case MARKTYPE_SHIFT_BEGIN: |
|
case MARKTYPE_SHIFT_END: |
|
snprintf(reply, siz, |
|
"marktype %s not yet handled", |
|
marks_marktype(marktype)); |
|
return strdup(reply); |
|
case MARKTYPE_OTHER_BEGIN: |
|
case MARKTYPE_OTHER_FINISH: |
|
i_description = require_name(trf_root, |
|
"description", |
|
1, NULL, |
|
reply, siz); |
|
if (!i_description) |
|
return strdup(reply); |
|
if (!marks_description(description, sizeof(description), |
|
marktype, height, NULL, |
|
transfer_data(i_description))) |
|
goto dame; |
|
break; |
|
default: |
|
snprintf(reply, siz, |
|
"unknown marktype '%s'", marktype); |
|
return strdup(reply); |
|
} |
|
i_status = optional_name(trf_root, "status", 1, NULL, reply, siz); |
|
if (i_status) { |
|
STRNCPY(status, transfer_data(i_status)); |
|
switch(status[0]) { |
|
case MARK_READY: |
|
case MARK_USED: |
|
case '\0': |
|
break; |
|
default: |
|
snprintf(reply, siz, |
|
"unknown mark status '%s'", status); |
|
return strdup(reply); |
|
} |
|
} |
|
if (workinfoid == -1) { |
|
snprintf(reply, siz, "workinfoid not found"); |
|
return strdup(reply); |
|
} |
|
w_item = find_workinfo(workinfoid, NULL); |
|
if (!w_item) { |
|
snprintf(reply, siz, "invalid workinfoid %"PRId64, |
|
workinfoid); |
|
return strdup(reply); |
|
} |
|
DATA_WORKINFO(workinfo, w_item); |
|
ok = marks_process(conn, true, workinfo->poolinstance, |
|
workinfoid, description, extra, marktype, |
|
status, by, code, inet, cd, trf_root); |
|
} else if (strcasecmp(action, "expire") == 0) { |
|
/* Expire the mark - effectively deletes it |
|
* Require workinfoid */ |
|
i_workinfoid = require_name(trf_root, "workinfoid", 1, (char *)intpatt, reply, siz); |
|
if (!i_workinfoid) |
|
return strdup(reply); |
|
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); |
|
K_RLOCK(marks_free); |
|
m_item = find_marks(workinfoid); |
|
K_RUNLOCK(marks_free); |
|
if (!m_item) { |
|
snprintf(reply, siz, |
|
"unknown current mark with workinfoid %"PRId64, workinfoid); |
|
return strdup(reply); |
|
} |
|
ok = marks_process(conn, false, EMPTY, workinfoid, NULL, |
|
NULL, NULL, NULL, by, code, inet, cd, |
|
trf_root); |
|
} else if (strcasecmp(action, "status") == 0) { |
|
/* Change the status on a mark |
|
* Require workinfoid and status |
|
* N.B. you can cause generate errors if you change the status of a USED marks */ |
|
i_workinfoid = require_name(trf_root, "workinfoid", 1, (char *)intpatt, reply, siz); |
|
if (!i_workinfoid) |
|
return strdup(reply); |
|
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); |
|
K_RLOCK(marks_free); |
|
m_item = find_marks(workinfoid); |
|
K_RUNLOCK(marks_free); |
|
if (!m_item) { |
|
snprintf(reply, siz, |
|
"unknown current mark with workinfoid %"PRId64, workinfoid); |
|
return strdup(reply); |
|
} |
|
DATA_MARKS(marks, m_item); |
|
i_status = require_name(trf_root, "status", 0, NULL, reply, siz); |
|
if (!i_status) |
|
return strdup(reply); |
|
STRNCPY(status, transfer_data(i_status)); |
|
switch(status[0]) { |
|
case MARK_READY: |
|
case MARK_USED: |
|
case '\0': |
|
break; |
|
default: |
|
snprintf(reply, siz, |
|
"unknown mark status '%s'", status); |
|
return strdup(reply); |
|
} |
|
// Unchanged |
|
if (strcmp(status, marks->status) == 0) { |
|
action = "status-unchanged"; |
|
ok = true; |
|
} else { |
|
ok = marks_process(conn, true, marks->poolinstance, |
|
workinfoid, marks->description, |
|
marks->extra, marks->marktype, |
|
status, by, code, inet, cd, |
|
trf_root); |
|
} |
|
} else if (strcasecmp(action, "extra") == 0) { |
|
/* Change the 'extra' description |
|
* Require workinfoid and extra |
|
* If a mark is actually multiple marks with the same |
|
* workinfoid, then we can record the extra info here |
|
* This would be true of each block, once shifts are |
|
* implemented, since the current shift ends when a |
|
* block is found |
|
* This could also be true, very rarely, if the beginning |
|
* of a pplns payout range matched any other mark, |
|
* since the beginning can be any workinfoid */ |
|
i_workinfoid = require_name(trf_root, "workinfoid", 1, (char *)intpatt, reply, siz); |
|
if (!i_workinfoid) |
|
return strdup(reply); |
|
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); |
|
K_RLOCK(marks_free); |
|
m_item = find_marks(workinfoid); |
|
K_RUNLOCK(marks_free); |
|
if (!m_item) { |
|
snprintf(reply, siz, |
|
"unknown current mark with workinfoid %"PRId64, workinfoid); |
|
return strdup(reply); |
|
} |
|
DATA_MARKS(marks, m_item); |
|
i_extra = require_name(trf_root, "extra", 0, NULL, reply, siz); |
|
if (!i_extra) |
|
return strdup(reply); |
|
STRNCPY(extra, transfer_data(i_extra)); |
|
// Unchanged |
|
if (strcmp(extra, marks->extra) == 0) { |
|
action = "extra-unchanged"; |
|
ok = true; |
|
} else { |
|
ok = marks_process(conn, true, marks->poolinstance, |
|
workinfoid, marks->description, |
|
extra, marks->marktype, |
|
status, by, code, inet, cd, |
|
trf_root); |
|
} |
|
} else if (strcasecmp(action, "generate") == 0) { |
|
/* Generate workmarkers, -m/genon will automatically do this |
|
* No parameters */ |
|
tmp[0] = '\0'; |
|
ok = workmarkers_generate(conn, tmp, sizeof(tmp), |
|
by, code, inet, cd, trf_root, true); |
|
if (!ok) { |
|
snprintf(reply, siz, "%s error: %s", action, tmp); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
if (*tmp) { |
|
snprintf(reply, siz, "%s: %s", action, tmp); |
|
LOGWARNING("%s.%s", id, reply); |
|
} |
|
} else if (strcasecmp(action, "expunge") == 0) { |
|
/* Expire all generated workmarkers that aren't PROCESSED |
|
* No parameters |
|
* This exists so we can fix all workmarkers that haven't |
|
* been PROCESSED yet, |
|
* if there was a problem with the marks |
|
* Simply expunge all the workmarkers, correct the marks, |
|
* then generate the workmarkers again |
|
* WARNING - using psql to do the worksummary generation |
|
* will not update the workmarkers status inside ckdb |
|
* so this will expunge those worksummary records also |
|
* You'll need to restart ckdb after using psql */ |
|
int count = 0; |
|
ok = true; |
|
wm_item_prev = NULL; |
|
K_RLOCK(workmarkers_free); |
|
wm_item = last_in_ktree(workmarkers_root, ctx); |
|
K_RUNLOCK(workmarkers_free); |
|
while (wm_item) { |
|
K_RLOCK(workmarkers_free); |
|
wm_item_prev = prev_in_ktree(ctx); |
|
K_RUNLOCK(workmarkers_free); |
|
DATA_WORKMARKERS(workmarkers, wm_item); |
|
if (CURRENT(&(workmarkers->expirydate)) && |
|
!WMPROCESSED(workmarkers->status)) { |
|
ok = workmarkers_process(conn, false, false, |
|
workmarkers->markerid, |
|
NULL, 0, 0, NULL, NULL, by, |
|
code, inet, cd, trf_root); |
|
if (!ok) |
|
break; |
|
count++; |
|
} |
|
wm_item = wm_item_prev; |
|
} |
|
if (ok) { |
|
if (count == 0) { |
|
snprintf(msg, sizeof(msg), |
|
"no unprocessed current workmarkers"); |
|
} else { |
|
snprintf(msg, sizeof(msg), |
|
"%d workmarkers expunged", count); |
|
} |
|
} |
|
} else if (strcasecmp(action, "sum") == 0) { |
|
/* For the last available workmarker, |
|
* summarise it's sharesummaries into markersummaries |
|
* -m/genon will automatically do this |
|
* No parameters */ |
|
ok = make_markersummaries(true, by, code, inet, cd, trf_root); |
|
if (!ok) { |
|
snprintf(reply, siz, "%s failed", action); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
} else if (strcasecmp(action, "ready") == 0) { |
|
/* Mark a processed workmarker as ready |
|
* for fixing problems with markersummaries |
|
* Requires markerid */ |
|
i_markerid = require_name(trf_root, "markerid", 1, (char *)intpatt, reply, siz); |
|
if (!i_markerid) |
|
return strdup(reply); |
|
TXT_TO_BIGINT("markerid", transfer_data(i_markerid), markerid); |
|
K_RLOCK(workmarkers_free); |
|
wm_item = find_workmarkerid(markerid, true, '\0'); |
|
K_RUNLOCK(workmarkers_free); |
|
if (!wm_item) { |
|
snprintf(reply, siz, |
|
"unknown workmarkers with markerid %"PRId64, markerid); |
|
return strdup(reply); |
|
} |
|
DATA_WORKMARKERS(workmarkers, wm_item); |
|
if (!WMPROCESSED(workmarkers->status)) { |
|
snprintf(reply, siz, |
|
"markerid isn't processed %"PRId64, markerid); |
|
return strdup(reply); |
|
} |
|
ok = workmarkers_process(NULL, false, true, markerid, |
|
workmarkers->poolinstance, |
|
workmarkers->workinfoidend, |
|
workmarkers->workinfoidstart, |
|
workmarkers->description, |
|
MARKER_READY_STR, |
|
by, code, inet, cd, trf_root); |
|
if (!ok) { |
|
snprintf(reply, siz, "%s failed", action); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
} else if (strcasecmp(action, "processed") == 0) { |
|
/* Mark a workmarker as processed |
|
* Requires markerid */ |
|
i_markerid = require_name(trf_root, "markerid", 1, (char *)intpatt, reply, siz); |
|
if (!i_markerid) |
|
return strdup(reply); |
|
TXT_TO_BIGINT("markerid", transfer_data(i_markerid), markerid); |
|
K_RLOCK(workmarkers_free); |
|
wm_item = find_workmarkerid(markerid, true, '\0'); |
|
K_RUNLOCK(workmarkers_free); |
|
if (!wm_item) { |
|
snprintf(reply, siz, |
|
"unknown workmarkers with markerid %"PRId64, markerid); |
|
return strdup(reply); |
|
} |
|
DATA_WORKMARKERS(workmarkers, wm_item); |
|
if (WMPROCESSED(workmarkers->status)) { |
|
snprintf(reply, siz, |
|
"already processed markerid %"PRId64, markerid); |
|
return strdup(reply); |
|
} |
|
ok = workmarkers_process(NULL, false, true, markerid, |
|
workmarkers->poolinstance, |
|
workmarkers->workinfoidend, |
|
workmarkers->workinfoidstart, |
|
workmarkers->description, |
|
MARKER_PROCESSED_STR, |
|
by, code, inet, cd, trf_root); |
|
if (!ok) { |
|
snprintf(reply, siz, "%s failed", action); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
} else if (strcasecmp(action, "cancel") == 0) { |
|
/* Cancel(delete) all the markersummaries in a workmarker |
|
* This can only be done if the workmarker isn't processed |
|
* It reports on the console, summary information of the |
|
* markersummaries that were deleted |
|
* |
|
* WARNING ... if you do this after the workmarker has been |
|
* processed, after switching it to ready, there will no |
|
* longer be any matching shares or sharesummaries in ram |
|
* to regenerate the markersummaries, so you'd need to restart |
|
* ckdb to reload the shares to regenerate the markersummaries |
|
* HOWEVER, ckdb wont reload the shares if there is a later |
|
* workmarker that is already processed |
|
* |
|
* To reprocess an already processed workmarker, you'd have |
|
* to firstly turn off auto processing with genoff, then |
|
* change the required workmarker, and all after it, to |
|
* ready, then cancel them all, then finally restart ckdb |
|
* which will reload all the necessary shares and regenerate |
|
* the markersummaries |
|
* Of course if you don't have ALL the necessary shares in |
|
* the CCLs then you'd lose data doing this |
|
* |
|
* SS_to_MS will complain if any markersummaries already exist |
|
* when processing a workmarker |
|
* Normally you would use 'processed' if the markersummaries |
|
* are OK, and just the workmarker failed to be updated to |
|
* processed status |
|
* However, if there is actually something wrong with the |
|
* shift data (markersummaries) you can delete them and they |
|
* will be regenerated |
|
* This will usually only work as expected if the last |
|
* workmarker isn't marked as processed, but somehow there |
|
* are markersummaries for it in the DB, thus the reload |
|
* will reload all the shares for the workmarker then it |
|
* will print a warning every 13s on the console saying |
|
* that it can't process the workmarker |
|
* In this case you would cancel the workmarker then ckdb |
|
* will regenerate it from the shares/sharesummaries in ram |
|
* |
|
* Requires markerid */ |
|
i_markerid = require_name(trf_root, "markerid", 1, (char *)intpatt, reply, siz); |
|
if (!i_markerid) |
|
return strdup(reply); |
|
TXT_TO_BIGINT("markerid", transfer_data(i_markerid), markerid); |
|
K_RLOCK(workmarkers_free); |
|
wm_item = find_workmarkerid(markerid, true, '\0'); |
|
K_RUNLOCK(workmarkers_free); |
|
if (!wm_item) { |
|
snprintf(reply, siz, |
|
"unknown workmarkers with markerid %"PRId64, markerid); |
|
return strdup(reply); |
|
} |
|
DATA_WORKMARKERS(workmarkers, wm_item); |
|
if (WMPROCESSED(workmarkers->status)) { |
|
snprintf(reply, siz, |
|
"can't cancel a processed markerid %"PRId64, |
|
markerid); |
|
return strdup(reply); |
|
} |
|
|
|
ok = delete_markersummaries(NULL, workmarkers); |
|
if (!ok) { |
|
snprintf(reply, siz, "%s failed", action); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
} else if (strcasecmp(action, "genon") == 0) { |
|
/* Turn on auto marker generation and processing |
|
* and report the before/after status |
|
* No parameters */ |
|
bool old = markersummary_auto; |
|
markersummary_auto = true; |
|
snprintf(msg, sizeof(msg), "mark generation state was %s," |
|
" now %s", |
|
old ? "On" : "Off", |
|
markersummary_auto ? "On" : "Off"); |
|
ok = true; |
|
} else if (strcasecmp(action, "genoff") == 0) { |
|
/* Turn off auto marker generation and processing |
|
* and report the before/after status |
|
* No parameters */ |
|
bool old = markersummary_auto; |
|
markersummary_auto = false; |
|
snprintf(msg, sizeof(msg), "mark generation state was %s," |
|
" now %s", |
|
old ? "On" : "Off", |
|
markersummary_auto ? "On" : "Off"); |
|
ok = true; |
|
} else if (strcasecmp(action, "pps") == 0) { |
|
/* Recalculate a shift's rewards/rewarded |
|
* Require markerid */ |
|
i_markerid = require_name(trf_root, "markerid", 1, |
|
(char *)intpatt, reply, siz); |
|
if (!i_markerid) |
|
return strdup(reply); |
|
TXT_TO_BIGINT("markerid", transfer_data(i_markerid), markerid); |
|
|
|
K_RLOCK(workmarkers_free); |
|
wm_item = find_workmarkerid(markerid, false, MARKER_PROCESSED); |
|
K_RUNLOCK(workmarkers_free); |
|
if (!wm_item) { |
|
snprintf(reply, siz, "no markerid %"PRId64, markerid); |
|
return strdup(reply); |
|
} |
|
DATA_WORKMARKERS(workmarkers, wm_item); |
|
pps = shift_rewards(wm_item); |
|
if (pps) { |
|
snprintf(msg, sizeof(msg), |
|
"shift '%s' markerid %"PRId64" rewards %d " |
|
"rewarded %.3e pps %.3e", |
|
workmarkers->description, |
|
workmarkers->markerid, workmarkers->rewards, |
|
workmarkers->rewarded, workmarkers->pps_value); |
|
} else { |
|
snprintf(msg, sizeof(msg), |
|
"shift '%s' markerid %"PRId64" no rewards yet" |
|
" pps %.3e", |
|
workmarkers->description, |
|
workmarkers->markerid, workmarkers->pps_value); |
|
} |
|
ok = true; |
|
} else { |
|
snprintf(reply, siz, "unknown action '%s'", action); |
|
LOGERR("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
if (!ok) { |
|
dame: |
|
LOGERR("%s() %s.failed.DBE", __func__, id); |
|
return strdup("failed.DBE"); |
|
} |
|
if (msg[0]) |
|
snprintf(reply, siz, "ok.%s %s", action, msg); |
|
else |
|
snprintf(reply, siz, "ok.%s", action); |
|
LOGWARNING("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
// Layout the reply like cmd_shifts so the php/js code is similar |
|
static char *cmd_pshift(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_username; |
|
K_ITEM *u_item, *p_item, *m_item, *wm_item, *ms_item, *wi_item; |
|
K_TREE_CTX wm_ctx[1]; |
|
WORKMARKERS *wm; |
|
WORKINFO *wi; |
|
MARKERSUMMARY *ms; |
|
PAYOUTS *payouts; |
|
USERS *users; |
|
MARKS *marks = NULL; |
|
char reply[1024] = ""; |
|
char tmp[1024]; |
|
size_t siz = sizeof(reply); |
|
char *buf; |
|
size_t len, off; |
|
tv_t marker_end = { 0L, 0L }; |
|
int rows; |
|
int64_t maxrows; |
|
double wm_count, d; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = adminuser(trf_root, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) |
|
return strdup("bad"); |
|
DATA_USERS(users, u_item); |
|
|
|
maxrows = user_sys_setting(users->userid, SHIFTS_SETTING_NAME, |
|
SHIFTS_DEFAULT, now); |
|
|
|
K_RLOCK(payouts_free); |
|
p_item = find_last_payouts(); |
|
K_RUNLOCK(payouts_free); |
|
if (p_item) { |
|
DATA_PAYOUTS(payouts, p_item); |
|
wm_count = payout_stats(payouts, "wm_count"); |
|
wm_count *= 1.42; |
|
if (maxrows < wm_count) |
|
maxrows = wm_count; |
|
} |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
rows = 0; |
|
K_RLOCK(workmarkers_free); |
|
wm_item = last_in_ktree(workmarkers_workinfoid_root, wm_ctx); |
|
DATA_WORKMARKERS_NULL(wm, wm_item); |
|
while (rows < (maxrows - 1) && wm_item) { |
|
if (CURRENT(&(wm->expirydate)) && WMPROCESSED(wm->status)) { |
|
K_RUNLOCK(workmarkers_free); |
|
|
|
K_RLOCK(marks_free); |
|
m_item = find_marks(wm->workinfoidend); |
|
K_RUNLOCK(marks_free); |
|
DATA_MARKS_NULL(marks, m_item); |
|
if (m_item == NULL) { |
|
// Log it but keep going |
|
LOGERR("%s() missing mark for markerid " |
|
"%"PRId64"/%s widend %"PRId64, |
|
__func__, wm->markerid, |
|
wm->description, |
|
wm->workinfoidend); |
|
} |
|
|
|
K_RLOCK(markersummary_free); |
|
K_RLOCK(workmarkers_free); |
|
ms_item = find_markersummary_p(wm->markerid); |
|
K_RUNLOCK(workmarkers_free); |
|
K_RUNLOCK(markersummary_free); |
|
if (ms_item) { |
|
DATA_MARKERSUMMARY(ms, ms_item); |
|
double_to_buf(ms->diffacc, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "%d_diffacc:%d=%s%c", |
|
0, rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
d = ms->diffsta + ms->diffdup + ms->diffhi + |
|
ms->diffrej; |
|
double_to_buf(d, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "%d_diffinv:%d=%s%c", |
|
0, rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(ms->shareacc, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "%d_shareacc:%d=%s%c", |
|
0, rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
d = ms->sharesta + ms->sharedup + ms->sharehi + |
|
ms->sharerej; |
|
double_to_buf(d, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "%d_shareinv:%d=%s%c", |
|
0, rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
|
|
if (marker_end.tv_sec == 0L) { |
|
wi_item = next_workinfo(wm->workinfoidend, NULL); |
|
if (!wi_item) { |
|
/* There's no workinfo after this shift |
|
* Unexpected ... estimate last wid+30s */ |
|
wi_item = find_workinfo(wm->workinfoidend, NULL); |
|
if (!wi_item) { |
|
// Nothing is currently locked |
|
LOGERR("%s() workmarker %"PRId64"/%s." |
|
" missing widend %"PRId64, |
|
__func__, wm->markerid, |
|
wm->description, |
|
wm->workinfoidend); |
|
snprintf(reply, siz, "data error 1"); |
|
free(buf); |
|
return(strdup(reply)); |
|
} |
|
DATA_WORKINFO(wi, wi_item); |
|
copy_tv(&marker_end, &(wi->createdate)); |
|
marker_end.tv_sec += 30; |
|
} else { |
|
DATA_WORKINFO(wi, wi_item); |
|
copy_tv(&marker_end, &(wi->createdate)); |
|
} |
|
} |
|
|
|
wi_item = find_workinfo(wm->workinfoidstart, NULL); |
|
if (!wi_item) { |
|
// Nothing is currently locked |
|
LOGERR("%s() workmarker %"PRId64"/%s. missing " |
|
"widstart %"PRId64, |
|
__func__, wm->markerid, wm->description, |
|
wm->workinfoidstart); |
|
snprintf(reply, siz, "data error 2"); |
|
free(buf); |
|
return(strdup(reply)); |
|
} |
|
DATA_WORKINFO(wi, wi_item); |
|
|
|
bigint_to_buf(wm->markerid, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "markerid:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
str_to_buf(wm->description, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "shift:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "endmarkextra:%d=%s%c", |
|
rows, |
|
m_item ? marks->extra : EMPTY, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
ftv_to_buf(&(wi->createdate), reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "start:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
ftv_to_buf(&marker_end, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "end:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
rows++; |
|
|
|
// Setup for next shift |
|
copy_tv(&marker_end, &(wi->createdate)); |
|
|
|
K_RLOCK(workmarkers_free); |
|
} |
|
wm_item = prev_in_ktree(wm_ctx); |
|
DATA_WORKMARKERS_NULL(wm, wm_item); |
|
} |
|
K_RUNLOCK(workmarkers_free); |
|
|
|
snprintf(tmp, sizeof(tmp), "%d_pool=%s%c", 0, "all", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "%d_flds=%s%c", |
|
0, "diffacc,diffinv,shareacc,shareinv", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "prefix_all=%d_%c", 0, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", |
|
rows, FLDSEP, |
|
"markerid,shift,start,end", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "arn=%s", "Pool Shifts"); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), ",Pool_%d", 0); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "%carp=", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), ",%d_", 0); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); |
|
return(buf); |
|
} |
|
|
|
/* Show a share status report on the console |
|
* Currently: sequence status and OoO info */ |
|
static char *cmd_shsta(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, __maybe_unused K_TREE *trf_root) |
|
{ |
|
char ooo_buf[256]; |
|
char buf[256]; |
|
|
|
LOGWARNING("OoO %s", ooo_status(ooo_buf, sizeof(ooo_buf))); |
|
sequence_report(true); |
|
|
|
snprintf(buf, sizeof(buf), "ok.%s", cmd); |
|
LOGDEBUG("%s.%s", id, buf); |
|
return strdup(buf); |
|
} |
|
|
|
static char *cmd_userinfo(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, |
|
__maybe_unused K_TREE *trf_root) |
|
{ |
|
K_ITEM *ui_item; |
|
USERINFO *userinfo; |
|
char reply[1024] = ""; |
|
char tmp[1024]; |
|
size_t len, off; |
|
double d; |
|
char *buf; |
|
int rows; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
|
|
rows = 0; |
|
K_RLOCK(userinfo_free); |
|
ui_item = STORE_RHEAD(userinfo_store); |
|
while (ui_item) { |
|
DATA_USERINFO(userinfo, ui_item); |
|
|
|
str_to_buf(userinfo->username, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "username:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "blocks:%d=%d%c", rows, |
|
userinfo->blocks - |
|
(userinfo->orphans + userinfo->rejects), |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "orphans:%d=%d%c", rows, |
|
userinfo->orphans, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(userinfo->diffacc, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "diffacc:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
d = userinfo->diffsta + userinfo->diffdup + userinfo->diffhi + |
|
userinfo->diffrej; |
|
double_to_buf(d, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "diffinv:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(userinfo->shareacc, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "shareacc:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
d = userinfo->sharesta + userinfo->sharedup + userinfo->sharehi + |
|
userinfo->sharerej; |
|
double_to_buf(d, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "shareinv:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "lastblock=%ld%c", |
|
userinfo->last_block.tv_sec, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
rows++; |
|
ui_item = ui_item->next; |
|
} |
|
K_RUNLOCK(userinfo_free); |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"rows=%d%cflds=%s%c", |
|
rows, FLDSEP, |
|
"username,blocks,orphans,diffacc,diffinv,shareacc,shareinv," |
|
"lastblock", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=", "UserInfo", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
LOGDEBUG("%s.ok.%d_rows", id, rows); |
|
return buf; |
|
} |
|
|
|
/* Set/show the BTC server settings |
|
* You must supply the btcserver to change anything |
|
* The format for userpass is username:password |
|
* If you don't supply the btcserver it will simply report the current server |
|
* If you supply btcserver but not the userpass it will use the current userpass |
|
* The reply will ONLY contain the URL, not the user/pass */ |
|
static char *cmd_btcset(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *notcd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_btcserver, *i_userpass; |
|
char *btcserver = NULL, *userpass = NULL, *tmp; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
char buf[256]; |
|
|
|
i_btcserver = optional_name(trf_root, "btcserver", 1, NULL, reply, siz); |
|
if (i_btcserver) { |
|
btcserver = strdup(transfer_data(i_btcserver)); |
|
i_userpass = optional_name(trf_root, "userpass", 0, NULL, reply, siz); |
|
if (i_userpass) |
|
userpass = transfer_data(i_userpass); |
|
|
|
ck_wlock(&btc_lock); |
|
btc_server = btcserver; |
|
btcserver = NULL; |
|
if (userpass) { |
|
if (btc_auth) { |
|
tmp = btc_auth; |
|
while (*tmp) |
|
*(tmp++) = '\0'; |
|
} |
|
FREENULL(btc_auth); |
|
btc_auth = http_base64(userpass); |
|
} |
|
ck_wunlock(&btc_lock); |
|
|
|
if (userpass) { |
|
tmp = userpass; |
|
while (*tmp) |
|
*(tmp++) = '\0'; |
|
} |
|
} |
|
|
|
FREENULL(btcserver); |
|
|
|
ck_wlock(&btc_lock); |
|
snprintf(buf, sizeof(buf), "ok.btcserver=%s", btc_server); |
|
ck_wunlock(&btc_lock); |
|
LOGDEBUG("%s.%s.%s", id, cmd, buf); |
|
return strdup(buf); |
|
} |
|
|
|
/* Query CKDB for certain information |
|
* See each string compare below of 'request' for the list of queries |
|
* For non-error conditions, rows=0 means there were no matching results |
|
* for the request, and rows=n is placed last in the reply */ |
|
static char *cmd_query(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *cd, K_TREE *trf_root) |
|
{ |
|
K_TREE_CTX ctx[1]; |
|
char cd_buf[DATE_BUFSIZ]; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
char tmp[1024] = ""; |
|
char msg[1024] = ""; |
|
char *buf = NULL; |
|
size_t len, off; |
|
K_ITEM *i_request; |
|
char *request; |
|
bool ok = false; |
|
int rows = 0; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_request = require_name(trf_root, "request", 1, NULL, reply, siz); |
|
if (!i_request) |
|
return strdup(reply); |
|
request = transfer_data(i_request); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
|
|
if (strcasecmp(request, "block") == 0) { |
|
/* return DB information for the blocks with height=value |
|
* if expired= is present, it will also return expired records */ |
|
K_ITEM *i_height, *i_expired, *b_item; |
|
bool expired = false; |
|
BLOCKS *blocks; |
|
int32_t height; |
|
|
|
i_height = require_name(trf_root, "height", |
|
1, (char *)intpatt, |
|
reply, siz); |
|
if (!i_height) |
|
return strdup(reply); |
|
TXT_TO_INT("height", transfer_data(i_height), height); |
|
|
|
i_expired = optional_name(trf_root, "expired", |
|
0, NULL, reply, siz); |
|
if (i_expired) |
|
expired = true; |
|
|
|
int_to_buf(height, reply, sizeof(reply)); |
|
snprintf(msg, sizeof(msg), "height=%s", reply); |
|
|
|
K_RLOCK(blocks_free); |
|
b_item = find_prev_blocks(height, ctx); |
|
DATA_BLOCKS_NULL(blocks, b_item); |
|
while (b_item && blocks->height <= height) { |
|
if ((expired || CURRENT(&(blocks->expirydate))) && |
|
blocks->height == height) { |
|
int_to_buf(blocks->height, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), |
|
"height:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"blockhash:%d=%s%c", |
|
rows, blocks->blockhash, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"confirmed:%d=%s%c", |
|
rows, blocks->confirmed, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
tv_to_buf(&(blocks->expirydate), cd_buf, |
|
sizeof(cd_buf)); |
|
snprintf(tmp, sizeof(tmp), |
|
EDDB"_str:%d=%s%c", |
|
rows, cd_buf, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
tv_to_buf(&(blocks->createdate), cd_buf, |
|
sizeof(cd_buf)); |
|
snprintf(tmp, sizeof(tmp), |
|
CDDB"_str:%d=%s%c", |
|
rows, cd_buf, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
tv_to_buf(&(blocks->blockcreatedate), cd_buf, |
|
sizeof(cd_buf)); |
|
snprintf(tmp, sizeof(tmp), |
|
"block"CDDB"_str:%d=%s%c", |
|
rows, cd_buf, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
bigint_to_buf(blocks->workinfoid, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), |
|
"workinfoid:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
rows++; |
|
} |
|
b_item = next_in_ktree(ctx); |
|
DATA_BLOCKS_NULL(blocks, b_item); |
|
} |
|
K_RUNLOCK(blocks_free); |
|
|
|
snprintf(tmp, sizeof(tmp), "flds=%s%c", |
|
"height,blockhash,confirmed,"EDDB"_str," |
|
CDDB"_str,block"CDDB"_str,workinfoid", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s%c", |
|
"Blocks", FLDSEP, "", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
ok = true; |
|
} else if (strcasecmp(request, "workinfo") == 0) { |
|
/* return DB information for the workinfo with wid=value |
|
* if expired= is present, it will also return expired records |
|
* though ckdb doesn't expire workinfo records - only external |
|
* pgsql scripts would do that to the DB, then ckdb would |
|
* load them the next time it (re)starts */ |
|
K_ITEM *i_wid, *i_expired, *wi_item, *wm_item; |
|
bool expired = false; |
|
WORKINFO *workinfo; |
|
WORKMARKERS *wm; |
|
int64_t wid; |
|
|
|
i_wid = require_name(trf_root, "wid", |
|
1, (char *)intpatt, |
|
reply, siz); |
|
if (!i_wid) |
|
return strdup(reply); |
|
TXT_TO_BIGINT("wid", transfer_data(i_wid), wid); |
|
|
|
i_expired = optional_name(trf_root, "expired", |
|
0, NULL, reply, siz); |
|
if (i_expired) |
|
expired = true; |
|
|
|
bigint_to_buf(wid, reply, sizeof(reply)); |
|
snprintf(msg, sizeof(msg), "wid=%s", reply); |
|
|
|
/* We look for the 'next' (or last) workinfo then go backwards |
|
* to ensure we find all expired records in case they |
|
* were requested */ |
|
K_RLOCK(workinfo_free); |
|
wi_item = next_workinfo(wid, ctx); |
|
if (!wi_item) |
|
wi_item = last_in_ktree(workinfo_root, ctx); |
|
DATA_WORKINFO_NULL(workinfo, wi_item); |
|
while (wi_item && workinfo->workinfoid >= wid) { |
|
if ((expired || CURRENT(&(workinfo->expirydate))) && |
|
workinfo->workinfoid == wid) { |
|
bigint_to_buf(workinfo->workinfoid, |
|
reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), |
|
"workinfoid:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
int_to_buf(workinfo->height, |
|
reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), |
|
"height:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"prevhash:%d=%s%c", |
|
rows, workinfo->prevhash, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
tv_to_buf(&(workinfo->expirydate), cd_buf, |
|
sizeof(cd_buf)); |
|
snprintf(tmp, sizeof(tmp), |
|
EDDB"_str:%d=%s%c", |
|
rows, cd_buf, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
tv_to_buf(&(workinfo->createdate), cd_buf, |
|
sizeof(cd_buf)); |
|
snprintf(tmp, sizeof(tmp), |
|
CDDB"_str:%d=%s%c", |
|
rows, cd_buf, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"ndiff:%d=%.1f%c", rows, |
|
workinfo->diff_target, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"ppsvalue:%d=%.15f%c", rows, |
|
workinfo_pps(wi_item, |
|
workinfo->workinfoid), |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
K_RLOCK(workmarkers_free); |
|
wm_item = find_workmarkers(wid, false, |
|
MARKER_PROCESSED, |
|
NULL); |
|
K_RUNLOCK(workmarkers_free); |
|
if (!wm_item) { |
|
snprintf(tmp, sizeof(tmp), |
|
"markerid:%d=%c", rows, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"shift:%d=%c", rows, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"shiftend:%d=%c", rows, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"shiftstart:%d=%c", rows, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} else { |
|
DATA_WORKMARKERS(wm, wm_item); |
|
bigint_to_buf(wm->markerid, |
|
reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), |
|
"markerid:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"shift:%d=%s%c", |
|
rows, wm->description, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
bigint_to_buf(wm->workinfoidend, |
|
reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), |
|
"shiftend:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
bigint_to_buf(wm->workinfoidstart, |
|
reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), |
|
"shiftstart:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
rows++; |
|
} |
|
wi_item = prev_in_ktree(ctx); |
|
DATA_WORKINFO_NULL(workinfo, wi_item); |
|
} |
|
K_RUNLOCK(workinfo_free); |
|
|
|
snprintf(tmp, sizeof(tmp), "flds=%s%c", |
|
"workinfoid,height,prevhash,"EDDB"_str,"CDDB"_str," |
|
"ndiff,ppsvalue,markerid,shift,shiftend,shiftstart", |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s%c", |
|
"Workinfo", FLDSEP, "", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
ok = true; |
|
} else if (strcasecmp(request, "range") == 0) { |
|
/* Return the workinfoid range that has block height=height |
|
* WARNING! This will traverse workinfo from the end back to |
|
* the given height, and thus since workinfo is the 2nd |
|
* largest tree, it may access swapped data if you request |
|
* older data */ |
|
K_ITEM *i_height, *wi_item; |
|
WORKINFO *workinfo; |
|
int32_t height, this_height; |
|
int64_t idend, idstt; |
|
|
|
i_height = require_name(trf_root, "height", |
|
1, (char *)intpatt, |
|
reply, siz); |
|
if (!i_height) |
|
return strdup(reply); |
|
TXT_TO_INT("height", transfer_data(i_height), height); |
|
|
|
int_to_buf(height, reply, sizeof(reply)); |
|
snprintf(msg, sizeof(msg), "height=%s", reply); |
|
|
|
idend = idstt = 0L; |
|
/* Start from the last workinfo and continue until we get |
|
* below block 'height' */ |
|
K_RLOCK(workinfo_free); |
|
wi_item = last_in_ktree(workinfo_root, ctx); |
|
DATA_WORKINFO_NULL(workinfo, wi_item); |
|
while (wi_item) { |
|
this_height = workinfo->height; |
|
if (this_height < height) |
|
break; |
|
if (CURRENT(&(workinfo->expirydate)) && |
|
this_height == height) { |
|
if (idend == 0L) |
|
idend = workinfo->workinfoid; |
|
idstt = workinfo->workinfoid; |
|
} |
|
wi_item = prev_in_ktree(ctx); |
|
DATA_WORKINFO_NULL(workinfo, wi_item); |
|
} |
|
K_RUNLOCK(workinfo_free); |
|
|
|
int_to_buf(height, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "height:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
bigint_to_buf(idend, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "workinfoidend:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
bigint_to_buf(idstt, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "workinfoidstart:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
rows++; |
|
|
|
snprintf(tmp, sizeof(tmp), "flds=%s%c", |
|
"height,workinfoidend,workinfoidstart", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s%c", |
|
"WorkinfoRange", FLDSEP, "", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
ok = true; |
|
} else if (strcasecmp(request, "diff") == 0) { |
|
/* return the details of the next diff change after |
|
* block height=height |
|
* WARNING! This will traverse workinfo from the end back to |
|
* the given height, and thus since workinfo is the 2nd |
|
* largest tree, it may access swapped data if you request |
|
* older data */ |
|
K_ITEM *i_height, *wi_item; |
|
WORKINFO *workinfo = NULL; |
|
int32_t height, this_height; |
|
char bits[TXT_SML+1]; |
|
bool got = false; |
|
|
|
i_height = require_name(trf_root, "height", |
|
1, (char *)intpatt, |
|
reply, siz); |
|
if (!i_height) |
|
return strdup(reply); |
|
TXT_TO_INT("height", transfer_data(i_height), height); |
|
|
|
int_to_buf(height, reply, sizeof(reply)); |
|
snprintf(msg, sizeof(msg), "height=%s", reply); |
|
|
|
snprintf(tmp, sizeof(tmp), "height0:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
/* Start from the last workinfo and continue until we get |
|
* below block 'height' */ |
|
K_RLOCK(workinfo_free); |
|
wi_item = last_in_ktree(workinfo_root, ctx); |
|
DATA_WORKINFO_NULL(workinfo, wi_item); |
|
while (wi_item) { |
|
if (CURRENT(&(workinfo->expirydate))) { |
|
this_height = workinfo->height; |
|
if (this_height < height) |
|
break; |
|
} |
|
wi_item = prev_in_ktree(ctx); |
|
DATA_WORKINFO_NULL(workinfo, wi_item); |
|
} |
|
// If we fell off the front use the first one |
|
if (!wi_item) |
|
wi_item = first_in_ktree(workinfo_root, ctx); |
|
DATA_WORKINFO_NULL(workinfo, wi_item); |
|
while (wi_item) { |
|
if (CURRENT(&(workinfo->expirydate))) { |
|
this_height = workinfo->height; |
|
if (this_height >= height) |
|
break; |
|
} |
|
wi_item = next_in_ktree(ctx); |
|
DATA_WORKINFO_NULL(workinfo, wi_item); |
|
} |
|
if (wi_item) { |
|
DATA_WORKINFO(workinfo, wi_item); |
|
this_height = workinfo->height; |
|
if (this_height == height) { |
|
// We have our starting point |
|
STRNCPY(bits, workinfo->bits); |
|
got = true; |
|
|
|
bigint_to_buf(workinfo->workinfoid, |
|
reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), |
|
"workinfoid0:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"ndiff0:%d=%.1f%c", rows, |
|
workinfo->diff_target, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
while (wi_item) { |
|
if (CURRENT(&(workinfo->expirydate))) { |
|
if (strcmp(bits, workinfo->bits) != 0) |
|
break; |
|
} |
|
wi_item = next_in_ktree(ctx); |
|
DATA_WORKINFO_NULL(workinfo, wi_item); |
|
} |
|
} else |
|
wi_item = NULL; |
|
} |
|
K_RUNLOCK(workinfo_free); |
|
|
|
if (!got) { |
|
snprintf(tmp, sizeof(tmp), "workinfoid0:%d=%c", |
|
rows, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "ndiff0:%d=%c", |
|
rows, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
|
|
if (!wi_item) { |
|
snprintf(tmp, sizeof(tmp), "height:%d=%c", |
|
rows, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "workinfoid:%d=%c", |
|
rows, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "ndiff:%d=%c", |
|
rows, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} else { |
|
this_height = workinfo->height; |
|
int_to_buf(this_height, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "height:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
bigint_to_buf(workinfo->workinfoid, |
|
reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "workinfoid:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "ndiff:%d=%.1f%c", |
|
rows, |
|
workinfo->diff_target, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
|
|
rows++; |
|
|
|
snprintf(tmp, sizeof(tmp), "flds=%s%c", |
|
"height0,workinfoid0,ndiff0,height,workinfo,ndiff", |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s%c", |
|
"WorkinfoRange", FLDSEP, "", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
ok = true; |
|
} else if (strcasecmp(request, "payout") == 0) { |
|
/* return the details of the payouts for block height=height |
|
* if expired= is present, also return expired records */ |
|
K_ITEM *i_height, *i_expired, *p_item; |
|
PAYOUTS *payouts = NULL; |
|
bool expired = false; |
|
int32_t height; |
|
char *stats = NULL, *ptr; |
|
|
|
i_height = require_name(trf_root, "height", |
|
1, (char *)intpatt, |
|
reply, siz); |
|
if (!i_height) |
|
return strdup(reply); |
|
TXT_TO_INT("height", transfer_data(i_height), height); |
|
|
|
int_to_buf(height, reply, sizeof(reply)); |
|
snprintf(msg, sizeof(msg), "height=%s", reply); |
|
|
|
i_expired = optional_name(trf_root, "expired", |
|
0, NULL, reply, siz); |
|
if (i_expired) |
|
expired = true; |
|
|
|
K_RLOCK(payouts_free); |
|
p_item = first_payouts(height, ctx); |
|
DATA_PAYOUTS_NULL(payouts, p_item); |
|
while (p_item && payouts->height == height) { |
|
if (expired || CURRENT(&(payouts->expirydate))) { |
|
int_to_buf(payouts->height, |
|
reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), |
|
"height:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
bigint_to_buf(payouts->payoutid, |
|
reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), |
|
"payoutid:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
bigint_to_buf(payouts->minerreward, |
|
reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), |
|
"minerreward:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
bigint_to_buf(payouts->workinfoidstart, |
|
reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), |
|
"workinfoidstart:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
bigint_to_buf(payouts->workinfoidend, |
|
reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), |
|
"workinfoidend:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
bigint_to_buf(payouts->elapsed, |
|
reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), |
|
"elapsed:%d=%s%c", |
|
rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"status:%d=%s%c", |
|
rows, payouts->status, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"diffwanted:%d=%f%c", |
|
rows, payouts->diffwanted, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), |
|
"diffused:%d=%f%c", |
|
rows, payouts->diffused, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
ptr = stats = strdup(payouts->stats); |
|
if (!stats) { |
|
quithere(1, "strdup (%"PRId64") OOM", |
|
payouts->payoutid); |
|
} |
|
while (*ptr) { |
|
if (*ptr == FLDSEP) |
|
*ptr = ' '; |
|
ptr++; |
|
} |
|
snprintf(tmp, sizeof(tmp), |
|
"stats:%d=%s%c", |
|
rows, stats, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
FREENULL(stats); |
|
tv_to_buf(&(payouts->expirydate), cd_buf, |
|
sizeof(cd_buf)); |
|
snprintf(tmp, sizeof(tmp), |
|
EDDB"_str:%d=%s%c", |
|
rows, cd_buf, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
tv_to_buf(&(payouts->createdate), cd_buf, |
|
sizeof(cd_buf)); |
|
snprintf(tmp, sizeof(tmp), |
|
CDDB"_str:%d=%s%c", |
|
rows, cd_buf, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
rows++; |
|
} |
|
p_item = next_in_ktree(ctx); |
|
DATA_PAYOUTS_NULL(payouts, p_item); |
|
} |
|
K_RUNLOCK(payouts_free); |
|
|
|
snprintf(tmp, sizeof(tmp), "flds=%s%c", |
|
"height,payoutid,minerreward,workinfoidstart," |
|
"workinfoidend,elapsed,status,diffwanted,diffused," |
|
"stats,"EDDB"_str,"CDDB"_str", |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s%c", |
|
"Payouts", FLDSEP, "", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
ok = true; |
|
} else { |
|
free(buf); |
|
snprintf(reply, siz, "unknown request '%s'", request); |
|
LOGERR("%s() %s.%s", __func__, id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
if (!ok) { |
|
free(buf); |
|
snprintf(reply, siz, "failed.%s%s%s", |
|
request, |
|
msg[0] ? " " : "", |
|
msg[0] ? msg : ""); |
|
LOGERR("%s() %s.%s", __func__, id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
snprintf(tmp, sizeof(tmp), "rows=%d", rows); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
LOGWARNING("%s() %s.%s%s%s", __func__, id, request, |
|
msg[0] ? " " : "", |
|
msg[0] ? msg : ""); |
|
return buf; |
|
} |
|
|
|
// Query and disable internal lock detection code |
|
static char *cmd_locks(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *cd, |
|
__maybe_unused K_TREE *trf_root) |
|
{ |
|
bool code_locks = false, code_deadlocks = false; |
|
bool was_locks = false, was_deadlocks = false; |
|
bool new_locks = false, new_deadlocks = false; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
#if LOCK_CHECK |
|
K_ITEM *i_locks, *i_deadlocks; |
|
char *deadlocks; |
|
#endif |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
#if LOCK_CHECK |
|
code_locks = true; |
|
was_locks = new_locks = check_locks; |
|
|
|
code_deadlocks = true; |
|
was_deadlocks = new_locks = check_deadlocks; |
|
#endif |
|
|
|
/* options are |
|
* locks <- disable lock checking if it's enabled (value ignored) |
|
* deadlocks=Y/N <- enable/disable deadlock prediction |
|
* any word with any case starting with 'Y' means enable it |
|
* anything else means disable it |
|
* When you enable it, it won't re-enable it for threads that |
|
* have failed a deadlock prediction test |
|
* It will report the status of both */ |
|
|
|
#if LOCK_CHECK |
|
i_locks = optional_name(trf_root, "locks", 0, NULL, reply, siz); |
|
if (i_locks) |
|
new_locks = check_locks = false; |
|
|
|
i_deadlocks = optional_name(trf_root, "deadlocks", 0, NULL, reply, siz); |
|
if (i_deadlocks) { |
|
deadlocks = transfer_data(i_deadlocks); |
|
if (toupper(*deadlocks) == TRUE_CHR) |
|
check_deadlocks = true; |
|
else |
|
check_deadlocks = false; |
|
new_deadlocks = check_deadlocks; |
|
} |
|
#endif |
|
|
|
snprintf(reply, siz, |
|
"code_locks=%s%cwas_locks=%s%cnew_locks=%s%c" |
|
"code_deadlocks=%s%cwas_deadlocks=%s%cnew_deadlocks=%s", |
|
TFSTR(code_locks), FLDSEP, TFSTR(was_locks), FLDSEP, |
|
TFSTR(new_locks), FLDSEP, TFSTR(code_deadlocks), FLDSEP, |
|
TFSTR(was_deadlocks), FLDSEP, TFSTR(new_deadlocks)); |
|
LOGWARNING("%s() %s.%s", __func__, id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
static void event_tree(K_TREE *event_tree, char *list, char *reply, size_t siz, |
|
char *buf, size_t *off, size_t *len, int *rows) |
|
{ |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM *e_item; |
|
EVENTS *e; |
|
|
|
e_item = first_in_ktree(event_tree, ctx); |
|
while (e_item) { |
|
DATA_EVENTS(e, e_item); |
|
if (CURRENT(&(e->expirydate))) { |
|
snprintf(reply, siz, "list:%d=%s%c", |
|
*rows, list, FLDSEP); |
|
APPEND_REALLOC(buf, *off, *len, reply); |
|
|
|
snprintf(reply, siz, "id:%d=%d%c", |
|
*rows, e->id, FLDSEP); |
|
APPEND_REALLOC(buf, *off, *len, reply); |
|
|
|
snprintf(reply, siz, "user:%d=%s%c", |
|
*rows, e->createby, FLDSEP); |
|
APPEND_REALLOC(buf, *off, *len, reply); |
|
|
|
if (event_tree == events_ipc_root) { |
|
snprintf(reply, siz, "ipc:%d=%s%c", |
|
*rows, e->ipc, FLDSEP); |
|
APPEND_REALLOC(buf, *off, *len, reply); |
|
} else { |
|
snprintf(reply, siz, "ip:%d=%s%c", |
|
*rows, e->createinet, FLDSEP); |
|
APPEND_REALLOC(buf, *off, *len, reply); |
|
} |
|
|
|
if (event_tree == events_hash_root) { |
|
snprintf(reply, siz, "hash:%d=%.8s%c", |
|
*rows, e->hash, FLDSEP); |
|
APPEND_REALLOC(buf, *off, *len, reply); |
|
} |
|
|
|
snprintf(reply, siz, CDTRF":%d=%ld%c", |
|
(*rows)++, e->createdate.tv_sec, FLDSEP); |
|
APPEND_REALLOC(buf, *off, *len, reply); |
|
} |
|
e_item = next_in_ktree(ctx); |
|
} |
|
} |
|
|
|
// Events status/settings |
|
static char *cmd_events(__maybe_unused PGconn *conn, char *cmd, char *id, |
|
__maybe_unused tv_t *now, __maybe_unused char *by, |
|
__maybe_unused char *code, __maybe_unused char *inet, |
|
__maybe_unused tv_t *cd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *i_action, *i_cmd, *i_list, *i_ip, *i_eventname, *i_lifetime; |
|
K_ITEM *i_des, *i_item; |
|
K_TREE_CTX ctx[1]; |
|
IPS *ips; |
|
char *action, *alert_cmd, *list, *ip, *eventname, *des; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
char tmp[1024] = ""; |
|
char *buf = NULL; |
|
size_t len, off; |
|
int i, rows, oldlife, lifetime; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_action = require_name(trf_root, "action", 1, NULL, reply, siz); |
|
if (!i_action) |
|
return strdup(reply); |
|
action = transfer_data(i_action); |
|
|
|
if (strcasecmp(action, "cmd") == 0) { |
|
/* Change ckdb_alert_cmd to 'cmd' |
|
* blank to disable it */ |
|
i_cmd = require_name(trf_root, "cmd", 0, NULL, reply, siz); |
|
if (!i_cmd) |
|
return strdup(reply); |
|
alert_cmd = transfer_data(i_cmd); |
|
if (strlen(alert_cmd) > MAX_ALERT_CMD) |
|
return strdup("Invalid cmd length - limit " STRINT(MAX_ALERT_CMD)); |
|
K_WLOCK(event_limits_free); |
|
FREENULL(ckdb_alert_cmd); |
|
if (*alert_cmd) |
|
ckdb_alert_cmd = strdup(alert_cmd); |
|
K_WUNLOCK(event_limits_free); |
|
APPEND_REALLOC_INIT(buf, off, len); |
|
if (*alert_cmd) |
|
APPEND_REALLOC(buf, off, len, "ok.cmd set"); |
|
else |
|
APPEND_REALLOC(buf, off, len, "ok.cmd disabled"); |
|
} else if (strcasecmp(action, "settings") == 0) { |
|
// Return all current event settings |
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
K_RLOCK(event_limits_free); |
|
i = -1; |
|
while (e_limits[++i].name) { |
|
|
|
#define EVENTFLD(_fld) do { \ |
|
snprintf(tmp, sizeof(tmp), "%s_" #_fld "=%d%c", \ |
|
e_limits[i].name, e_limits[i]._fld, FLDSEP); \ |
|
APPEND_REALLOC(buf, off, len, tmp); \ |
|
} while (0) |
|
|
|
EVENTFLD(user_low_time); |
|
EVENTFLD(user_low_time_limit); |
|
EVENTFLD(user_hi_time); |
|
EVENTFLD(user_hi_time_limit); |
|
EVENTFLD(ip_low_time); |
|
EVENTFLD(ip_low_time_limit); |
|
EVENTFLD(ip_hi_time); |
|
EVENTFLD(ip_hi_time_limit); |
|
EVENTFLD(lifetime); |
|
} |
|
snprintf(tmp, sizeof(tmp), "event_limits_hash_lifetime=%d", |
|
event_limits_hash_lifetime); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
K_RUNLOCK(event_limits_free); |
|
} else if (strcasecmp(action, "events") == 0) { |
|
/* List the event tree contents |
|
* List is 'all' or one of: hash, user, ip or ipc <- tree names |
|
* Output can be large - check web Admin->ckp for tree sizes */ |
|
bool all, one = false; |
|
i_list = require_name(trf_root, "list", 1, NULL, reply, siz); |
|
if (!i_list) |
|
return strdup(reply); |
|
list = transfer_data(i_list); |
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
rows = 0; |
|
all = (strcmp(list, "all") == 0); |
|
K_RLOCK(events_free); |
|
if (all || strcmp(list, "user") == 0) { |
|
one = true; |
|
event_tree(events_user_root, "user", reply, siz, buf, |
|
&off, &len, &rows); |
|
} |
|
if (all || strcmp(list, "ip") == 0) { |
|
one = true; |
|
event_tree(events_ip_root, "ip", reply, siz, buf, |
|
&off, &len, &rows); |
|
} |
|
if (all || strcmp(list, "ipc") == 0) { |
|
one = true; |
|
event_tree(events_ipc_root, "ipc", reply, siz, buf, |
|
&off, &len, &rows); |
|
} |
|
if (all || strcmp(list, "hash") == 0) { |
|
one = true; |
|
event_tree(events_hash_root, "hash", reply, siz, buf, |
|
&off, &len, &rows); |
|
} |
|
K_RUNLOCK(events_free); |
|
if (!one) { |
|
free(buf); |
|
snprintf(reply, siz, "unknown stats list '%s'", list); |
|
LOGERR("%s() %s.%s", __func__, id, reply); |
|
return strdup(reply); |
|
} |
|
snprintf(tmp, sizeof(tmp), "rows=%d", rows); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} else if (strcasecmp(action, "ips") == 0) { |
|
// List the ips tree contents |
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
rows = 0; |
|
K_RLOCK(ips_free); |
|
i_item = first_in_ktree(ips_root, ctx); |
|
while (i_item) { |
|
DATA_IPS(ips, i_item); |
|
if (CURRENT(&(ips->expirydate))) { |
|
snprintf(tmp, sizeof(tmp), "group:%d=%s%c", |
|
rows, ips->group, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "ip:%d=%s%c", |
|
rows, ips->ip, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "description:%d=%s%c", |
|
rows, ips->description ? : EMPTY, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "lifetime:%d=%d%c", |
|
rows, ips->lifetime, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(tmp, sizeof(tmp), "log:%d=%c%c", |
|
rows, ips->log ? TRUE_CHR : FALSE_CHR, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
snprintf(reply, siz, CDTRF":%d=%ld%c", |
|
rows++, ips->createdate.tv_sec, |
|
FLDSEP); |
|
APPEND_REALLOC(buf, off, len, reply); |
|
} |
|
i_item = next_in_ktree(ctx); |
|
} |
|
K_RUNLOCK(ips_free); |
|
snprintf(tmp, sizeof(tmp), "rows=%d", rows); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} else if (strcasecmp(action, "ban") == 0) { |
|
/* Ban the ip with optional eventname and lifetime |
|
* N.B. this doesn't survive a CKDB restart |
|
* use just cmd_setopts for permanent bans */ |
|
bool found = false; |
|
oldlife = 0; |
|
i_ip = require_name(trf_root, "ip", 1, NULL, reply, siz); |
|
if (!i_ip) |
|
return strdup(reply); |
|
ip = transfer_data(i_ip); |
|
i_eventname = optional_name(trf_root, "eventname", 1, NULL, reply, siz); |
|
if (i_eventname) |
|
eventname = transfer_data(i_eventname); |
|
else { |
|
if (*reply) |
|
return strdup(reply); |
|
eventname = EVENTNAME_ALL; |
|
} |
|
i_lifetime = optional_name(trf_root, "lifetime", 1, |
|
(char *)intpatt, reply, siz); |
|
if (i_lifetime) |
|
lifetime = atoi(transfer_data(i_lifetime)); |
|
else { |
|
if (*reply) |
|
return strdup(reply); |
|
// default to almost 42 years :) |
|
lifetime = 60*60*24*365*42; |
|
} |
|
i_des = optional_name(trf_root, "des", 1, NULL, reply, siz); |
|
if (i_des) |
|
des = transfer_data(i_des); |
|
else { |
|
if (*reply) |
|
return strdup(reply); |
|
des = NULL; |
|
} |
|
K_WLOCK(ips_free); |
|
i_item = find_ips(IPS_GROUP_BAN, ip, eventname, NULL); |
|
if (i_item) { |
|
DATA_IPS(ips, i_item); |
|
found = true; |
|
oldlife = ips->lifetime; |
|
ips->lifetime = lifetime; |
|
// Don't change it if it's not supplied |
|
if (des) { |
|
LIST_MEM_SUB(ips_free, ips->description); |
|
FREENULL(ips->description); |
|
ips->description = strdup(des); |
|
LIST_MEM_ADD(ips_free, ips->description); |
|
} |
|
} else { |
|
ips_add(IPS_GROUP_BAN, ip, eventname, |
|
is_elimitname(eventname, true), des, true, |
|
false, lifetime, true); |
|
} |
|
K_WUNLOCK(ips_free); |
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
if (found) { |
|
snprintf(tmp, sizeof(tmp), "already %s/%s %d->%d", |
|
ip, eventname, oldlife, lifetime); |
|
} else { |
|
snprintf(tmp, sizeof(tmp), "ban %s/%s %d", |
|
ip, eventname, lifetime); |
|
} |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} else if (strcasecmp(action, "unban") == 0) { |
|
/* Unban the ip+eventname - sets lifetime to 1 meaning |
|
* it expires 1 second after it was created |
|
* so next access will remove the ban and succeed |
|
* N.B. if it was a permanent 'cmd_setopts' ban, the unban |
|
* won't survive a CKDB restart. |
|
* You need to BOTH use this AND remove the optioncontrol |
|
* record from the database to permanently remove a ban |
|
* (since there's no cmd_expopts ... yet) */ |
|
|
|
bool found = false; |
|
i_ip = require_name(trf_root, "ip", 1, NULL, reply, siz); |
|
if (!i_ip) |
|
return strdup(reply); |
|
ip = transfer_data(i_ip); |
|
i_eventname = require_name(trf_root, "eventname", 1, NULL, reply, siz); |
|
if (!i_eventname) |
|
return strdup(reply); |
|
eventname = transfer_data(i_eventname); |
|
K_WLOCK(ips_free); |
|
i_item = find_ips(IPS_GROUP_BAN, ip, eventname, NULL); |
|
if (i_item) { |
|
found = true; |
|
DATA_IPS(ips, i_item); |
|
ips->lifetime = 1; |
|
} |
|
K_WUNLOCK(ips_free); |
|
APPEND_REALLOC_INIT(buf, off, len); |
|
if (found) { |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
APPEND_REALLOC(buf, off, len, ip); |
|
APPEND_REALLOC(buf, off, len, "/"); |
|
APPEND_REALLOC(buf, off, len, eventname); |
|
APPEND_REALLOC(buf, off, len, " unbanned"); |
|
} else { |
|
APPEND_REALLOC(buf, off, len, |
|
"ERR.unknown ip+eventname"); |
|
} |
|
} else { |
|
snprintf(reply, siz, "unknown action '%s'", action); |
|
LOGERR("%s() %s.%s", __func__, id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
return buf; |
|
} |
|
|
|
/* The socket command format is as follows: |
|
* Basic structure: |
|
* cmd.ID.fld1=value1 FLDSEP fld2=value2 FLDSEP fld3=... |
|
* cmd is the cmd_str from the table below |
|
* ID is a string of anything but '.' - preferably just digits and/or letters |
|
* FLDSEP is a single character macro - defined in the code near the top |
|
* no spaces around FLDSEP - they are added above for readability |
|
* i.e. it's really: cmd.ID.fld1=value1FLDSEPfld2... |
|
* fldN names cannot contain '=' or FLDSEP |
|
* valueN values cannot contain FLDSEP except for the json field (see below) |
|
* |
|
* The reply will be ID.timestamp.status.information... |
|
* Status 'ok' means it succeeded |
|
* Some cmds you can optionally send as just 'cmd' if 'noid' below is true |
|
* then the reply will be .timestamp.status.information |
|
* i.e. a zero length 'ID' at the start of the reply |
|
* |
|
* Data from ckpool starts with a fld1: json={...} of field data |
|
* This is assumed to be the only field data sent and any other fields after |
|
* it will cause a json error |
|
* Any fields before it will circumvent the json interpretation of {...} and |
|
* the full json in {...} will be stored as text in TRANSFER under the name |
|
* 'json' - which will (usually) mean the command will fail if it requires |
|
* actual field data |
|
* |
|
* Examples of the commands not from ckpool with an example reply |
|
* STAMP is the unix timestamp in seconds |
|
* With no ID: |
|
* ping |
|
* .STAMP.ok.pong |
|
* |
|
* terminate |
|
* .STAMP.ok.exiting |
|
* |
|
* With an ID |
|
* In each case the ID in these examples, also returned, is 'ID' which can |
|
* of course be most any string, as stated above |
|
* For commands with multiple fld=value the space between them must be typed |
|
* as a TAB |
|
* ping.ID |
|
* ID.STAMP.ok.pong |
|
* |
|
* newid.ID.idname=fooid idvalue=1234 |
|
* ID.STAMP.ok.added fooid 1234 |
|
* |
|
* loglevel is a special case to make it quick and easy to use: |
|
* loglevel.ID |
|
* sets the loglevel to atoi(ID) |
|
* Without an ID, it just reports the current value |
|
* |
|
* createdate = true |
|
* means that the data sent must contain a fld or json fld called createdate |
|
* |
|
* The reply format for authorise, addrauth and heartbeat includes json: |
|
* ID.STAMP.ok.cmd={json} |
|
* where cmd is auth, addrauth, or heartbeat |
|
* For the heartbeat pulse reply it has no '={}' |
|
*/ |
|
|
|
// cmd_val cmd_str noid createdate func seq access |
|
struct CMDS ckdb_cmds[] = { |
|
{ CMD_TERMINATE, "terminate", true, false, NULL, SEQ_NONE, ACCESS_SYSTEM }, |
|
{ CMD_PING, "ping", true, false, NULL, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, |
|
{ CMD_VERSION, "version", true, false, NULL, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, |
|
{ CMD_LOGLEVEL, "loglevel", true, false, NULL, SEQ_NONE, ACCESS_SYSTEM }, |
|
{ CMD_FLUSH, "flush", true, false, NULL, SEQ_NONE, ACCESS_SYSTEM }, |
|
{ CMD_SHARELOG, STR_WORKINFO, false, true, cmd_sharelog, SEQ_WORKINFO, ACCESS_POOL }, |
|
{ CMD_SHARELOG, STR_SHARES, false, true, cmd_sharelog, SEQ_SHARES, ACCESS_POOL }, |
|
{ CMD_SHARELOG, STR_SHAREERRORS,false, true, cmd_sharelog, SEQ_SHAREERRORS,ACCESS_POOL }, |
|
{ CMD_SHARELOG, STR_AGEWORKINFO,false, true, cmd_sharelog, SEQ_AGEWORKINFO,ACCESS_POOL }, |
|
{ CMD_AUTH, "authorise", false, true, cmd_auth, SEQ_AUTH, ACCESS_POOL }, |
|
{ CMD_ADDRAUTH, "addrauth", false, true, cmd_addrauth, SEQ_ADDRAUTH, ACCESS_POOL }, |
|
{ CMD_HEARTBEAT,"heartbeat", false, true, cmd_heartbeat, SEQ_HEARTBEAT, ACCESS_POOL }, |
|
{ CMD_ADDUSER, "adduser", false, false, cmd_adduser, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_NEWPASS, "newpass", false, false, cmd_newpass, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_CHKPASS, "chkpass", false, false, cmd_chkpass, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_2FA, "2fa", false, false, cmd_2fa, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_USERSET, "usersettings", false, false, cmd_userset, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_WORKERSET,"workerset", false, false, cmd_workerset, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_POOLSTAT, "poolstats", false, true, cmd_poolstats, SEQ_POOLSTATS, ACCESS_POOL }, |
|
{ CMD_USERSTAT, "userstats", false, true, cmd_userstats, SEQ_NONE, ACCESS_POOL }, |
|
{ CMD_WORKERSTAT,"workerstats", false, true, cmd_workerstats,SEQ_WORKERSTAT, ACCESS_POOL }, |
|
{ CMD_BLOCK, "block", false, true, cmd_blocks, SEQ_BLOCK, ACCESS_POOL }, |
|
{ CMD_BLOCKLIST,"blocklist", false, false, cmd_blocklist, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_BLOCKSTATUS,"blockstatus",false, false, cmd_blockstatus,SEQ_NONE, ACCESS_SYSTEM }, |
|
{ CMD_NEWID, "newid", false, false, cmd_newid, SEQ_NONE, ACCESS_SYSTEM }, |
|
{ CMD_PAYMENTS, "payments", false, false, cmd_payments, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_WORKERS, "workers", false, false, cmd_workers, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_ALLUSERS, "allusers", false, false, cmd_allusers, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_HOMEPAGE, "homepage", false, false, cmd_homepage, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_GETATTS, "getatts", false, false, cmd_getatts, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_SETATTS, "setatts", false, false, cmd_setatts, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_EXPATTS, "expatts", false, false, cmd_expatts, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_GETOPTS, "getopts", false, false, cmd_getopts, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_SETOPTS, "setopts", false, false, cmd_setopts, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_DSP, "dsp", false, false, cmd_dsp, SEQ_NONE, ACCESS_SYSTEM }, |
|
{ CMD_STATS, "stats", true, false, cmd_stats, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, |
|
{ CMD_PPLNS, "pplns", false, false, cmd_pplns, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, |
|
{ CMD_PPLNS2, "pplns2", false, false, cmd_pplns2, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, |
|
{ CMD_PAYOUTS, "payouts", false, false, cmd_payouts, SEQ_NONE, ACCESS_SYSTEM }, |
|
{ CMD_MPAYOUTS, "mpayouts", false, false, cmd_mpayouts, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, |
|
{ CMD_SHIFTS, "shifts", false, false, cmd_shifts, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, |
|
{ CMD_USERSTATUS,"userstatus", false, false, cmd_userstatus, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, |
|
{ CMD_MARKS, "marks", false, false, cmd_marks, SEQ_NONE, ACCESS_SYSTEM }, |
|
{ CMD_PSHIFT, "pshift", false, false, cmd_pshift, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, |
|
{ CMD_SHSTA, "shsta", true, false, cmd_shsta, SEQ_NONE, ACCESS_SYSTEM }, |
|
{ CMD_USERINFO, "userinfo", false, false, cmd_userinfo, SEQ_NONE, ACCESS_WEB }, |
|
{ CMD_BTCSET, "btcset", false, false, cmd_btcset, SEQ_NONE, ACCESS_SYSTEM }, |
|
{ CMD_QUERY, "query", false, false, cmd_query, SEQ_NONE, ACCESS_SYSTEM }, |
|
{ CMD_LOCKS, "locks", false, false, cmd_locks, SEQ_NONE, ACCESS_SYSTEM }, |
|
{ CMD_EVENTS, "events", false, false, cmd_events, SEQ_NONE, ACCESS_SYSTEM }, |
|
{ CMD_END, NULL, false, false, NULL, SEQ_NONE, 0 } |
|
};
|
|
|