13296 lines
369 KiB
13296 lines
369 KiB
/* |
|
* Copyright 1995-2014 Andrew Smith |
|
* Copyright 2014 Con Kolivas |
|
* |
|
* 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 "config.h" |
|
|
|
#include <sys/ioctl.h> |
|
#include <sys/prctl.h> |
|
#include <sys/socket.h> |
|
#include <sys/stat.h> |
|
#include <sys/types.h> |
|
#include <sys/wait.h> |
|
#include <fenv.h> |
|
#include <getopt.h> |
|
#include <jansson.h> |
|
#include <signal.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <ctype.h> |
|
#include <unistd.h> |
|
#include <stdint.h> |
|
#include <regex.h> |
|
#include <sha2.h> |
|
#ifdef HAVE_LIBPQ_FE_H |
|
#include <libpq-fe.h> |
|
#elif defined (HAVE_POSTGRESQL_LIBPQ_FE_H) |
|
#include <postgresql/libpq-fe.h> |
|
#endif |
|
|
|
#include "ckpool.h" |
|
#include "libckpool.h" |
|
|
|
#include "klist.h" |
|
#include "ktree.h" |
|
|
|
/* TODO: any tree/list accessed in new threads needs |
|
* to ensure all code using those trees/lists use locks |
|
* This code's lock implementation is equivalent to table level locking |
|
* Consider adding row level locking (a per kitem usage count) if needed |
|
* TODO: verify all tables with multithread access are locked |
|
*/ |
|
|
|
#define DB_VLOCK "1" |
|
#define DB_VERSION "0.9.2" |
|
#define CKDB_VERSION DB_VERSION"-0.302" |
|
|
|
#define WHERE_FFL " - from %s %s() line %d" |
|
#define WHERE_FFL_HERE __FILE__, __func__, __LINE__ |
|
#define WHERE_FFL_PASS file, func, line |
|
#define WHERE_FFL_ARGS __maybe_unused const char *file, \ |
|
__maybe_unused const char *func, \ |
|
__maybe_unused const int line |
|
|
|
#define STRINT(x) STRINT2(x) |
|
#define STRINT2(x) #x |
|
|
|
// So they can fit into a 1 byte flag field |
|
#define TRUE_STR "Y" |
|
#define FALSE_STR "N" |
|
|
|
#define TRUE_CHR 'Y' |
|
#define FALSE_CHR 'N' |
|
|
|
#define coinbase1height(_cb1) _coinbase1height(_cb1, WHERE_FFL_HERE) |
|
#define cmp_height(_cb1a, _cb1b) _cmp_height(_cb1a, _cb1b, WHERE_FFL_HERE) |
|
|
|
static char *EMPTY = ""; |
|
static char *db_name; |
|
static char *db_user; |
|
static char *db_pass; |
|
|
|
// Currently hard coded at 4 characters |
|
static char *status_chars = "|/-\\"; |
|
|
|
static char *restorefrom; |
|
|
|
/* Startup |
|
* ------- |
|
* During startup we load the DB and track where it is up to with |
|
* dbstatus, we then reload "ckpool's ckdb logfiles" (CCLs) based |
|
* on dbstatus |
|
* Once the DB is loaded, we can immediately start receiving ckpool |
|
* messages since ckpool already has logged all messages to the CLLs |
|
* and ckpool only verifies authorise responses |
|
* Thus we can queue all messages: |
|
* workinfo, shares, shareerror, ageworkinfo, poolstats, userstats |
|
* and block |
|
* with an ok.queued reply to ckpool, to be processed after the reload |
|
* completes and just process authorise messages immediately while the |
|
* reload runs |
|
* This can't cause a duplicate process of an authorise message since a |
|
* reload will ignore any messages before the last DB auths message, |
|
* however, if ckdb and ckpool get out of sync due to ckpool starting |
|
* during the reload (as mentioned below) it is possible for ckdb to |
|
* find an authorise message in the CCLs that was processed in the |
|
* message queue and thus is already in the DB. |
|
* This error would be very rare and also not an issue |
|
* To avoid this, we start the ckpool message queue after loading |
|
* the users, auths, idcontrol and workers DB tables, before loading the |
|
* much larger sharesummary, workinfo, userstats and poolstats DB tables |
|
* so that ckdb is effectively ready for messages almost immediately |
|
* The first ckpool message also allows us to know where ckpool is up to |
|
* in the CCLs and thus where to stop processing the CCLs to stay in |
|
* sync with ckpool |
|
* If ckpool isn't running, then the reload will complete at the end of |
|
* the last CCL file, however if the 1st message arrives from ckpool while |
|
* processing the CCLs, that will mark the point where to stop processing |
|
* but can also produce a fatal error at the end of processing, reporting |
|
* the ckpool message, if the message was not found in the CCL processing |
|
* after the message was received |
|
* This can be caused by two circumstances: |
|
* 1) the disk had not yet written it to the CCL when ckdb read EOF and |
|
* ckpool was started at about the same time as the reload completed. |
|
* This can be seen if the message displayed in the fatal error IS NOT |
|
* in ckdb's message logfile. |
|
* A ckdb restart will resolve this |
|
* 2) ckpool was started at the time of the end of the reload, but the |
|
* message was written to disk and found in the CCL before it was |
|
* processed in the message queue. |
|
* This can be seen if the message displayed in the fatal error IS in |
|
* ckdb's message logfile and means the messages after it in ckdb's |
|
* message logfile have already been processed. |
|
* Again, a ckdb restart will resolve this |
|
* In both the above (very rare) cases, if ckdb was to continue running, |
|
* it would break the synchronisation and could cause DB problems, so |
|
* ckdb aborting and needing a complete restart resolves it |
|
* The users table, required for the authorise messages, is always updated |
|
* immediately and is not affected by ckpool messages until we |
|
* During the reload, when checking the timeframe for summarisation, we |
|
* use the current last userstats createdate as 'now' to avoid touching a |
|
* timeframe where data could still be waiting to be loaded |
|
*/ |
|
|
|
/* Reload data needed |
|
* ------------------ |
|
* After the DB load completes, load "ckpool's ckdb logfile" (CCL), and |
|
* all later CCLs, that contains the oldest date of all of the following: |
|
* RAM shares: oldest DB sharesummary firstshare where complete='n' |
|
* All shares before this have been summarised to the DB with |
|
* complete='a' (or 'y') and were deleted from RAM |
|
* If there are none with complete='n' but are others in the DB, |
|
* then the newest firstshare is used |
|
* RAM shareerrors: as above |
|
* DB+RAM sharesummary: created from shares, so as above |
|
* Some shares after this may have been summarised to other |
|
* sharesummary complete='n', but for any such sharesummary |
|
* we reset it back to the first share found and it will |
|
* correct itself during the CCL reload |
|
* TODO: Verify that all DB sharesummaries with complete='n' |
|
* have done this |
|
* DB+RAM workinfo: start from newest DB createdate workinfo |
|
* DB+RAM auths: start from newest DB createdate auths |
|
* DB+RAM poolstats: newest createdate poolstats |
|
* TODO: subtract how much we need in RAM of the 'between' |
|
* non db records - will depend on TODO: pool stats reporting |
|
* requirements |
|
* DB+RAM userstats: start of the time band of the latest DB record, |
|
* since all data before this has been summarised to the DB |
|
* The userstats summarisation always processes the oldest |
|
* RAM data to the DB |
|
* TODO: multiple pools is not yet handled by ckdb |
|
* TODO: handle a pool restart with a different instance name |
|
* since this would always make the userstats reload point |
|
* just after the instance name was changed - however |
|
* this can be handled for now by simply ignoring the |
|
* poolinstance |
|
* DB+RAM workers: created by auths so auths will resolve it |
|
* DB+RAM blocks: resolved by workinfo - any unsaved blocks (if any) |
|
* will be after the last DB workinfo |
|
* DB+RAM accountbalance (TODO): resolved by shares/workinfo/blocks |
|
* RAM workerstatus: all except last_idle are set at the end of the |
|
* CCL reload |
|
* Code currently doesn't use last_idle |
|
* |
|
* idcontrol: only userid reuse is critical and the user is added |
|
* immeditately to the DB before replying to the add message |
|
* |
|
* Tables that are/will be written straight to the DB, so are OK: |
|
* users, useraccounts, paymentaddresses, payments, |
|
* accountadjustment, optioncontrol, miningpayouts, |
|
* eventlog |
|
* |
|
* The code deals with the issue of 'now' when reloading by: |
|
* createdate is considered 'now' for all data during a reload and is |
|
* a mandatory field always checked for in ckpool data |
|
* Implementing this is simple by assuming 'now' is always createdate |
|
* i.e. during reload but also during normal running to avoid timing |
|
* issues with ckpool data |
|
* Other data supplies the current time to the functions that require |
|
* 'now' since that data is not part of a reload |
|
* |
|
* During reload, any data before the calculated reload stamp for |
|
* that data is discarded |
|
* Any data that matches the reload stamp is processed with an |
|
* ignore duplicates flag for all except as below. |
|
* Any data after the stamp, is processed normally for all except: |
|
* 1) userstats: any record that falls in a DB userstats that would |
|
* summarise that record is discarded, |
|
* otherwise the userstats is processed normally |
|
* 2) shares/shareerrors: any record that matches an incomplete DB |
|
* sharesummary that hasn't been reset, will reset the sharesummary |
|
* so that the sharesummary will be recalculated |
|
* The record is processed normally with or without the reset |
|
* If the sharesummary is complete, the record is discarded |
|
* 3) ageworkinfo records are also handled by the shares date |
|
* while processing, any records already aged are not updated |
|
* and a warning is displayed if there were any matching shares |
|
*/ |
|
|
|
typedef struct loadstatus { |
|
tv_t oldest_sharesummary_firstshare_n; |
|
tv_t newest_sharesummary_firstshare_a; |
|
tv_t newest_sharesummary_firstshare_ay; |
|
tv_t sharesummary_firstshare; // whichever of above 2 used |
|
tv_t oldest_sharesummary_firstshare_a; |
|
tv_t newest_sharesummary_firstshare_y; |
|
tv_t newest_createdate_workinfo; |
|
tv_t newest_createdate_auths; |
|
tv_t newest_createdate_poolstats; |
|
tv_t newest_starttimeband_userstats; |
|
tv_t newest_createdate_blocks; |
|
int64_t oldest_workinfoid_n; // of oldest firstshare sharesummary n |
|
int64_t oldest_workinfoid_a; // of oldest firstshare sharesummary a |
|
int64_t newest_workinfoid_a; // of newest firstshare sharesummary a |
|
int64_t newest_workinfoid_y; // of newest firstshare sharesummary y |
|
} LOADSTATUS; |
|
static LOADSTATUS dbstatus; |
|
|
|
// Share stats since last block |
|
typedef struct poolstatus { |
|
int64_t workinfoid; // Last block |
|
double diffacc; |
|
double diffinv; // Non-acc |
|
double shareacc; |
|
double shareinv; // Non-acc |
|
double best_sdiff; // TODO (maybe) |
|
} POOLSTATUS; |
|
static POOLSTATUS pool; |
|
/* TODO: when we know about orphans, the count reset to zero |
|
* will need to be undone - i.e. recalculate this data from |
|
* the memory tables - maybe ... */ |
|
|
|
// size limit on the command string |
|
#define CMD_SIZ 31 |
|
#define ID_SIZ 31 |
|
|
|
// size to allocate for pgsql text and display (bigger than needed) |
|
#define DATE_BUFSIZ (63+1) |
|
#define CDATE_BUFSIZ (127+1) |
|
#define BIGINT_BUFSIZ (63+1) |
|
#define INT_BUFSIZ (63+1) |
|
#define DOUBLE_BUFSIZ (63+1) |
|
|
|
#define TXT_BIG 256 |
|
#define TXT_MED 128 |
|
#define TXT_SML 64 |
|
#define TXT_FLAG 1 |
|
|
|
// TAB |
|
#define FLDSEP 0x09 |
|
#define FLDSEPSTR "\011" |
|
|
|
// Ensure long long and int64_t are both 8 bytes (and thus also the same) |
|
#define ASSERT1(condition) __maybe_unused static char sizeof_longlong_must_be_8[(condition)?1:-1] |
|
#define ASSERT2(condition) __maybe_unused static char sizeof_int64_t_must_be_8[(condition)?1:-1] |
|
ASSERT1(sizeof(long long) == 8); |
|
ASSERT2(sizeof(int64_t) == 8); |
|
|
|
#define MAXID 0x7fffffffffffffffLL |
|
|
|
#define PGOK(_res) ((_res) == PGRES_COMMAND_OK || \ |
|
(_res) == PGRES_TUPLES_OK || \ |
|
(_res) == PGRES_EMPTY_QUERY) |
|
|
|
// Clear text printable version of txt up to first '\0' |
|
static char *safe_text(char *txt) |
|
{ |
|
unsigned char *ptr = (unsigned char *)txt; |
|
size_t len; |
|
char *ret, *buf; |
|
|
|
if (!txt) { |
|
buf = strdup("(Null)"); |
|
if (!buf) |
|
quithere(1, "malloc OOM"); |
|
return buf; |
|
} |
|
|
|
// Allocate the maximum needed |
|
len = (strlen(txt)+1)*4+1; |
|
ret = buf = malloc(len); |
|
if (!buf) |
|
quithere(1, "malloc (%d) OOM", (int)len); |
|
while (*ptr) { |
|
if (*ptr >= ' ' && *ptr <= '~') |
|
*(buf++) = *(ptr++); |
|
else { |
|
snprintf(buf, 5, "0x%02x", *(ptr++)); |
|
buf += 4; |
|
} |
|
} |
|
strcpy(buf, "0x00"); |
|
return ret; |
|
} |
|
|
|
static char *pqerrmsg(PGconn *conn) |
|
{ |
|
char *ptr, *buf = strdup(PQerrorMessage(conn)); |
|
|
|
ptr = buf + strlen(buf) - 1; |
|
while (ptr >= buf && (*ptr == '\n' || *ptr == '\r')) |
|
*(ptr--) = '\0'; |
|
while (--ptr >= buf) { |
|
if (*ptr == '\n' || *ptr == '\r' || *ptr == '\t') |
|
*ptr = ' '; |
|
} |
|
return buf; |
|
} |
|
|
|
#define PGLOG(__LOG, __str, __rescode, __conn) do { \ |
|
char *__buf = pqerrmsg(__conn); \ |
|
__LOG("%s(): %s failed (%d) '%s'", __func__, \ |
|
__str, (int)rescode, __buf); \ |
|
free(__buf); \ |
|
} while (0) |
|
|
|
#define PGLOGERR(_str, _rescode, _conn) PGLOG(LOGERR, _str, _rescode, _conn) |
|
#define PGLOGEMERG(_str, _rescode, _conn) PGLOG(LOGEMERG, _str, _rescode, _conn) |
|
|
|
/* N.B. STRNCPY() truncates, whereas txt_to_str() aborts ckdb if src > trg |
|
* If copying data from the DB, code should always use txt_to_str() since |
|
* data should never be lost/truncated if it came from the DB - |
|
* that simply implies a code bug or a database change that must be fixed */ |
|
#define STRNCPY(trg, src) do { \ |
|
strncpy((char *)(trg), (char *)(src), sizeof(trg)); \ |
|
trg[sizeof(trg) - 1] = '\0'; \ |
|
} while (0) |
|
|
|
#define STRNCPYSIZ(trg, src, siz) do { \ |
|
strncpy((char *)(trg), (char *)(src), siz); \ |
|
trg[siz - 1] = '\0'; \ |
|
} while (0) |
|
|
|
#define AR_SIZ 1024 |
|
|
|
#define APPEND_REALLOC_INIT(_buf, _off, _len) do { \ |
|
_len = AR_SIZ; \ |
|
(_buf) = malloc(_len); \ |
|
if (!(_buf)) \ |
|
quithere(1, "malloc (%d) OOM", (int)_len); \ |
|
(_buf)[0] = '\0'; \ |
|
_off = 0; \ |
|
} while(0) |
|
|
|
#define APPEND_REALLOC(_dst, _dstoff, _dstsiz, _src) do { \ |
|
size_t _newlen, _srclen = strlen(_src); \ |
|
_newlen = (_dstoff) + _srclen; \ |
|
if (_newlen >= (_dstsiz)) { \ |
|
_dstsiz = _newlen + AR_SIZ - (_newlen % AR_SIZ); \ |
|
_dst = realloc(_dst, _dstsiz); \ |
|
if (!(_dst)) \ |
|
quithere(1, "realloc (%d) OOM", (int)_dstsiz); \ |
|
} \ |
|
strcpy((_dst)+(_dstoff), _src); \ |
|
_dstoff += _srclen; \ |
|
} while(0) |
|
|
|
enum data_type { |
|
TYPE_STR, |
|
TYPE_BIGINT, |
|
TYPE_INT, |
|
TYPE_TV, |
|
TYPE_TVS, |
|
TYPE_CTV, |
|
TYPE_BLOB, |
|
TYPE_DOUBLE |
|
}; |
|
|
|
#define TXT_TO_STR(__nam, __fld, __data) txt_to_str(__nam, __fld, (__data), sizeof(__data)) |
|
#define TXT_TO_BIGINT(__nam, __fld, __data) txt_to_bigint(__nam, __fld, &(__data), sizeof(__data)) |
|
#define TXT_TO_INT(__nam, __fld, __data) txt_to_int(__nam, __fld, &(__data), sizeof(__data)) |
|
#define TXT_TO_TV(__nam, __fld, __data) txt_to_tv(__nam, __fld, &(__data), sizeof(__data)) |
|
#define TXT_TO_CTV(__nam, __fld, __data) txt_to_ctv(__nam, __fld, &(__data), sizeof(__data)) |
|
#define TXT_TO_BLOB(__nam, __fld, __data) txt_to_blob(__nam, __fld, __data) |
|
#define TXT_TO_DOUBLE(__nam, __fld, __data) txt_to_double(__nam, __fld, &(__data), sizeof(__data)) |
|
|
|
#define PQ_GET_FLD(__res, __row, __name, __fld, __ok) do { \ |
|
int __col = PQfnumber(__res, __name); \ |
|
if (__col == -1) { \ |
|
LOGERR("%s(): Unknown field '%s' row %d", __func__, __name, __row); \ |
|
__ok = false; \ |
|
} else \ |
|
__fld = PQgetvalue(__res, __row, __col); \ |
|
} while (0) |
|
|
|
// HISTORY FIELDS |
|
#define HISTORYDATECONTROL ",createdate,createby,createcode,createinet,expirydate" |
|
#define HISTORYDATECOUNT 5 |
|
|
|
#define HISTORYDATEFLDS(_res, _row, _data, _ok) do { \ |
|
char *_fld; \ |
|
PQ_GET_FLD(_res, _row, "createdate", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_TV("createdate", _fld, (_data)->createdate); \ |
|
PQ_GET_FLD(_res, _row, "createby", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_STR("createby", _fld, (_data)->createby); \ |
|
PQ_GET_FLD(_res, _row, "createcode", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_STR("createcode", _fld, (_data)->createcode); \ |
|
PQ_GET_FLD(_res, _row, "createinet", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_STR("createinet", _fld, (_data)->createinet); \ |
|
PQ_GET_FLD(_res, _row, "expirydate", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_TV("expirydate", _fld, (_data)->expirydate); \ |
|
} while (0) |
|
|
|
#define HISTORYDATECONTROLFIELDS \ |
|
tv_t createdate; \ |
|
char createby[TXT_SML+1]; \ |
|
char createcode[TXT_MED+1]; \ |
|
char createinet[TXT_MED+1]; \ |
|
tv_t expirydate |
|
|
|
#define HISTORYDATEPARAMS(_params, _his_pos, _row) do { \ |
|
_params[_his_pos++] = tv_to_buf(&(_row->createdate), NULL, 0); \ |
|
_params[_his_pos++] = str_to_buf(_row->createby, NULL, 0); \ |
|
_params[_his_pos++] = str_to_buf(_row->createcode, NULL, 0); \ |
|
_params[_his_pos++] = str_to_buf(_row->createinet, NULL, 0); \ |
|
_params[_his_pos++] = tv_to_buf(&(_row->expirydate), NULL, 0); \ |
|
} while (0) |
|
|
|
// 6-Jun-6666 06:06:06+00 |
|
#define DEFAULT_EXPIRY 148204965966L |
|
// 1-Jun-6666 00:00:00+00 |
|
#define COMPARE_EXPIRY 148204512000L |
|
|
|
static const tv_t default_expiry = { DEFAULT_EXPIRY, 0L }; |
|
|
|
// No actual need to test tv_usec |
|
#define CURRENT(_tv) (((_tv)->tv_sec == DEFAULT_EXPIRY) ? true : false) |
|
|
|
// 31-Dec-9999 23:59:59+00 |
|
#define DATE_S_EOT 253402300799L |
|
#define DATE_uS_EOT 0L |
|
static const tv_t date_eot = { DATE_S_EOT, DATE_uS_EOT }; |
|
|
|
// All data will be after: 2-Jan-2014 00:00:00+00 |
|
#define DATE_BEGIN 1388620800L |
|
static const tv_t date_begin = { DATE_BEGIN, 0L }; |
|
|
|
#define HISTORYDATEINIT(_row, _cd, _by, _code, _inet) do { \ |
|
_row->createdate.tv_sec = (_cd)->tv_sec; \ |
|
_row->createdate.tv_usec = (_cd)->tv_usec; \ |
|
STRNCPY(_row->createby, _by); \ |
|
STRNCPY(_row->createcode, _code); \ |
|
STRNCPY(_row->createinet, _inet); \ |
|
_row->expirydate.tv_sec = default_expiry.tv_sec; \ |
|
_row->expirydate.tv_usec = default_expiry.tv_usec; \ |
|
} while (0) |
|
|
|
// Override _row defaults if transfer fields are present |
|
#define HISTORYDATETRANSFER(_root, _row) do { \ |
|
K_ITEM *__item; \ |
|
TRANSFER *__transfer; \ |
|
__item = optional_name(_root, "createby", 1, NULL); \ |
|
if (__item) { \ |
|
DATA_TRANSFER(__transfer, __item); \ |
|
STRNCPY(_row->createby, __transfer->mvalue); \ |
|
} \ |
|
__item = optional_name(_root, "createcode", 1, NULL); \ |
|
if (__item) { \ |
|
DATA_TRANSFER(__transfer, __item); \ |
|
STRNCPY(_row->createcode, __transfer->mvalue); \ |
|
} \ |
|
__item = optional_name(_root, "createinet", 1, NULL); \ |
|
if (__item) { \ |
|
DATA_TRANSFER(__transfer, __item); \ |
|
STRNCPY(_row->createinet, __transfer->mvalue); \ |
|
} \ |
|
} while (0) |
|
|
|
// MODIFY FIELDS |
|
#define MODIFYDATECONTROL ",createdate,createby,createcode,createinet" \ |
|
",modifydate,modifyby,modifycode,modifyinet" |
|
|
|
#define MODIFYDATECOUNT 8 |
|
#define MODIFYUPDATECOUNT 4 |
|
|
|
#define MODIFYDATEFLDS(_res, _row, _data, _ok) do { \ |
|
char *_fld; \ |
|
PQ_GET_FLD(_res, _row, "createdate", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_TV("createdate", _fld, (_data)->createdate); \ |
|
PQ_GET_FLD(_res, _row, "createby", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_STR("createby", _fld, (_data)->createby); \ |
|
PQ_GET_FLD(_res, _row, "createcode", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_STR("createcode", _fld, (_data)->createcode); \ |
|
PQ_GET_FLD(_res, _row, "createinet", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_STR("createinet", _fld, (_data)->createinet); \ |
|
PQ_GET_FLD(_res, _row, "modifydate", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_TV("modifydate", _fld, (_data)->modifydate); \ |
|
PQ_GET_FLD(_res, _row, "modifyby", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_STR("modifyby", _fld, (_data)->modifyby); \ |
|
PQ_GET_FLD(_res, _row, "modifycode", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_STR("modifycode", _fld, (_data)->modifycode); \ |
|
PQ_GET_FLD(_res, _row, "modifyinet", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_STR("modifyinet", _fld, (_data)->modifyinet); \ |
|
} while (0) |
|
|
|
#define MODIFYDATECONTROLFIELDS \ |
|
tv_t createdate; \ |
|
char createby[TXT_SML+1]; \ |
|
char createcode[TXT_MED+1]; \ |
|
char createinet[TXT_MED+1]; \ |
|
tv_t modifydate; \ |
|
char modifyby[TXT_SML+1]; \ |
|
char modifycode[TXT_MED+1]; \ |
|
char modifyinet[TXT_MED+1] |
|
|
|
#define MODIFYDATEPARAMS(_params, _mod_pos, _row) do { \ |
|
_params[_mod_pos++] = tv_to_buf(&(_row->createdate), NULL, 0); \ |
|
_params[_mod_pos++] = str_to_buf(_row->createby, NULL, 0); \ |
|
_params[_mod_pos++] = str_to_buf(_row->createcode, NULL, 0); \ |
|
_params[_mod_pos++] = str_to_buf(_row->createinet, NULL, 0); \ |
|
_params[_mod_pos++] = tv_to_buf(&(_row->modifydate), NULL, 0); \ |
|
_params[_mod_pos++] = str_to_buf(_row->modifyby, NULL, 0); \ |
|
_params[_mod_pos++] = str_to_buf(_row->modifycode, NULL, 0); \ |
|
_params[_mod_pos++] = str_to_buf(_row->modifyinet, NULL, 0); \ |
|
} while (0) |
|
|
|
#define MODIFYUPDATEPARAMS(_params, _mod_pos, _row) do { \ |
|
_params[_mod_pos++] = tv_to_buf(&(_row->modifydate), NULL, 0); \ |
|
_params[_mod_pos++] = str_to_buf(_row->modifyby, NULL, 0); \ |
|
_params[_mod_pos++] = str_to_buf(_row->modifycode, NULL, 0); \ |
|
_params[_mod_pos++] = str_to_buf(_row->modifyinet, NULL, 0); \ |
|
} while (0) |
|
|
|
#define MODIFYDATEINIT(_row, _cd, _by, _code, _inet) do { \ |
|
_row->createdate.tv_sec = (_cd)->tv_sec; \ |
|
_row->createdate.tv_usec = (_cd)->tv_usec; \ |
|
STRNCPY(_row->createby, _by); \ |
|
STRNCPY(_row->createcode, _code); \ |
|
STRNCPY(_row->createinet, _inet); \ |
|
_row->modifydate.tv_sec = 0; \ |
|
_row->modifydate.tv_usec = 0; \ |
|
_row->modifyby[0] = '\0'; \ |
|
_row->modifycode[0] = '\0'; \ |
|
_row->modifyinet[0] = '\0'; \ |
|
} while (0) |
|
|
|
#define MODIFYUPDATE(_row, _cd, _by, _code, _inet) do { \ |
|
_row->modifydate.tv_sec = (_cd)->tv_sec; \ |
|
_row->modifydate.tv_usec = (_cd)->tv_usec; \ |
|
STRNCPY(_row->modifyby, _by); \ |
|
STRNCPY(_row->modifycode, _code); \ |
|
STRNCPY(_row->modifyinet, _inet); \ |
|
} while (0) |
|
|
|
// SIMPLE FIELDS |
|
#define SIMPLEDATECONTROL ",createdate,createby,createcode,createinet" |
|
#define SIMPLEDATECOUNT 4 |
|
|
|
#define SIMPLEDATEFLDS(_res, _row, _data, _ok) do { \ |
|
char *_fld; \ |
|
PQ_GET_FLD(_res, _row, "createdate", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_TV("createdate", _fld, (_data)->createdate); \ |
|
PQ_GET_FLD(_res, _row, "createby", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_STR("createby", _fld, (_data)->createby); \ |
|
PQ_GET_FLD(_res, _row, "createcode", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_STR("createcode", _fld, (_data)->createcode); \ |
|
PQ_GET_FLD(_res, _row, "createinet", _fld, _ok); \ |
|
if (!_ok) \ |
|
break; \ |
|
TXT_TO_STR("createinet", _fld, (_data)->createinet); \ |
|
} while (0) |
|
|
|
#define SIMPLEDATECONTROLFIELDS \ |
|
tv_t createdate; \ |
|
char createby[TXT_SML+1]; \ |
|
char createcode[TXT_MED+1]; \ |
|
char createinet[TXT_MED+1] |
|
|
|
#define SIMPLEDATEPARAMS(_params, _his_pos, _row) do { \ |
|
_params[_his_pos++] = tv_to_buf(&(_row->createdate), NULL, 0); \ |
|
_params[_his_pos++] = str_to_buf(_row->createby, NULL, 0); \ |
|
_params[_his_pos++] = str_to_buf(_row->createcode, NULL, 0); \ |
|
_params[_his_pos++] = str_to_buf(_row->createinet, NULL, 0); \ |
|
} while (0) |
|
|
|
#define SIMPLEDATEINIT(_row, _cd, _by, _code, _inet) do { \ |
|
_row->createdate.tv_sec = (_cd)->tv_sec; \ |
|
_row->createdate.tv_usec = (_cd)->tv_usec; \ |
|
STRNCPY(_row->createby, _by); \ |
|
STRNCPY(_row->createcode, _code); \ |
|
STRNCPY(_row->createinet, _inet); \ |
|
} while (0) |
|
|
|
#define SIMPLEDATEDEFAULT(_row, _cd) do { \ |
|
_row->createdate.tv_sec = (_cd)->tv_sec; \ |
|
_row->createdate.tv_usec = (_cd)->tv_usec; \ |
|
STRNCPY(_row->createby, by_default); \ |
|
STRNCPY(_row->createcode, (char *)__func__); \ |
|
STRNCPY(_row->createinet, inet_default); \ |
|
} while (0) |
|
|
|
// Override _row defaults if transfer fields are present |
|
#define SIMPLEDATETRANSFER(_root, _row) do { \ |
|
K_ITEM *__item; \ |
|
TRANSFER *__transfer; \ |
|
__item = optional_name(_root, "createby", 1, NULL); \ |
|
if (__item) { \ |
|
DATA_TRANSFER(__transfer, __item); \ |
|
STRNCPY(_row->createby, __transfer->mvalue); \ |
|
} \ |
|
__item = optional_name(_root, "createcode", 1, NULL); \ |
|
if (__item) { \ |
|
DATA_TRANSFER(__transfer, __item); \ |
|
STRNCPY(_row->createcode, __transfer->mvalue); \ |
|
} \ |
|
__item = optional_name(_root, "createinet", 1, NULL); \ |
|
if (__item) { \ |
|
DATA_TRANSFER(__transfer, __item); \ |
|
STRNCPY(_row->createinet, __transfer->mvalue); \ |
|
} \ |
|
} while (0) |
|
|
|
// For easy parameter constant strings |
|
#define PQPARAM1 "$1" |
|
#define PQPARAM2 "$1,$2" |
|
#define PQPARAM3 "$1,$2,$3" |
|
#define PQPARAM4 "$1,$2,$3,$4" |
|
#define PQPARAM5 "$1,$2,$3,$4,$5" |
|
#define PQPARAM6 "$1,$2,$3,$4,$5,$6" |
|
#define PQPARAM7 "$1,$2,$3,$4,$5,$6,$7" |
|
#define PQPARAM8 "$1,$2,$3,$4,$5,$6,$7,$8" |
|
#define PQPARAM9 PQPARAM8 ",$9" |
|
#define PQPARAM10 PQPARAM8 ",$9,$10" |
|
#define PQPARAM11 PQPARAM8 ",$9,$10,$11" |
|
#define PQPARAM12 PQPARAM8 ",$9,$10,$11,$12" |
|
#define PQPARAM13 PQPARAM8 ",$9,$10,$11,$12,$13" |
|
#define PQPARAM14 PQPARAM8 ",$9,$10,$11,$12,$13,$14" |
|
#define PQPARAM15 PQPARAM8 ",$9,$10,$11,$12,$13,$14,$15" |
|
#define PQPARAM16 PQPARAM8 ",$9,$10,$11,$12,$13,$14,$15,$16" |
|
#define PQPARAM22 PQPARAM16 ",$17,$18,$19,$20,$21,$22" |
|
#define PQPARAM27 PQPARAM22 ",$23,$24,$25,$26,$27" |
|
|
|
#define PARCHK(_par, _params) do { \ |
|
if (_par != (int)(sizeof(_params)/sizeof(_params[0]))) { \ |
|
quithere(1, "params[] usage (%d) != size (%d)", \ |
|
_par, (int)(sizeof(_params)/sizeof(_params[0]))); \ |
|
} \ |
|
} while (0) |
|
|
|
#define PARCHKVAL(_par, _val, _params) do { \ |
|
if (_par != _val) { \ |
|
quithere(1, "params[] usage (%d) != expected (%d)", \ |
|
_par, _val); \ |
|
} \ |
|
if (_val > (int)(sizeof(_params)/sizeof(_params[0]))) { \ |
|
quithere(1, "params[] usage (%d) > size (%d)", \ |
|
_val, (int)(sizeof(_params)/sizeof(_params[0]))); \ |
|
} \ |
|
} while (0) |
|
|
|
#define BTC_TO_D(_amt) ((double)((_amt) / 100000000.0)) |
|
|
|
// argv -y - don't run in ckdb mode, just confirm sharesummaries |
|
static bool confirm_sharesummary; |
|
|
|
/* Optional workinfoid range -Y to supply when confirming sharesummaries |
|
* N.B. if you specify -Y it will enable -y, so -y isn't also required |
|
* |
|
* Default (NULL) is to confirm all aged sharesummaries |
|
* Default should normally be used every time |
|
* The below options are mainly for debugging or |
|
* a quicker confirm if required due to not running confirms regularly |
|
* TODO: ... once the code includes flagging confirmed sharesummaries |
|
* Valid options are: |
|
* bNNN - confirm all workinfoid's from the previous db block before |
|
* NNN (or 0) up to the workinfoid of the 1st db block height |
|
* equal or after NNN |
|
* wNNN - confirm all workinfoid's from NNN up to the last aged |
|
* sharesummary |
|
* rNNN-MMM - confirm all workinfoid's from NNN to MMM inclusive |
|
* cNNN-MMM - check the CCL record timestamps then exit |
|
* i - just show the DB load information then exit |
|
*/ |
|
static char *confirm_range; |
|
static int confirm_block; |
|
static int64_t confirm_range_start; |
|
static int64_t confirm_range_finish; |
|
static bool confirm_check_createdate; |
|
static int64_t ccl_mismatch_abs; |
|
static int64_t ccl_mismatch; |
|
static double ccl_mismatch_min; |
|
static double ccl_mismatch_max; |
|
static int64_t ccl_unordered_abs; |
|
static int64_t ccl_unordered; |
|
static double ccl_unordered_most; |
|
|
|
// The workinfoid range we are processing |
|
static int64_t confirm_first_workinfoid; |
|
static int64_t confirm_last_workinfoid; |
|
/* Stop the reload 11min after the 'last' workinfoid+1 appears |
|
* ckpool uses 10min - but add 1min to be sure */ |
|
#define WORKINFO_AGE 660 |
|
static tv_t confirm_finish; |
|
|
|
static tv_t reload_timestamp; |
|
|
|
// DB users,workers,auth load is complete |
|
static bool db_auths_complete = false; |
|
// DB load is complete |
|
static bool db_load_complete = false; |
|
// Different input data handling |
|
static bool reloading = false; |
|
// Data load is complete |
|
static bool startup_complete = false; |
|
// Tell everyone to die |
|
static bool everyone_die = false; |
|
|
|
static cklock_t fpm_lock; |
|
static char *first_pool_message; |
|
static sem_t socketer_sem; |
|
|
|
static const char *userpatt = "^[^/\\._"FLDSEPSTR"]*$"; // disallow: '/' '.' '_' and FLDSEP |
|
static const char *mailpatt = "^[A-Za-z0-9_-][A-Za-z0-9_\\.-]*@[A-Za-z0-9][A-Za-z0-9\\.-]*[A-Za-z0-9]$"; |
|
static const char *idpatt = "^[_A-Za-z][_A-Za-z0-9]*$"; |
|
static const char *intpatt = "^[0-9][0-9]*$"; |
|
static const char *hashpatt = "^[A-Fa-f0-9]*$"; |
|
|
|
#define JSON_TRANSFER "json=" |
|
#define JSON_TRANSFER_LEN (sizeof(JSON_TRANSFER)-1) |
|
|
|
// Methods for sharelog (common function for all) |
|
#define STR_WORKINFO "workinfo" |
|
#define STR_SHARES "shares" |
|
#define STR_SHAREERRORS "shareerror" |
|
#define STR_AGEWORKINFO "ageworkinfo" |
|
|
|
static char *by_default = "code"; |
|
static char *inet_default = "127.0.0.1"; |
|
|
|
enum cmd_values { |
|
CMD_UNSET, |
|
CMD_REPLY, // Means something was wrong - send back reply |
|
CMD_SHUTDOWN, |
|
CMD_PING, |
|
CMD_VERSION, |
|
CMD_LOGLEVEL, |
|
CMD_SHARELOG, |
|
CMD_AUTH, |
|
CMD_ADDRAUTH, |
|
CMD_ADDUSER, |
|
CMD_NEWPASS, |
|
CMD_CHKPASS, |
|
CMD_USERSET, |
|
CMD_POOLSTAT, |
|
CMD_USERSTAT, |
|
CMD_BLOCK, |
|
CMD_BLOCKLIST, |
|
CMD_BLOCKSTATUS, |
|
CMD_NEWID, |
|
CMD_PAYMENTS, |
|
CMD_WORKERS, |
|
CMD_ALLUSERS, |
|
CMD_HOMEPAGE, |
|
CMD_GETATTS, |
|
CMD_SETATTS, |
|
CMD_DSP, |
|
CMD_STATS, |
|
CMD_PPLNS, |
|
CMD_END |
|
}; |
|
|
|
// For NON-list stack/heap K_ITEMS |
|
#define INIT_GENERIC(_item, _name) do { \ |
|
(_item)->name = _name ## _free->name; \ |
|
} while (0) |
|
|
|
#define DATA_GENERIC(_var, _item, _name, _nonull) do { \ |
|
if ((_item) == NULL) { \ |
|
if (_nonull) { \ |
|
quithere(1, "Attempt to cast NULL item data (as '%s')", \ |
|
_name ## _free->name); \ |
|
} else \ |
|
(_var) = NULL; \ |
|
} else { \ |
|
if ((_item)->name != _name ## _free->name) { \ |
|
quithere(1, "Attempt to cast item '%s' data as '%s'", \ |
|
(_item)->name, \ |
|
_name ## _free->name); \ |
|
} \ |
|
(_var) = ((struct _name *)((_item)->data)); \ |
|
} \ |
|
} while (0) |
|
|
|
// LOGQUEUE |
|
typedef struct logqueue { |
|
char *msg; |
|
} LOGQUEUE; |
|
|
|
#define ALLOC_LOGQUEUE 1024 |
|
#define LIMIT_LOGQUEUE 0 |
|
#define INIT_LOGQUEUE(_item) INIT_GENERIC(_item, logqueue) |
|
#define DATA_LOGQUEUE(_var, _item) DATA_GENERIC(_var, _item, logqueue, true) |
|
|
|
static K_LIST *logqueue_free; |
|
static K_STORE *logqueue_store; |
|
|
|
// WORKQUEUE |
|
typedef struct workqueue { |
|
char *buf; |
|
int which_cmds; |
|
enum cmd_values cmdnum; |
|
char cmd[CMD_SIZ+1]; |
|
char id[ID_SIZ+1]; |
|
tv_t now; |
|
char by[TXT_SML+1]; |
|
char code[TXT_MED+1]; |
|
char inet[TXT_MED+1]; |
|
tv_t cd; |
|
K_TREE *trf_root; |
|
K_STORE *trf_store; |
|
} WORKQUEUE; |
|
|
|
#define ALLOC_WORKQUEUE 1024 |
|
#define LIMIT_WORKQUEUE 0 |
|
#define CULL_WORKQUEUE 16 |
|
#define INIT_WORKQUEUE(_item) INIT_GENERIC(_item, workqueue) |
|
#define DATA_WORKQUEUE(_var, _item) DATA_GENERIC(_var, _item, workqueue, true) |
|
|
|
static K_LIST *workqueue_free; |
|
static K_STORE *workqueue_store; |
|
static pthread_mutex_t wq_waitlock; |
|
static pthread_cond_t wq_waitcond; |
|
|
|
// TRANSFER |
|
#define NAME_SIZE 63 |
|
#define VALUE_SIZE 1023 |
|
typedef struct transfer { |
|
char name[NAME_SIZE+1]; |
|
char svalue[VALUE_SIZE+1]; |
|
char *mvalue; |
|
} TRANSFER; |
|
|
|
#define ALLOC_TRANSFER 64 |
|
#define LIMIT_TRANSFER 0 |
|
#define CULL_TRANSFER 64 |
|
#define INIT_TRANSFER(_item) INIT_GENERIC(_item, transfer) |
|
#define DATA_TRANSFER(_var, _item) DATA_GENERIC(_var, _item, transfer, true) |
|
|
|
static K_LIST *transfer_free; |
|
|
|
#define transfer_data(_item) _transfer_data(_item, WHERE_FFL_HERE) |
|
|
|
// For mutiple variable function calls that need the data |
|
static char *_transfer_data(K_ITEM *item, WHERE_FFL_ARGS) |
|
{ |
|
TRANSFER *transfer; |
|
char *mvalue; |
|
|
|
if (!item) { |
|
quitfrom(1, file, func, line, |
|
"Attempt to use NULL transfer item"); |
|
} |
|
if (item->name != transfer_free->name) { |
|
quitfrom(1, file, func, line, |
|
"Attempt to cast item '%s' data as '%s'", |
|
item->name, |
|
transfer_free->name); |
|
} |
|
transfer = (TRANSFER *)(item->data); |
|
if (!transfer) { |
|
quitfrom(1, file, func, line, |
|
"Transfer item has NULL data"); |
|
} |
|
mvalue = transfer->mvalue; |
|
if (!mvalue) { |
|
/* N.B. name and svalue strings will have \0 termination |
|
* even if they are both corrupt, since mvalue is NULL */ |
|
quitfrom(1, file, func, line, |
|
"Transfer '%s' '%s' has NULL mvalue", |
|
transfer->name, transfer->svalue); |
|
} |
|
return mvalue; |
|
} |
|
|
|
// So the records below have the same 'name' as the klist |
|
static const char Transfer[] = "Transfer"; |
|
|
|
// older version missing field defaults |
|
static TRANSFER auth_1 = { "poolinstance", "", auth_1.svalue }; |
|
static K_ITEM auth_poolinstance = { Transfer, NULL, NULL, (void *)(&auth_1) }; |
|
static TRANSFER auth_2 = { "preauth", FALSE_STR, auth_2.svalue }; |
|
static K_ITEM auth_preauth = { Transfer, NULL, NULL, (void *)(&auth_2) }; |
|
static TRANSFER poolstats_1 = { "elapsed", "0", poolstats_1.svalue }; |
|
static K_ITEM poolstats_elapsed = { Transfer, NULL, NULL, (void *)(&poolstats_1) }; |
|
static TRANSFER userstats_1 = { "elapsed", "0", userstats_1.svalue }; |
|
static K_ITEM userstats_elapsed = { Transfer, NULL, NULL, (void *)(&userstats_1) }; |
|
static TRANSFER userstats_2 = { "workername", "all", userstats_2.svalue }; |
|
static K_ITEM userstats_workername = { Transfer, NULL, NULL, (void *)(&userstats_2) }; |
|
static TRANSFER userstats_3 = { "idle", FALSE_STR, userstats_3.svalue }; |
|
static K_ITEM userstats_idle = { Transfer, NULL, NULL, (void *)(&userstats_3) }; |
|
static TRANSFER userstats_4 = { "eos", TRUE_STR, userstats_4.svalue }; |
|
static K_ITEM userstats_eos = { Transfer, NULL, NULL, (void *)(&userstats_4) }; |
|
|
|
static TRANSFER shares_1 = { "secondaryuserid", TRUE_STR, shares_1.svalue }; |
|
static K_ITEM shares_secondaryuserid = { Transfer, NULL, NULL, (void *)(&shares_1) }; |
|
static TRANSFER shareerrors_1 = { "secondaryuserid", TRUE_STR, shareerrors_1.svalue }; |
|
static K_ITEM shareerrors_secondaryuserid = { Transfer, NULL, NULL, (void *)(&shareerrors_1) }; |
|
// Time limit that this problem occurred |
|
// 24-Aug-2014 05:20+00 (1st one shortly after this) |
|
static tv_t missing_secuser_min = { 1408857600L, 0L }; |
|
// 24-Aug-2014 06:00+00 |
|
static tv_t missing_secuser_max = { 1408860000L, 0L }; |
|
|
|
// USERS |
|
typedef struct users { |
|
int64_t userid; |
|
char username[TXT_BIG+1]; |
|
// Anything in 'status' disables the account |
|
char status[TXT_BIG+1]; |
|
char emailaddress[TXT_BIG+1]; |
|
tv_t joineddate; |
|
char passwordhash[TXT_BIG+1]; |
|
char secondaryuserid[TXT_SML+1]; |
|
char salt[TXT_BIG+1]; |
|
HISTORYDATECONTROLFIELDS; |
|
} USERS; |
|
|
|
#define ALLOC_USERS 1024 |
|
#define LIMIT_USERS 0 |
|
#define INIT_USERS(_item) INIT_GENERIC(_item, users) |
|
#define DATA_USERS(_var, _item) DATA_GENERIC(_var, _item, users, true) |
|
#define DATA_USERS_NULL(_var, _item) DATA_GENERIC(_var, _item, users, false) |
|
|
|
#define SHA256SIZHEX 64 |
|
#define SHA256SIZBIN 32 |
|
#define SALTSIZHEX 32 |
|
#define SALTSIZBIN 16 |
|
|
|
static K_TREE *users_root; |
|
static K_TREE *userid_root; |
|
static K_LIST *users_free; |
|
static K_STORE *users_store; |
|
|
|
// USERATTS |
|
typedef struct useratts { |
|
int64_t userid; |
|
char attname[TXT_SML+1]; |
|
char status[TXT_BIG+1]; |
|
char attstr[TXT_BIG+1]; |
|
char attstr2[TXT_BIG+1]; |
|
int64_t attnum; |
|
int64_t attnum2; |
|
tv_t attdate; |
|
tv_t attdate2; |
|
HISTORYDATECONTROLFIELDS; |
|
} USERATTS; |
|
|
|
#define ALLOC_USERATTS 1024 |
|
#define LIMIT_USERATTS 0 |
|
#define INIT_USERATTS(_item) INIT_GENERIC(_item, useratts) |
|
#define DATA_USERATTS(_var, _item) DATA_GENERIC(_var, _item, useratts, true) |
|
#define DATA_USERATTS_NULL(_var, _item) DATA_GENERIC(_var, _item, useratts, false) |
|
|
|
static K_TREE *useratts_root; |
|
static K_LIST *useratts_free; |
|
static K_STORE *useratts_store; |
|
|
|
// WORKERS |
|
typedef struct workers { |
|
int64_t workerid; |
|
int64_t userid; |
|
char workername[TXT_BIG+1]; // includes username |
|
int32_t difficultydefault; |
|
char idlenotificationenabled[TXT_FLAG+1]; |
|
int32_t idlenotificationtime; |
|
HISTORYDATECONTROLFIELDS; |
|
} WORKERS; |
|
|
|
#define ALLOC_WORKERS 1024 |
|
#define LIMIT_WORKERS 0 |
|
#define INIT_WORKERS(_item) INIT_GENERIC(_item, workers) |
|
#define DATA_WORKERS(_var, _item) DATA_GENERIC(_var, _item, workers, true) |
|
#define DATA_WORKERS_NULL(_var, _item) DATA_GENERIC(_var, _item, workers, false) |
|
|
|
static K_TREE *workers_root; |
|
static K_LIST *workers_free; |
|
static K_STORE *workers_store; |
|
|
|
#define DIFFICULTYDEFAULT_MIN 10 |
|
#define DIFFICULTYDEFAULT_MAX 1000000 |
|
#define DIFFICULTYDEFAULT_DEF DIFFICULTYDEFAULT_MIN |
|
#define DIFFICULTYDEFAULT_DEF_STR STRINT(DIFFICULTYDEFAULT_DEF) |
|
#define IDLENOTIFICATIONENABLED "y" |
|
#define IDLENOTIFICATIONDISABLED " " |
|
#define IDLENOTIFICATIONENABLED_DEF IDLENOTIFICATIONDISABLED |
|
#define IDLENOTIFICATIONTIME_MIN 10 |
|
#define IDLENOTIFICATIONTIME_MAX 60 |
|
#define IDLENOTIFICATIONTIME_DEF IDLENOTIFICATIONTIME_MIN |
|
#define IDLENOTIFICATIONTIME_DEF_STR STRINT(IDLENOTIFICATIONTIME_DEF) |
|
|
|
// PAYMENTADDRESSES |
|
typedef struct paymentaddresses { |
|
int64_t paymentaddressid; |
|
int64_t userid; |
|
char payaddress[TXT_BIG+1]; |
|
int32_t payratio; |
|
HISTORYDATECONTROLFIELDS; |
|
} PAYMENTADDRESSES; |
|
|
|
#define ALLOC_PAYMENTADDRESSES 1024 |
|
#define LIMIT_PAYMENTADDRESSES 0 |
|
#define INIT_PAYMENTADDRESSES(_item) INIT_GENERIC(_item, paymentaddresses) |
|
#define DATA_PAYMENTADDRESSES(_var, _item) DATA_GENERIC(_var, _item, paymentaddresses, true) |
|
|
|
static K_TREE *paymentaddresses_root; |
|
static K_LIST *paymentaddresses_free; |
|
static K_STORE *paymentaddresses_store; |
|
|
|
// PAYMENTS |
|
typedef struct payments { |
|
int64_t paymentid; |
|
int64_t userid; |
|
tv_t paydate; |
|
char payaddress[TXT_BIG+1]; |
|
char originaltxn[TXT_BIG+1]; |
|
int64_t amount; |
|
char committxn[TXT_BIG+1]; |
|
char commitblockhash[TXT_BIG+1]; |
|
HISTORYDATECONTROLFIELDS; |
|
} PAYMENTS; |
|
|
|
#define ALLOC_PAYMENTS 1024 |
|
#define LIMIT_PAYMENTS 0 |
|
#define INIT_PAYMENTS(_item) INIT_GENERIC(_item, payments) |
|
#define DATA_PAYMENTS(_var, _item) DATA_GENERIC(_var, _item, payments, true) |
|
#define DATA_PAYMENTS_NULL(_var, _item) DATA_GENERIC(_var, _item, payments, false) |
|
|
|
static K_TREE *payments_root; |
|
static K_LIST *payments_free; |
|
static K_STORE *payments_store; |
|
|
|
/* unused yet |
|
// ACCOUNTBALANCE |
|
typedef struct accountbalance { |
|
int64_t userid; |
|
int64_t confirmedpaid; |
|
int64_t confirmedunpaid; |
|
int64_t pendingconfirm; |
|
int32_t heightupdate; |
|
HISTORYDATECONTROLFIELDS; |
|
} ACCOUNTBALANCE; |
|
|
|
#define ALLOC_ACCOUNTBALANCE 1024 |
|
#define LIMIT_ACCOUNTBALANCE 0 |
|
#define INIT_ACCOUNTBALANCE(_item) INIT_GENERIC(_item, accountbalance) |
|
#define DATA_ACCOUNTBALANCE(_var, _item) DATA_GENERIC(_var, _item, accountbalance, true) |
|
|
|
static K_TREE *accountbalance_root; |
|
static K_LIST *accountbalance_free; |
|
static K_STORE *accountbalance_store; |
|
|
|
// ACCOUNTADJUSTMENT |
|
typedef struct accountadjustment { |
|
int64_t userid; |
|
char authority[TXT_BIG+1]; |
|
char *reason; |
|
int64_t amount; |
|
HISTORYDATECONTROLFIELDS; |
|
} ACCOUNTADJUSTMENT; |
|
|
|
#define ALLOC_ACCOUNTADJUSTMENT 100 |
|
#define LIMIT_ACCOUNTADJUSTMENT 0 |
|
#define INIT_ACCOUNTADJUSTMENT(_item) INIT_GENERIC(_item, accountadjustment) |
|
#define DATA_ACCOUNTADJUSTMENT(_var, _item) DATA_GENERIC(_var, _item, accountadjustment, true) |
|
|
|
static K_TREE *accountadjustment_root; |
|
static K_LIST *accountadjustment_free; |
|
static K_STORE *accountadjustment_store; |
|
*/ |
|
|
|
// IDCONTROL |
|
typedef struct idcontrol { |
|
char idname[TXT_SML+1]; |
|
int64_t lastid; |
|
MODIFYDATECONTROLFIELDS; |
|
} IDCONTROL; |
|
|
|
#define ALLOC_IDCONTROL 16 |
|
#define LIMIT_IDCONTROL 0 |
|
#define INIT_IDCONTROL(_item) INIT_GENERIC(_item, idcontrol) |
|
#define DATA_IDCONTROL(_var, _item) DATA_GENERIC(_var, _item, idcontrol, true) |
|
|
|
// These are only used for db access - not stored in memory |
|
//static K_TREE *idcontrol_root; |
|
static K_LIST *idcontrol_free; |
|
static K_STORE *idcontrol_store; |
|
|
|
/* unused yet |
|
// OPTIONCONTROL |
|
typedef struct optioncontrol { |
|
char optionname[TXT_SML+1]; |
|
char *optionvalue; |
|
tv_t activationdate; |
|
int32_t activationheight; |
|
HISTORYDATECONTROLFIELDS; |
|
} OPTIONCONTROL; |
|
|
|
#define ALLOC_OPTIONCONTROL 64 |
|
#define LIMIT_OPTIONCONTROL 0 |
|
#define INIT_OPTIONCONTROL(_item) INIT_GENERIC(_item, optioncontrol) |
|
#define DATA_OPTIONCONTROL(_var, _item) DATA_GENERIC(_var, _item, optioncontrol, true) |
|
|
|
static K_TREE *optioncontrol_root; |
|
static K_LIST *optioncontrol_free; |
|
static K_STORE *optioncontrol_store; |
|
*/ |
|
|
|
// TODO: discarding workinfo,shares |
|
// WORKINFO workinfo.id.json={...} |
|
typedef struct workinfo { |
|
int64_t workinfoid; |
|
char poolinstance[TXT_BIG+1]; |
|
char *transactiontree; |
|
char *merklehash; |
|
char prevhash[TXT_BIG+1]; |
|
char coinbase1[TXT_BIG+1]; |
|
char coinbase2[TXT_BIG+1]; |
|
char version[TXT_SML+1]; |
|
char bits[TXT_SML+1]; |
|
char ntime[TXT_SML+1]; |
|
int64_t reward; |
|
HISTORYDATECONTROLFIELDS; |
|
} WORKINFO; |
|
|
|
// ~10 hrs |
|
#define ALLOC_WORKINFO 1400 |
|
#define LIMIT_WORKINFO 0 |
|
#define INIT_WORKINFO(_item) INIT_GENERIC(_item, workinfo) |
|
#define DATA_WORKINFO(_var, _item) DATA_GENERIC(_var, _item, workinfo, true) |
|
|
|
static K_TREE *workinfo_root; |
|
// created during data load then destroyed since not needed later |
|
static K_TREE *workinfo_height_root; |
|
static K_LIST *workinfo_free; |
|
static K_STORE *workinfo_store; |
|
// one in the current block |
|
static K_ITEM *workinfo_current; |
|
// first workinfo of current block |
|
static tv_t last_bc; |
|
// current network diff |
|
static double current_ndiff; |
|
|
|
// SHARES shares.id.json={...} |
|
typedef struct shares { |
|
int64_t workinfoid; |
|
int64_t userid; |
|
char workername[TXT_BIG+1]; |
|
int32_t clientid; |
|
char enonce1[TXT_SML+1]; |
|
char nonce2[TXT_BIG+1]; |
|
char nonce[TXT_SML+1]; |
|
double diff; |
|
double sdiff; |
|
int32_t errn; |
|
char error[TXT_SML+1]; |
|
char secondaryuserid[TXT_SML+1]; |
|
HISTORYDATECONTROLFIELDS; |
|
} SHARES; |
|
|
|
#define ALLOC_SHARES 10000 |
|
#define LIMIT_SHARES 0 |
|
#define INIT_SHARES(_item) INIT_GENERIC(_item, shares) |
|
#define DATA_SHARES(_var, _item) DATA_GENERIC(_var, _item, shares, true) |
|
|
|
static K_TREE *shares_root; |
|
static K_LIST *shares_free; |
|
static K_STORE *shares_store; |
|
|
|
// SHAREERRORS shareerrors.id.json={...} |
|
typedef struct shareerrors { |
|
int64_t workinfoid; |
|
int64_t userid; |
|
char workername[TXT_BIG+1]; |
|
int32_t clientid; |
|
int32_t errn; |
|
char error[TXT_SML+1]; |
|
char secondaryuserid[TXT_SML+1]; |
|
HISTORYDATECONTROLFIELDS; |
|
} SHAREERRORS; |
|
|
|
#define ALLOC_SHAREERRORS 10000 |
|
#define LIMIT_SHAREERRORS 0 |
|
#define INIT_SHAREERRORS(_item) INIT_GENERIC(_item, shareerrors) |
|
#define DATA_SHAREERRORS(_var, _item) DATA_GENERIC(_var, _item, shareerrors, true) |
|
|
|
static K_TREE *shareerrors_root; |
|
static K_LIST *shareerrors_free; |
|
static K_STORE *shareerrors_store; |
|
|
|
// SHARESUMMARY |
|
typedef struct sharesummary { |
|
int64_t userid; |
|
char workername[TXT_BIG+1]; |
|
int64_t workinfoid; |
|
double diffacc; |
|
double diffsta; |
|
double diffdup; |
|
double diffhi; |
|
double diffrej; |
|
double shareacc; |
|
double sharesta; |
|
double sharedup; |
|
double sharehi; |
|
double sharerej; |
|
int64_t sharecount; |
|
int64_t errorcount; |
|
int64_t countlastupdate; // non-DB field |
|
bool inserted; // non-DB field |
|
bool saveaged; // non-DB field |
|
bool reset; // non-DB field |
|
tv_t firstshare; |
|
tv_t lastshare; |
|
double lastdiffacc; |
|
char complete[TXT_FLAG+1]; |
|
MODIFYDATECONTROLFIELDS; |
|
} SHARESUMMARY; |
|
|
|
/* After this many shares added, we need to update the DB record |
|
The DB record is added with the 1st share */ |
|
#define SHARESUMMARY_UPDATE_EVERY 10 |
|
|
|
#define ALLOC_SHARESUMMARY 10000 |
|
#define LIMIT_SHARESUMMARY 0 |
|
#define INIT_SHARESUMMARY(_item) INIT_GENERIC(_item, sharesummary) |
|
#define DATA_SHARESUMMARY(_var, _item) DATA_GENERIC(_var, _item, sharesummary, true) |
|
#define DATA_SHARESUMMARY_NULL(_var, _item) DATA_GENERIC(_var, _item, sharesummary, false) |
|
|
|
#define SUMMARY_NEW 'n' |
|
#define SUMMARY_COMPLETE 'a' |
|
#define SUMMARY_CONFIRM 'y' |
|
|
|
static K_TREE *sharesummary_root; |
|
static K_TREE *sharesummary_workinfoid_root; |
|
static K_LIST *sharesummary_free; |
|
static K_STORE *sharesummary_store; |
|
|
|
// BLOCKS block.id.json={...} |
|
typedef struct blocks { |
|
int32_t height; |
|
char blockhash[TXT_BIG+1]; |
|
int64_t workinfoid; |
|
int64_t userid; |
|
char workername[TXT_BIG+1]; |
|
int32_t clientid; |
|
char enonce1[TXT_SML+1]; |
|
char nonce2[TXT_BIG+1]; |
|
char nonce[TXT_SML+1]; |
|
int64_t reward; |
|
char confirmed[TXT_FLAG+1]; |
|
double diffacc; |
|
double diffinv; |
|
double shareacc; |
|
double shareinv; |
|
int64_t elapsed; |
|
char statsconfirmed[TXT_FLAG+1]; |
|
HISTORYDATECONTROLFIELDS; |
|
} BLOCKS; |
|
|
|
#define ALLOC_BLOCKS 100 |
|
#define LIMIT_BLOCKS 0 |
|
#define INIT_BLOCKS(_item) INIT_GENERIC(_item, blocks) |
|
#define DATA_BLOCKS(_var, _item) DATA_GENERIC(_var, _item, blocks, true) |
|
#define DATA_BLOCKS_NULL(_var, _item) DATA_GENERIC(_var, _item, blocks, false) |
|
|
|
#define BLOCKS_NEW 'n' |
|
#define BLOCKS_NEW_STR "n" |
|
#define BLOCKS_CONFIRM '1' |
|
#define BLOCKS_CONFIRM_STR "1" |
|
#define BLOCKS_42 'F' |
|
#define BLOCKS_42_STR "F" |
|
#define BLOCKS_ORPHAN 'O' |
|
#define BLOCKS_ORPHAN_STR "O" |
|
|
|
#define BLOCKS_STATSPENDING FALSE_CHR |
|
#define BLOCKS_STATSPENDING_STR FALSE_STR |
|
#define BLOCKS_STATSCONFIRMED TRUE_CHR |
|
#define BLOCKS_STATSCONFIRMED_STR TRUE_STR |
|
|
|
static const char *blocks_new = "New"; |
|
static const char *blocks_confirm = "1-Confirm"; |
|
static const char *blocks_42 = "42-Confirm"; |
|
static const char *blocks_orphan = "Orphan"; |
|
static const char *blocks_unknown = "?Unknown?"; |
|
|
|
#define KANO -27972 |
|
|
|
static K_TREE *blocks_root; |
|
static K_LIST *blocks_free; |
|
static K_STORE *blocks_store; |
|
|
|
// MININGPAYOUTS |
|
typedef struct miningpayouts { |
|
int64_t miningpayoutid; |
|
int64_t userid; |
|
int32_t height; |
|
char blockhash[TXT_BIG+1]; |
|
int64_t amount; |
|
HISTORYDATECONTROLFIELDS; |
|
} MININGPAYOUTS; |
|
|
|
#define ALLOC_MININGPAYOUTS 1000 |
|
#define LIMIT_MININGPAYOUTS 0 |
|
#define INIT_MININGPAYOUTS(_item) INIT_GENERIC(_item, miningpayouts) |
|
#define DATA_MININGPAYOUTS(_var, _item) DATA_GENERIC(_var, _item, miningpayouts, true) |
|
|
|
static K_TREE *miningpayouts_root; |
|
static K_LIST *miningpayouts_free; |
|
static K_STORE *miningpayouts_store; |
|
|
|
/* |
|
// EVENTLOG |
|
typedef struct eventlog { |
|
int64_t eventlogid; |
|
char poolinstance[TXT_BIG+1]; |
|
char eventlogcode[TXT_SML+1]; |
|
char *eventlogdescription; |
|
HISTORYDATECONTROLFIELDS; |
|
} EVENTLOG; |
|
|
|
#define ALLOC_EVENTLOG 100 |
|
#define LIMIT_EVENTLOG 0 |
|
#define INIT_EVENTLOG(_item) INIT_GENERIC(_item, eventlog) |
|
#define DATA_EVENTLOG(_var, _item) DATA_GENERIC(_var, _item, eventlog, true) |
|
|
|
static K_TREE *eventlog_root; |
|
static K_LIST *eventlog_free; |
|
static K_STORE *eventlog_store; |
|
*/ |
|
|
|
// AUTHS authorise.id.json={...} |
|
typedef struct auths { |
|
int64_t authid; |
|
char poolinstance[TXT_BIG+1]; |
|
int64_t userid; |
|
char workername[TXT_BIG+1]; |
|
int32_t clientid; |
|
char enonce1[TXT_SML+1]; |
|
char useragent[TXT_BIG+1]; |
|
char preauth[TXT_FLAG+1]; |
|
HISTORYDATECONTROLFIELDS; |
|
} AUTHS; |
|
|
|
#define ALLOC_AUTHS 1000 |
|
#define LIMIT_AUTHS 0 |
|
#define INIT_AUTHS(_item) INIT_GENERIC(_item, auths) |
|
#define DATA_AUTHS(_var, _item) DATA_GENERIC(_var, _item, auths, true) |
|
|
|
static K_TREE *auths_root; |
|
static K_LIST *auths_free; |
|
static K_STORE *auths_store; |
|
|
|
// POOLSTATS poolstats.id.json={...} |
|
// Store every > 9.5m? |
|
// TODO: redo like userstats, but every 10min |
|
#define STATS_PER (9.5*60.0) |
|
|
|
typedef struct poolstats { |
|
char poolinstance[TXT_BIG+1]; |
|
int64_t elapsed; |
|
int32_t users; |
|
int32_t workers; |
|
double hashrate; |
|
double hashrate5m; |
|
double hashrate1hr; |
|
double hashrate24hr; |
|
bool stored; // Non-db field |
|
SIMPLEDATECONTROLFIELDS; |
|
} POOLSTATS; |
|
|
|
#define ALLOC_POOLSTATS 10000 |
|
#define LIMIT_POOLSTATS 0 |
|
#define INIT_POOLSTATS(_item) INIT_GENERIC(_item, poolstats) |
|
#define DATA_POOLSTATS(_var, _item) DATA_GENERIC(_var, _item, poolstats, true) |
|
#define DATA_POOLSTATS_NULL(_var, _item) DATA_GENERIC(_var, _item, poolstats, false) |
|
|
|
static K_TREE *poolstats_root; |
|
static K_LIST *poolstats_free; |
|
static K_STORE *poolstats_store; |
|
|
|
// USERSTATS userstats.id.json={...} |
|
// Pool sends each user (staggered) once per 10m |
|
typedef struct userstats { |
|
char poolinstance[TXT_BIG+1]; |
|
int64_t userid; |
|
char workername[TXT_BIG+1]; |
|
int64_t elapsed; |
|
double hashrate; |
|
double hashrate5m; |
|
double hashrate1hr; |
|
double hashrate24hr; |
|
bool idle; // Non-db field |
|
char summarylevel[TXT_FLAG+1]; // Initially SUMMARY_NONE in RAM |
|
int32_t summarycount; |
|
tv_t statsdate; |
|
SIMPLEDATECONTROLFIELDS; |
|
} USERSTATS; |
|
|
|
/* USERSTATS protocol includes a boolean 'eos' that when true, |
|
* we have received the full set of data for the given |
|
* createdate batch, and thus can move all (complete) records |
|
* matching the createdate from userstats_eos_store into the tree */ |
|
|
|
#define ALLOC_USERSTATS 10000 |
|
#define LIMIT_USERSTATS 0 |
|
#define INIT_USERSTATS(_item) INIT_GENERIC(_item, userstats) |
|
#define DATA_USERSTATS(_var, _item) DATA_GENERIC(_var, _item, userstats, true) |
|
#define DATA_USERSTATS_NULL(_var, _item) DATA_GENERIC(_var, _item, userstats, false) |
|
|
|
static K_TREE *userstats_root; |
|
static K_TREE *userstats_statsdate_root; // ordered by statsdate first |
|
static K_TREE *userstats_workerstatus_root; // during data load |
|
static K_LIST *userstats_free; |
|
static K_STORE *userstats_store; |
|
// Awaiting EOS |
|
static K_STORE *userstats_eos_store; |
|
// Temporary while summarising |
|
static K_STORE *userstats_summ; |
|
|
|
/* 1.5 x how often we expect to get user's stats from ckpool |
|
* This is used when grouping the sub-worker stats into a single user |
|
* We add each worker's latest stats to the total - except we ignore |
|
* any worker with newest stats being older than USERSTATS_PER_S */ |
|
#define USERSTATS_PER_S 900 |
|
|
|
/* on the allusers page, show any with stats in the last ... */ |
|
#define ALLUSERS_LIMIT_S 3600 |
|
|
|
#define SUMMARY_NONE '0' |
|
#define SUMMARY_DB '1' |
|
#define SUMMARY_FULL '2' |
|
|
|
/* Userstats get stored in the DB for each time band of this |
|
* amount from midnight (UTC+00) |
|
* Thus we simply put each stats value in the time band of the |
|
* stat's timestamp |
|
* Userstats are sumarised in the the same userstats table |
|
* If USERSTATS_DB_S is close to the expected time per USERSTATS |
|
* then it will have higher variance i.e. obviously: a higher |
|
* average of stats per sample will mean a lower SD of the number |
|
* of stats per sample |
|
* The #if below ensures USERSTATS_DB_S times an integer = a day |
|
* so the last band is the same size as the rest - |
|
* and will graph easily |
|
* Obvious WARNING - the smaller this is, the more stats in the DB |
|
* This is summary level '1' |
|
*/ |
|
#define USERSTATS_DB_S 3600 |
|
|
|
#if (((24*60*60) % USERSTATS_DB_S) != 0) |
|
#error "USERSTATS_DB_S times an integer must = a day" |
|
#endif |
|
|
|
#if ((24*60*60) < USERSTATS_DB_S) |
|
#error "USERSTATS_DB_S must be at most 1 day" |
|
#endif |
|
|
|
/* We summarise and discard userstats that are older than the |
|
* maximum of USERSTATS_DB_S, USERSTATS_PER_S, ALLUSERS_LIMIT_S |
|
*/ |
|
#if (USERSTATS_PER_S > ALLUSERS_LIMIT_S) |
|
#if (USERSTATS_PER_S > USERSTATS_DB_S) |
|
#define USERSTATS_AGE USERSTATS_PER_S |
|
#else |
|
#define USERSTATS_AGE USERSTATS_DB_S |
|
#endif |
|
#else |
|
#if (ALLUSERS_LIMIT_S > USERSTATS_DB_S) |
|
#define USERSTATS_AGE ALLUSERS_LIMIT_S |
|
#else |
|
#define USERSTATS_AGE USERSTATS_DB_S |
|
#endif |
|
#endif |
|
|
|
/* TODO: summarisation of the userstats after this many days are done |
|
* at the day level and the above stats are deleted from the db |
|
* Obvious WARNING - the larger this is, the more stats in the DB |
|
* This is summary level '2' |
|
*/ |
|
#define USERSTATS_DB_D 7 |
|
#define USERSTATS_DB_DS (USERSTATS_DB_D * (60*60*24)) |
|
|
|
// true if _new is newer, i.e. _old is before _new |
|
#define tv_newer(_old, _new) (((_old)->tv_sec == (_new)->tv_sec) ? \ |
|
((_old)->tv_usec < (_new)->tv_usec) : \ |
|
((_old)->tv_sec < (_new)->tv_sec)) |
|
#define tv_equal(_a, _b) (((_a)->tv_sec == (_b)->tv_sec) && \ |
|
((_a)->tv_usec == (_b)->tv_usec)) |
|
|
|
// WORKERSTATUS from various incoming data |
|
typedef struct workerstatus { |
|
int64_t userid; |
|
char workername[TXT_BIG+1]; |
|
tv_t last_auth; |
|
tv_t last_share; |
|
double last_diff; |
|
tv_t last_stats; |
|
tv_t last_idle; |
|
// Below gets reset on each block |
|
double diffacc; |
|
double diffinv; // Non-acc |
|
double shareacc; |
|
double shareinv; // Non-acc |
|
} WORKERSTATUS; |
|
|
|
#define ALLOC_WORKERSTATUS 1000 |
|
#define LIMIT_WORKERSTATUS 0 |
|
#define INIT_WORKERSTATUS(_item) INIT_GENERIC(_item, workerstatus) |
|
#define DATA_WORKERSTATUS(_var, _item) DATA_GENERIC(_var, _item, workerstatus, true) |
|
|
|
static K_TREE *workerstatus_root; |
|
static K_LIST *workerstatus_free; |
|
static K_STORE *workerstatus_store; |
|
|
|
static void _txt_to_data(enum data_type typ, char *nam, char *fld, void *data, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
char *tmp; |
|
|
|
switch (typ) { |
|
case TYPE_STR: |
|
// A database field being bigger than local storage is a fatal error |
|
if (siz < (strlen(fld)+1)) { |
|
quithere(1, "Field %s structure size %d is smaller than db %d" WHERE_FFL, |
|
nam, (int)siz, (int)strlen(fld)+1, WHERE_FFL_PASS); |
|
} |
|
strcpy((char *)data, fld); |
|
break; |
|
case TYPE_BIGINT: |
|
if (siz != sizeof(int64_t)) { |
|
quithere(1, "Field %s bigint incorrect structure size %d - should be %d" |
|
WHERE_FFL, |
|
nam, (int)siz, (int)sizeof(int64_t), WHERE_FFL_PASS); |
|
} |
|
*((long long *)data) = atoll(fld); |
|
break; |
|
case TYPE_INT: |
|
if (siz != sizeof(int32_t)) { |
|
quithere(1, "Field %s int incorrect structure size %d - should be %d" |
|
WHERE_FFL, |
|
nam, (int)siz, (int)sizeof(int32_t), WHERE_FFL_PASS); |
|
} |
|
*((int32_t *)data) = atoi(fld); |
|
break; |
|
case TYPE_TV: |
|
if (siz != sizeof(tv_t)) { |
|
quithere(1, "Field %s tv_t incorrect structure size %d - should be %d" |
|
WHERE_FFL, |
|
nam, (int)siz, (int)sizeof(tv_t), WHERE_FFL_PASS); |
|
} |
|
unsigned int yyyy, mm, dd, HH, MM, SS, uS = 0, tz; |
|
char pm[2]; |
|
struct tm tm; |
|
time_t tim; |
|
int n; |
|
n = sscanf(fld, "%u-%u-%u %u:%u:%u%1[+-]%u", |
|
&yyyy, &mm, &dd, &HH, &MM, &SS, pm, &tz); |
|
if (n != 8) { |
|
// allow uS |
|
n = sscanf(fld, "%u-%u-%u %u:%u:%u.%u%1[+-]%u", |
|
&yyyy, &mm, &dd, &HH, &MM, &SS, &uS, pm, &tz); |
|
if (n != 9) { |
|
quithere(1, "Field %s tv_t unhandled date '%s' (%d)" WHERE_FFL, |
|
nam, fld, n, WHERE_FFL_PASS); |
|
} |
|
} |
|
tm.tm_sec = (int)SS; |
|
tm.tm_min = (int)MM; |
|
tm.tm_hour = (int)HH; |
|
tm.tm_mday = (int)dd; |
|
tm.tm_mon = (int)mm - 1; |
|
tm.tm_year = (int)yyyy - 1900; |
|
tm.tm_isdst = -1; |
|
tim = timegm(&tm); |
|
if (tim > COMPARE_EXPIRY) { |
|
((tv_t *)data)->tv_sec = default_expiry.tv_sec; |
|
((tv_t *)data)->tv_usec = default_expiry.tv_usec; |
|
} else { |
|
// 2 digit tz (vs 4 digit) |
|
if (tz < 25) |
|
tz *= 60; |
|
// time was converted ignoring tz - so correct it |
|
if (pm[0] == '-') |
|
tim += 60 * tz; |
|
else |
|
tim -= 60 * tz; |
|
((tv_t *)data)->tv_sec = tim; |
|
((tv_t *)data)->tv_usec = uS; |
|
} |
|
break; |
|
case TYPE_CTV: |
|
if (siz != sizeof(tv_t)) { |
|
quithere(1, "Field %s tv_t incorrect structure size %d - should be %d" |
|
WHERE_FFL, |
|
nam, (int)siz, (int)sizeof(tv_t), WHERE_FFL_PASS); |
|
} |
|
long sec, nsec; |
|
int c; |
|
// Caller test for tv_sec=0 for failure |
|
((tv_t *)data)->tv_sec = 0L; |
|
((tv_t *)data)->tv_usec = 0L; |
|
c = sscanf(fld, "%ld,%ld", &sec, &nsec); |
|
if (c > 0) { |
|
((tv_t *)data)->tv_sec = (time_t)sec; |
|
if (c > 1) |
|
((tv_t *)data)->tv_usec = (nsec + 500) / 1000; |
|
if (((tv_t *)data)->tv_sec >= COMPARE_EXPIRY) { |
|
((tv_t *)data)->tv_sec = default_expiry.tv_sec; |
|
((tv_t *)data)->tv_usec = default_expiry.tv_usec; |
|
} |
|
} |
|
break; |
|
case TYPE_BLOB: |
|
tmp = strdup(fld); |
|
if (!tmp) { |
|
quithere(1, "Field %s (%d) OOM" WHERE_FFL, |
|
nam, (int)strlen(fld), WHERE_FFL_PASS); |
|
} |
|
*((char **)data) = tmp; |
|
break; |
|
case TYPE_DOUBLE: |
|
if (siz != sizeof(double)) { |
|
quithere(1, "Field %s int incorrect structure size %d - should be %d" |
|
WHERE_FFL, |
|
nam, (int)siz, (int)sizeof(double), WHERE_FFL_PASS); |
|
} |
|
*((double *)data) = atof(fld); |
|
break; |
|
default: |
|
quithere(1, "Unknown field %s (%d) to convert" WHERE_FFL, |
|
nam, (int)typ, WHERE_FFL_PASS); |
|
break; |
|
} |
|
} |
|
|
|
#define txt_to_str(_nam, _fld, _data, _siz) _txt_to_str(_nam, _fld, _data, _siz, WHERE_FFL_HERE) |
|
#define txt_to_bigint(_nam, _fld, _data, _siz) _txt_to_bigint(_nam, _fld, _data, _siz, WHERE_FFL_HERE) |
|
#define txt_to_int(_nam, _fld, _data, _siz) _txt_to_int(_nam, _fld, _data, _siz, WHERE_FFL_HERE) |
|
#define txt_to_tv(_nam, _fld, _data, _siz) _txt_to_tv(_nam, _fld, _data, _siz, WHERE_FFL_HERE) |
|
#define txt_to_ctv(_nam, _fld, _data, _siz) _txt_to_ctv(_nam, _fld, _data, _siz, WHERE_FFL_HERE) |
|
#define txt_to_blob(_nam, _fld, _data) _txt_to_blob(_nam, _fld, _data, WHERE_FFL_HERE) |
|
#define txt_to_double(_nam, _fld, _data, _siz) _txt_to_double(_nam, _fld, _data, _siz, WHERE_FFL_HERE) |
|
|
|
// N.B. STRNCPY* macros truncate, whereas this aborts ckdb if src > trg |
|
static void _txt_to_str(char *nam, char *fld, char data[], size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
_txt_to_data(TYPE_STR, nam, fld, (void *)data, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
static void _txt_to_bigint(char *nam, char *fld, int64_t *data, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
_txt_to_data(TYPE_BIGINT, nam, fld, (void *)data, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
static void _txt_to_int(char *nam, char *fld, int32_t *data, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
_txt_to_data(TYPE_INT, nam, fld, (void *)data, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
static void _txt_to_tv(char *nam, char *fld, tv_t *data, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
_txt_to_data(TYPE_TV, nam, fld, (void *)data, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
// Convert msg S,nS to tv_t |
|
static void _txt_to_ctv(char *nam, char *fld, tv_t *data, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
_txt_to_data(TYPE_CTV, nam, fld, (void *)data, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
static void _txt_to_blob(char *nam, char *fld, char *data, WHERE_FFL_ARGS) |
|
{ |
|
_txt_to_data(TYPE_BLOB, nam, fld, (void *)(&data), 0, WHERE_FFL_PASS); |
|
} |
|
|
|
static void _txt_to_double(char *nam, char *fld, double *data, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
_txt_to_data(TYPE_DOUBLE, nam, fld, (void *)data, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
static char *_data_to_buf(enum data_type typ, void *data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
struct tm tm; |
|
|
|
if (!buf) { |
|
switch (typ) { |
|
case TYPE_STR: |
|
case TYPE_BLOB: |
|
siz = strlen((char *)data) + 1; |
|
break; |
|
case TYPE_BIGINT: |
|
siz = BIGINT_BUFSIZ; |
|
break; |
|
case TYPE_INT: |
|
siz = INT_BUFSIZ; |
|
break; |
|
case TYPE_TV: |
|
case TYPE_TVS: |
|
siz = DATE_BUFSIZ; |
|
break; |
|
case TYPE_CTV: |
|
siz = CDATE_BUFSIZ; |
|
break; |
|
case TYPE_DOUBLE: |
|
siz = DOUBLE_BUFSIZ; |
|
break; |
|
default: |
|
quithere(1, "Unknown field (%d) to convert" WHERE_FFL, |
|
(int)typ, WHERE_FFL_PASS); |
|
break; |
|
} |
|
|
|
buf = malloc(siz); |
|
if (!buf) |
|
quithere(1, "OOM (%d)" WHERE_FFL, (int)siz, WHERE_FFL_PASS); |
|
} |
|
|
|
switch (typ) { |
|
case TYPE_STR: |
|
case TYPE_BLOB: |
|
snprintf(buf, siz, "%s", (char *)data); |
|
break; |
|
case TYPE_BIGINT: |
|
snprintf(buf, siz, "%"PRId64, *((uint64_t *)data)); |
|
break; |
|
case TYPE_INT: |
|
snprintf(buf, siz, "%"PRId32, *((uint32_t *)data)); |
|
break; |
|
case TYPE_TV: |
|
gmtime_r(&(((tv_t *)data)->tv_sec), &tm); |
|
snprintf(buf, siz, "%d-%02d-%02d %02d:%02d:%02d.%06ld+00", |
|
tm.tm_year + 1900, |
|
tm.tm_mon + 1, |
|
tm.tm_mday, |
|
tm.tm_hour, |
|
tm.tm_min, |
|
tm.tm_sec, |
|
(((tv_t *)data)->tv_usec)); |
|
break; |
|
case TYPE_CTV: |
|
snprintf(buf, siz, "%ld,%ld", |
|
(((tv_t *)data)->tv_sec), |
|
(((tv_t *)data)->tv_usec)); |
|
break; |
|
case TYPE_TVS: |
|
snprintf(buf, siz, "%ld", (((tv_t *)data)->tv_sec)); |
|
break; |
|
case TYPE_DOUBLE: |
|
snprintf(buf, siz, "%f", *((double *)data)); |
|
break; |
|
} |
|
|
|
return buf; |
|
} |
|
|
|
#define str_to_buf(_data, _buf, _siz) _str_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) |
|
#define bigint_to_buf(_data, _buf, _siz) _bigint_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) |
|
#define int_to_buf(_data, _buf, _siz) _int_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) |
|
#define tv_to_buf(_data, _buf, _siz) _tv_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) |
|
#define ctv_to_buf(_data, _buf, _siz) _ctv_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) |
|
#define tvs_to_buf(_data, _buf, _siz) _tvs_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) |
|
//#define blob_to_buf(_data, _buf, _siz) _blob_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) |
|
#define double_to_buf(_data, _buf, _siz) _double_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) |
|
|
|
static char *_str_to_buf(char data[], char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_STR, (void *)data, buf, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
static char *_bigint_to_buf(int64_t data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_BIGINT, (void *)(&data), buf, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
static char *_int_to_buf(int32_t data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_INT, (void *)(&data), buf, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
static char *_tv_to_buf(tv_t *data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_TV, (void *)data, buf, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
// Convert tv to S,uS |
|
static char *_ctv_to_buf(tv_t *data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_CTV, (void *)data, buf, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
// Convert tv to seconds (ignore uS) |
|
static char *_tvs_to_buf(tv_t *data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_TVS, (void *)data, buf, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
/* unused yet |
|
static char *_blob_to_buf(char *data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_BLOB, (void *)data, buf, siz, WHERE_FFL_PASS); |
|
} |
|
*/ |
|
|
|
static char *_double_to_buf(double data, char *buf, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
return _data_to_buf(TYPE_DOUBLE, (void *)(&data), buf, siz, WHERE_FFL_PASS); |
|
} |
|
|
|
static char logname[512]; |
|
static char *dbcode; |
|
|
|
// CCLs are every ... |
|
#define ROLL_S 3600 |
|
|
|
#define LOGQUE(_msg) log_queue_message(_msg) |
|
#define LOGFILE(_msg) rotating_log_nolock(_msg) |
|
#define LOGDUP "dup." |
|
|
|
// low spec version of rotating_log() - no locking |
|
static bool rotating_log_nolock(char *msg) |
|
{ |
|
char *filename; |
|
FILE *fp; |
|
bool ok = false; |
|
|
|
filename = rotating_filename(logname, time(NULL)); |
|
fp = fopen(filename, "a+e"); |
|
if (unlikely(!fp)) { |
|
LOGERR("Failed to fopen %s in rotating_log!", filename); |
|
goto stageleft; |
|
} |
|
fprintf(fp, "%s\n", msg); |
|
fclose(fp); |
|
ok = true; |
|
|
|
stageleft: |
|
free(filename); |
|
|
|
return ok; |
|
} |
|
|
|
static void log_queue_message(char *msg) |
|
{ |
|
K_ITEM *lq_item; |
|
LOGQUEUE *lq; |
|
|
|
K_WLOCK(logqueue_free); |
|
lq_item = k_unlink_head(logqueue_free); |
|
DATA_LOGQUEUE(lq, lq_item); |
|
lq->msg = strdup(msg); |
|
k_add_tail(logqueue_store, lq_item); |
|
K_WUNLOCK(logqueue_free); |
|
} |
|
|
|
void logmsg(int loglevel, const char *fmt, ...) |
|
{ |
|
int logfd = 0; |
|
char *buf = NULL; |
|
struct tm tm; |
|
time_t now_t; |
|
va_list ap; |
|
char stamp[128]; |
|
char *extra = EMPTY; |
|
|
|
if (loglevel > global_ckp->loglevel) |
|
return; |
|
|
|
now_t = time(NULL); |
|
localtime_r(&now_t, &tm); |
|
snprintf(stamp, sizeof(stamp), |
|
"[%d-%02d-%02d %02d:%02d:%02d]", |
|
tm.tm_year + 1900, |
|
tm.tm_mon + 1, |
|
tm.tm_mday, |
|
tm.tm_hour, |
|
tm.tm_min, |
|
tm.tm_sec); |
|
|
|
if (!fmt) { |
|
fprintf(stderr, "%s %s() called without fmt\n", stamp, __func__); |
|
return; |
|
} |
|
|
|
if (!global_ckp) |
|
extra = " !!NULL global_ckp!!"; |
|
else |
|
logfd = global_ckp->logfd; |
|
|
|
va_start(ap, fmt); |
|
VASPRINTF(&buf, fmt, ap); |
|
va_end(ap); |
|
|
|
if (logfd) { |
|
FILE *LOGFP = global_ckp->logfp; |
|
|
|
flock(logfd, LOCK_EX); |
|
fprintf(LOGFP, "%s %s", stamp, buf); |
|
if (loglevel <= LOG_ERR && errno != 0) |
|
fprintf(LOGFP, " with errno %d: %s", errno, strerror(errno)); |
|
errno = 0; |
|
fprintf(LOGFP, "\n"); |
|
flock(logfd, LOCK_UN); |
|
} |
|
if (loglevel <= LOG_WARNING) { |
|
if (loglevel <= LOG_ERR && errno != 0) { |
|
fprintf(stderr, "%s %s with errno %d: %s%s\n", |
|
stamp, buf, errno, strerror(errno), extra); |
|
errno = 0; |
|
} else |
|
fprintf(stderr, "%s %s%s\n", stamp, buf, extra); |
|
fflush(stderr); |
|
} |
|
free(buf); |
|
} |
|
|
|
static void setnow(tv_t *now) |
|
{ |
|
ts_t spec; |
|
spec.tv_sec = 0; |
|
spec.tv_nsec = 0; |
|
clock_gettime(CLOCK_REALTIME, &spec); |
|
now->tv_sec = spec.tv_sec; |
|
now->tv_usec = spec.tv_nsec / 1000; |
|
} |
|
|
|
// Limits are all +/-1s since on the live machine all were well within that |
|
static void check_createdate_ccl(char *cmd, tv_t *cd) |
|
{ |
|
static tv_t last_cd; |
|
static char last_cmd[CMD_SIZ+1]; |
|
char cd_buf1[DATE_BUFSIZ], cd_buf2[DATE_BUFSIZ]; |
|
char *filename; |
|
double td; |
|
|
|
if (cd->tv_sec < reload_timestamp.tv_sec || |
|
cd->tv_sec >= (reload_timestamp.tv_sec + ROLL_S)) { |
|
ccl_mismatch_abs++; |
|
td = tvdiff(cd, &reload_timestamp); |
|
if (td < -1 || td > ROLL_S + 1) { |
|
ccl_mismatch++; |
|
filename = rotating_filename("", reload_timestamp.tv_sec); |
|
tv_to_buf(cd, cd_buf1, sizeof(cd_buf1)); |
|
LOGERR("%s(): CCL contains mismatch data: cmd:%s CCL:%.10s cd:%s", |
|
__func__, cmd, filename, cd_buf1); |
|
free(filename); |
|
} |
|
if (ccl_mismatch_min > td) |
|
ccl_mismatch_min = td; |
|
if (ccl_mismatch_max < td) |
|
ccl_mismatch_max = td; |
|
} |
|
|
|
td = tvdiff(cd, &last_cd); |
|
if (td < 0.0) { |
|
ccl_unordered_abs++; |
|
if (ccl_unordered_most > td) |
|
ccl_unordered_most = td; |
|
} |
|
if (td < -1.0) { |
|
ccl_unordered++; |
|
tv_to_buf(&last_cd, cd_buf1, sizeof(cd_buf1)); |
|
tv_to_buf(cd, cd_buf2, sizeof(cd_buf2)); |
|
LOGERR("%s(): CCL time unordered: %s<->%s %ld,%ld<->%ld,%ld %s<->%s", |
|
__func__, last_cmd, cmd, last_cd.tv_sec,last_cd.tv_usec, |
|
cd->tv_sec, cd->tv_usec, cd_buf1, cd_buf2); |
|
} |
|
|
|
copy_tv(&last_cd, cd); |
|
STRNCPY(last_cmd, cmd); |
|
} |
|
|
|
#define CKPQ_READ true |
|
#define CKPQ_WRITE false |
|
|
|
#define CKPQexec(_conn, _qry, _isread) _CKPQexec(_conn, _qry, _isread, WHERE_FFL_HERE) |
|
|
|
// Bug check to ensure no unexpected write txns occur |
|
static PGresult *_CKPQexec(PGconn *conn, const char *qry, bool isread, WHERE_FFL_ARGS) |
|
{ |
|
// It would slow it down, but could check qry for insert/update/... |
|
if (!isread && confirm_sharesummary) |
|
quitfrom(1, file, func, line, "BUG: write txn during confirm"); |
|
|
|
return PQexec(conn, qry); |
|
} |
|
|
|
#define CKPQexecParams(_conn, _qry, _p1, _p2, _p3, _p4, _p5, _p6, _isread) \ |
|
_CKPQexecParams(_conn, _qry, _p1, _p2, _p3, _p4, _p5, _p6, \ |
|
_isread, WHERE_FFL_HERE) |
|
|
|
static PGresult *_CKPQexecParams(PGconn *conn, const char *qry, |
|
int nParams, |
|
const Oid *paramTypes, |
|
const char *const * paramValues, |
|
const int *paramLengths, |
|
const int *paramFormats, |
|
int resultFormat, |
|
bool isread, WHERE_FFL_ARGS) |
|
{ |
|
// It would slow it down, but could check qry for insert/update/... |
|
if (!isread && confirm_sharesummary) |
|
quitfrom(1, file, func, line, "BUG: write txn during confirm"); |
|
|
|
return PQexecParams(conn, qry, nParams, paramTypes, paramValues, paramLengths, |
|
paramFormats, resultFormat); |
|
} |
|
|
|
// Force use CKPQ... for PQ functions in use |
|
#define PQexec CKPQexec |
|
#define PQexecParams CKPQexecParams |
|
|
|
static uint64_t ticks; |
|
static time_t last_tick; |
|
|
|
static void tick() |
|
{ |
|
time_t now; |
|
char ch; |
|
|
|
now = time(NULL); |
|
if (now != last_tick) { |
|
last_tick = now; |
|
ch = status_chars[ticks++ & 0x3]; |
|
putchar(ch); |
|
putchar('\r'); |
|
fflush(stdout); |
|
} |
|
} |
|
|
|
static void dsp_transfer(K_ITEM *item, FILE *stream) |
|
{ |
|
TRANSFER *t; |
|
|
|
if (!item) |
|
fprintf(stream, "%s() called with (null) item\n", __func__); |
|
else { |
|
DATA_TRANSFER(t, item); |
|
fprintf(stream, " name='%s' mvalue='%s' malloc=%c\n", |
|
t->name, t->mvalue, |
|
(t->svalue == t->mvalue) ? 'N' : 'Y'); |
|
} |
|
} |
|
|
|
// order by name asc |
|
static cmp_t cmp_transfer(K_ITEM *a, K_ITEM *b) |
|
{ |
|
TRANSFER *ta, *tb; |
|
DATA_TRANSFER(ta, a); |
|
DATA_TRANSFER(tb, b); |
|
return CMP_STR(ta->name, tb->name); |
|
} |
|
|
|
static K_ITEM *find_transfer(K_TREE *trf_root, char *name) |
|
{ |
|
TRANSFER transfer; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look; |
|
|
|
STRNCPY(transfer.name, name); |
|
INIT_TRANSFER(&look); |
|
look.data = (void *)(&transfer); |
|
return find_in_ktree(trf_root, &look, cmp_transfer, ctx); |
|
} |
|
|
|
#define optional_name(_root, _name, _len, _patt) \ |
|
_optional_name(_root, _name, _len, _patt, WHERE_FFL_HERE) |
|
|
|
static K_ITEM *_optional_name(K_TREE *trf_root, char *name, int len, char *patt, |
|
WHERE_FFL_ARGS) |
|
{ |
|
TRANSFER *trf; |
|
K_ITEM *item; |
|
char *mvalue; |
|
regex_t re; |
|
size_t dlen; |
|
int ret; |
|
|
|
item = find_transfer(trf_root, name); |
|
if (!item) |
|
return NULL; |
|
|
|
DATA_TRANSFER(trf, item); |
|
mvalue = trf->mvalue; |
|
if (mvalue) |
|
dlen = strlen(mvalue); |
|
else |
|
dlen = 0; |
|
if (!mvalue || (int)dlen < len) { |
|
if (!mvalue) { |
|
LOGERR("%s(): field '%s' NULL (%d) from %s():%d", |
|
__func__, name, (int)dlen, len, func, line); |
|
} |
|
return NULL; |
|
} |
|
|
|
if (patt) { |
|
if (regcomp(&re, patt, REG_NOSUB) != 0) |
|
return NULL; |
|
|
|
ret = regexec(&re, mvalue, (size_t)0, NULL, 0); |
|
regfree(&re); |
|
|
|
if (ret != 0) |
|
return NULL; |
|
} |
|
|
|
return item; |
|
} |
|
|
|
#define require_name(_root, _name, _len, _patt, _reply, _siz) \ |
|
_require_name(_root, _name, _len, _patt, _reply, \ |
|
_siz, WHERE_FFL_HERE) |
|
|
|
static K_ITEM *_require_name(K_TREE *trf_root, char *name, int len, char *patt, |
|
char *reply, size_t siz, WHERE_FFL_ARGS) |
|
{ |
|
TRANSFER *trf; |
|
K_ITEM *item; |
|
char *mvalue; |
|
regex_t re; |
|
size_t dlen; |
|
int ret; |
|
|
|
item = find_transfer(trf_root, name); |
|
if (!item) { |
|
LOGERR("%s(): failed, field '%s' missing from %s():%d", |
|
__func__, name, func, line); |
|
snprintf(reply, siz, "failed.missing %s", name); |
|
return NULL; |
|
} |
|
|
|
DATA_TRANSFER(trf, item); |
|
mvalue = trf->mvalue; |
|
if (mvalue) |
|
dlen = strlen(mvalue); |
|
else |
|
dlen = 0; |
|
if (!mvalue || (int)dlen < len) { |
|
LOGERR("%s(): failed, field '%s' short (%s%d<%d) from %s():%d", |
|
__func__, name, mvalue ? EMPTY : "null", |
|
(int)dlen, len, func, line); |
|
snprintf(reply, siz, "failed.short %s", name); |
|
return NULL; |
|
} |
|
|
|
if (patt) { |
|
if (regcomp(&re, patt, REG_NOSUB) != 0) { |
|
LOGERR("%s(): failed, field '%s' failed to" |
|
" compile patt from %s():%d", |
|
__func__, name, func, line); |
|
snprintf(reply, siz, "failed.REC %s", name); |
|
return NULL; |
|
} |
|
|
|
ret = regexec(&re, mvalue, (size_t)0, NULL, 0); |
|
regfree(&re); |
|
|
|
if (ret != 0) { |
|
LOGERR("%s(): failed, field '%s' invalid from %s():%d", |
|
__func__, name, func, line); |
|
snprintf(reply, siz, "failed.invalid %s", name); |
|
return NULL; |
|
} |
|
} |
|
|
|
return item; |
|
} |
|
|
|
static PGconn *dbconnect() |
|
{ |
|
char conninfo[128]; |
|
PGconn *conn; |
|
|
|
snprintf(conninfo, sizeof(conninfo), |
|
"host=127.0.0.1 dbname=%s user=%s%s%s", |
|
db_name, db_user, |
|
db_pass ? " password=" : "", |
|
db_pass ? db_pass : ""); |
|
|
|
conn = PQconnectdb(conninfo); |
|
if (PQstatus(conn) != CONNECTION_OK) |
|
quithere(1, "ERR: Failed to connect to db '%s'", pqerrmsg(conn)); |
|
|
|
return conn; |
|
} |
|
|
|
static int64_t nextid(PGconn *conn, char *idname, int64_t increment, |
|
tv_t *cd, char *by, char *code, char *inet) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
char qry[1024]; |
|
char *params[5]; |
|
int par; |
|
int64_t lastid; |
|
char *field; |
|
bool ok; |
|
int n; |
|
|
|
lastid = 0; |
|
|
|
snprintf(qry, sizeof(qry), "select lastid from idcontrol " |
|
"where idname='%s' for update", |
|
idname); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexec(conn, qry, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
goto cleanup; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != 1) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, 1, n); |
|
goto cleanup; |
|
} |
|
|
|
n = PQntuples(res); |
|
if (n < 1) { |
|
LOGERR("%s(): No matching idname='%s'", __func__, idname); |
|
goto cleanup; |
|
} |
|
|
|
ok = true; |
|
PQ_GET_FLD(res, 0, "lastid", field, ok); |
|
if (!ok) |
|
goto cleanup; |
|
TXT_TO_BIGINT("lastid", field, lastid); |
|
|
|
PQclear(res); |
|
|
|
lastid += increment; |
|
snprintf(qry, sizeof(qry), "update idcontrol set " |
|
"lastid=$1, modifydate=$2, modifyby=$3, " |
|
"modifycode=$4, modifyinet=$5 " |
|
"where idname='%s'", |
|
idname); |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(lastid, NULL, 0); |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = str_to_buf(by, NULL, 0); |
|
params[par++] = str_to_buf(code, NULL, 0); |
|
params[par++] = str_to_buf(inet, NULL, 0); |
|
PARCHK(par, params); |
|
|
|
res = PQexecParams(conn, qry, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Update", rescode, conn); |
|
lastid = 0; |
|
} |
|
|
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
cleanup: |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
return lastid; |
|
} |
|
|
|
// order by userid asc,workername asc |
|
static cmp_t cmp_workerstatus(K_ITEM *a, K_ITEM *b) |
|
{ |
|
WORKERSTATUS *wa, *wb; |
|
DATA_WORKERSTATUS(wa, a); |
|
DATA_WORKERSTATUS(wb, b); |
|
cmp_t c = CMP_BIGINT(wa->userid, wb->userid); |
|
if (c == 0) |
|
c = CMP_STR(wa->workername, wb->workername); |
|
return c; |
|
} |
|
|
|
/* TODO: replace a lot of the code for all data types that codes finds, |
|
* each with specific functions for finding, to centralise the finds, |
|
* with passed ctx's */ |
|
static K_ITEM *get_workerstatus(int64_t userid, char *workername) |
|
{ |
|
WORKERSTATUS workerstatus; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look, *find; |
|
|
|
workerstatus.userid = userid; |
|
STRNCPY(workerstatus.workername, workername); |
|
|
|
INIT_WORKERSTATUS(&look); |
|
look.data = (void *)(&workerstatus); |
|
K_RLOCK(workerstatus_free); |
|
find = find_in_ktree(workerstatus_root, &look, cmp_workerstatus, ctx); |
|
K_RUNLOCK(workerstatus_free); |
|
return find; |
|
} |
|
|
|
/* Worker loading/creation calls this with create = true |
|
* All others with create = false since the workerstatus should exist |
|
* Failure is a code bug and a reported error, but handled anyway |
|
* This has 2 sets of file/func/line to allow 2 levels of traceback |
|
*/ |
|
static K_ITEM *_find_create_workerstatus(int64_t userid, char *workername, |
|
bool create, const char *file2, |
|
const char *func2, const int line2, |
|
WHERE_FFL_ARGS) |
|
{ |
|
WORKERSTATUS *row; |
|
K_ITEM *item; |
|
|
|
item = get_workerstatus(userid, workername); |
|
if (!item) { |
|
if (!create) { |
|
LOGEMERG("%s(): Missing workerstatus %"PRId64"/%s" |
|
WHERE_FFL WHERE_FFL, |
|
__func__, userid, workername, |
|
file2, func2, line2, WHERE_FFL_PASS); |
|
return NULL; |
|
} |
|
|
|
K_WLOCK(workerstatus_free); |
|
item = k_unlink_head(workerstatus_free); |
|
|
|
DATA_WORKERSTATUS(row, item); |
|
|
|
bzero(row, sizeof(*row)); |
|
row->userid = userid; |
|
STRNCPY(row->workername, workername); |
|
|
|
workerstatus_root = add_to_ktree(workerstatus_root, item, cmp_workerstatus); |
|
k_add_head(workerstatus_store, item); |
|
K_WUNLOCK(workerstatus_free); |
|
} |
|
return item; |
|
} |
|
|
|
#define find_create_workerstatus(_u, _w, _file, _func, _line) \ |
|
_find_create_workerstatus(_u, _w, true, _file, _func, _line, WHERE_FFL_HERE) |
|
#define find_workerstatus(_u, _w, _file, _func, _line) \ |
|
_find_create_workerstatus(_u, _w, false, _file, _func, _line, WHERE_FFL_HERE) |
|
|
|
static cmp_t cmp_userstats_workerstatus(K_ITEM *a, K_ITEM *b); |
|
static cmp_t cmp_sharesummary(K_ITEM *a, K_ITEM *b); |
|
|
|
static void zero_on_new_block() |
|
{ |
|
WORKERSTATUS *workerstatus; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM *ws_item; |
|
|
|
K_WLOCK(workerstatus_free); |
|
pool.diffacc = pool.diffinv = pool.shareacc = |
|
pool.shareinv = pool.best_sdiff = 0; |
|
ws_item = first_in_ktree(workerstatus_root, ctx); |
|
while (ws_item) { |
|
DATA_WORKERSTATUS(workerstatus, ws_item); |
|
workerstatus->diffacc = workerstatus->diffinv = |
|
workerstatus->shareacc = workerstatus->shareinv = 0.0; |
|
ws_item = next_in_ktree(ctx); |
|
} |
|
K_WUNLOCK(workerstatus_free); |
|
|
|
} |
|
|
|
/* Currently only used at the end of the startup |
|
* Will need to add locking if it's used, later, after startup completes */ |
|
static void set_block_share_counters() |
|
{ |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM *ss_item, ss_look, *ws_item; |
|
WORKERSTATUS *workerstatus; |
|
SHARESUMMARY *sharesummary, looksharesummary; |
|
|
|
INIT_SHARESUMMARY(&ss_look); |
|
|
|
zero_on_new_block(); |
|
|
|
ws_item = NULL; |
|
/* From the end backwards so we can skip the workinfoid's we don't |
|
* want by jumping back to just before the current worker when the |
|
* workinfoid goes below the limit */ |
|
K_RLOCK(sharesummary_free); |
|
ss_item = last_in_ktree(sharesummary_root, ctx); |
|
while (ss_item) { |
|
DATA_SHARESUMMARY(sharesummary, ss_item); |
|
if (sharesummary->workinfoid < pool.workinfoid) { |
|
// Skip back to the next worker |
|
looksharesummary.userid = sharesummary->userid; |
|
STRNCPY(looksharesummary.workername, |
|
sharesummary->workername); |
|
looksharesummary.workinfoid = -1; |
|
ss_look.data = (void *)(&looksharesummary); |
|
ss_item = find_before_in_ktree(sharesummary_root, &ss_look, |
|
cmp_sharesummary, ctx); |
|
continue; |
|
} |
|
|
|
/* Check for user/workername change for new workerstatus |
|
* The tree has user/workername grouped together in order |
|
* so this will only be once per user/workername */ |
|
if (!ws_item || |
|
sharesummary->userid != workerstatus->userid || |
|
strcmp(sharesummary->workername, workerstatus->workername)) { |
|
/* This is to trigger a console error if it is missing |
|
* since it should always exist |
|
* However, it is simplest to simply create it |
|
* and keep going */ |
|
K_RUNLOCK(sharesummary_free); |
|
ws_item = find_workerstatus(sharesummary->userid, |
|
sharesummary->workername, |
|
__FILE__, __func__, __LINE__); |
|
if (!ws_item) { |
|
ws_item = find_create_workerstatus(sharesummary->userid, |
|
sharesummary->workername, |
|
__FILE__, __func__, __LINE__); |
|
} |
|
K_RLOCK(sharesummary_free); |
|
DATA_WORKERSTATUS(workerstatus, ws_item); |
|
} |
|
|
|
pool.diffacc += sharesummary->diffacc; |
|
pool.diffinv += sharesummary->diffsta + sharesummary->diffdup + |
|
sharesummary->diffhi + sharesummary->diffrej; |
|
workerstatus->diffacc += sharesummary->diffacc; |
|
workerstatus->diffinv += sharesummary->diffsta + sharesummary->diffdup + |
|
sharesummary->diffhi + sharesummary->diffrej; |
|
workerstatus->shareacc += sharesummary->shareacc; |
|
workerstatus->shareinv += sharesummary->sharesta + sharesummary->sharedup + |
|
sharesummary->sharehi + sharesummary->sharerej; |
|
|
|
ss_item = prev_in_ktree(ctx); |
|
} |
|
K_RUNLOCK(sharesummary_free); |
|
} |
|
|
|
/* All data is loaded, now update workerstatus fields |
|
TODO: combine set_block_share_counters() with this? */ |
|
static void workerstatus_ready() |
|
{ |
|
K_TREE_CTX ws_ctx[1], us_ctx[1], ss_ctx[1]; |
|
K_ITEM *ws_item, us_look, ss_look, *us_item, *ss_item; |
|
USERSTATS lookuserstats, *userstats; |
|
SHARESUMMARY looksharesummary, *sharesummary; |
|
WORKERSTATUS *workerstatus; |
|
|
|
INIT_USERSTATS(&us_look); |
|
INIT_SHARESUMMARY(&ss_look); |
|
ws_item = first_in_ktree(workerstatus_root, ws_ctx); |
|
while (ws_item) { |
|
DATA_WORKERSTATUS(workerstatus, ws_item); |
|
lookuserstats.userid = workerstatus->userid; |
|
STRNCPY(lookuserstats.workername, workerstatus->workername); |
|
lookuserstats.statsdate.tv_sec = date_eot.tv_sec; |
|
lookuserstats.statsdate.tv_usec = date_eot.tv_usec; |
|
us_look.data = (void *)(&lookuserstats); |
|
us_item = find_before_in_ktree(userstats_workerstatus_root, &us_look, |
|
cmp_userstats_workerstatus, us_ctx); |
|
if (us_item) { |
|
DATA_USERSTATS(userstats, us_item); |
|
if (userstats->idle) { |
|
if (tv_newer(&(workerstatus->last_idle), |
|
&(userstats->statsdate))) { |
|
copy_tv(&(workerstatus->last_idle), |
|
&(userstats->statsdate)); |
|
} |
|
} else { |
|
if (tv_newer(&(workerstatus->last_stats), |
|
&(userstats->statsdate))) { |
|
copy_tv(&(workerstatus->last_stats), |
|
&(userstats->statsdate)); |
|
} |
|
} |
|
} |
|
|
|
looksharesummary.userid = workerstatus->userid; |
|
STRNCPY(looksharesummary.workername, workerstatus->workername); |
|
looksharesummary.workinfoid = MAXID; |
|
ss_look.data = (void *)(&looksharesummary); |
|
K_RLOCK(sharesummary_free); |
|
ss_item = find_before_in_ktree(sharesummary_root, &ss_look, |
|
cmp_sharesummary, ss_ctx); |
|
K_RUNLOCK(sharesummary_free); |
|
if (ss_item) { |
|
DATA_SHARESUMMARY(sharesummary, ss_item); |
|
if (tv_newer(&(workerstatus->last_share), |
|
&(sharesummary->lastshare))) { |
|
copy_tv(&(workerstatus->last_share), |
|
&(sharesummary->lastshare)); |
|
workerstatus->last_diff = |
|
sharesummary->lastdiffacc; |
|
} |
|
} |
|
|
|
ws_item = next_in_ktree(ws_ctx); |
|
} |
|
} |
|
|
|
#define workerstatus_update(_auths, _shares, _userstats) \ |
|
_workerstatus_update(_auths, _shares, _userstats, WHERE_FFL_HERE) |
|
|
|
static void _workerstatus_update(AUTHS *auths, SHARES *shares, |
|
USERSTATS *userstats, WHERE_FFL_ARGS) |
|
{ |
|
WORKERSTATUS *row; |
|
K_ITEM *item; |
|
|
|
if (auths) { |
|
item = find_workerstatus(auths->userid, auths->workername, |
|
file, func, line); |
|
if (item) { |
|
DATA_WORKERSTATUS(row, item); |
|
if (tv_newer(&(row->last_auth), &(auths->createdate))) |
|
copy_tv(&(row->last_auth), &(auths->createdate)); |
|
} |
|
} |
|
|
|
if (startup_complete && shares) { |
|
if (shares->errn == SE_NONE) { |
|
pool.diffacc += shares->diff; |
|
pool.shareacc++; |
|
} else { |
|
pool.diffinv += shares->diff; |
|
pool.shareinv++; |
|
} |
|
item = find_workerstatus(shares->userid, shares->workername, |
|
file, func, line); |
|
if (item) { |
|
DATA_WORKERSTATUS(row, item); |
|
if (tv_newer(&(row->last_share), &(shares->createdate))) { |
|
copy_tv(&(row->last_share), &(shares->createdate)); |
|
row->last_diff = shares->diff; |
|
} |
|
if (shares->errn == SE_NONE) { |
|
row->diffacc += shares->diff; |
|
row->shareacc++; |
|
} else { |
|
row->diffinv += shares->diff; |
|
row->shareinv++; |
|
} |
|
} |
|
} |
|
|
|
if (startup_complete && userstats) { |
|
item = find_workerstatus(userstats->userid, userstats->workername, |
|
file, func, line); |
|
if (item) { |
|
DATA_WORKERSTATUS(row, item); |
|
if (userstats->idle) { |
|
if (tv_newer(&(row->last_idle), &(userstats->statsdate))) |
|
copy_tv(&(row->last_idle), &(userstats->statsdate)); |
|
} else { |
|
if (tv_newer(&(row->last_stats), &(userstats->statsdate))) |
|
copy_tv(&(row->last_stats), &(userstats->statsdate)); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// default tree order by username asc,expirydate desc |
|
static cmp_t cmp_users(K_ITEM *a, K_ITEM *b) |
|
{ |
|
USERS *ua, *ub; |
|
DATA_USERS(ua, a); |
|
DATA_USERS(ub, b); |
|
cmp_t c = CMP_STR(ua->username, ub->username); |
|
if (c == 0) |
|
c = CMP_TV(ub->expirydate, ua->expirydate); |
|
return c; |
|
} |
|
|
|
// order by userid asc,expirydate desc |
|
static cmp_t cmp_userid(K_ITEM *a, K_ITEM *b) |
|
{ |
|
USERS *ua, *ub; |
|
DATA_USERS(ua, a); |
|
DATA_USERS(ub, b); |
|
cmp_t c = CMP_BIGINT(ua->userid, ub->userid); |
|
if (c == 0) |
|
c = CMP_TV(ub->expirydate, ua->expirydate); |
|
return c; |
|
} |
|
|
|
// Must be R or W locked before call |
|
static K_ITEM *find_users(char *username) |
|
{ |
|
USERS users; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look; |
|
|
|
STRNCPY(users.username, username); |
|
users.expirydate.tv_sec = default_expiry.tv_sec; |
|
users.expirydate.tv_usec = default_expiry.tv_usec; |
|
|
|
INIT_USERS(&look); |
|
look.data = (void *)(&users); |
|
return find_in_ktree(users_root, &look, cmp_users, ctx); |
|
} |
|
|
|
// Must be R or W locked before call |
|
static K_ITEM *find_userid(int64_t userid) |
|
{ |
|
USERS users; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look; |
|
|
|
users.userid = userid; |
|
users.expirydate.tv_sec = default_expiry.tv_sec; |
|
users.expirydate.tv_usec = default_expiry.tv_usec; |
|
|
|
INIT_USERS(&look); |
|
look.data = (void *)(&users); |
|
return find_in_ktree(userid_root, &look, cmp_userid, ctx); |
|
} |
|
|
|
// TODO: endian? |
|
static void make_salt(USERS *users) |
|
{ |
|
long int r1, r2, r3, r4; |
|
|
|
r1 = random(); |
|
r2 = random(); |
|
r3 = random(); |
|
r4 = random(); |
|
|
|
__bin2hex(users->salt, (void *)(&r1), 4); |
|
__bin2hex(users->salt+8, (void *)(&r2), 4); |
|
__bin2hex(users->salt+16, (void *)(&r3), 4); |
|
__bin2hex(users->salt+24, (void *)(&r4), 4); |
|
} |
|
|
|
static void password_hash(char *username, char *passwordhash, char *salt, char *result, size_t siz) |
|
{ |
|
char tohash[TXT_BIG+1]; |
|
char buf[TXT_BIG+1]; |
|
size_t len, tot; |
|
char why[1024]; |
|
|
|
if (siz < SHA256SIZHEX+1) { |
|
snprintf(why, sizeof(why), |
|
"target result too small (%d/%d)", |
|
(int)siz, SHA256SIZHEX+1); |
|
goto hashfail; |
|
} |
|
|
|
if (sizeof(buf) < SHA256SIZBIN) { |
|
snprintf(why, sizeof(why), |
|
"temporary target buf too small (%d/%d)", |
|
(int)sizeof(buf), SHA256SIZBIN); |
|
goto hashfail; |
|
} |
|
|
|
tot = len = strlen(passwordhash) / 2; |
|
if (len != SHA256SIZBIN) { |
|
snprintf(why, sizeof(why), |
|
"passwordhash wrong size (%d/%d)", |
|
(int)len, SHA256SIZBIN); |
|
goto hashfail; |
|
} |
|
if (len > sizeof(tohash)) { |
|
snprintf(why, sizeof(why), |
|
"temporary tohash too small (%d/%d)", |
|
(int)sizeof(tohash), (int)len); |
|
goto hashfail; |
|
} |
|
|
|
hex2bin(tohash, passwordhash, len); |
|
|
|
len = strlen(salt) / 2; |
|
if (len != SALTSIZBIN) { |
|
snprintf(why, sizeof(why), |
|
"salt wrong size (%d/%d)", |
|
(int)len, SALTSIZBIN); |
|
goto hashfail; |
|
} |
|
if ((tot + len) > sizeof(tohash)) { |
|
snprintf(why, sizeof(why), |
|
"passwordhash+salt too big (%d/%d)", |
|
(int)(tot + len), (int)sizeof(tohash)); |
|
goto hashfail; |
|
} |
|
|
|
hex2bin(tohash+tot, salt, len); |
|
tot += len; |
|
|
|
sha256((const unsigned char *)tohash, (unsigned int)tot, (unsigned char *)buf); |
|
|
|
__bin2hex(result, (void *)buf, SHA256SIZBIN); |
|
|
|
return; |
|
hashfail: |
|
LOGERR("%s() Failed to hash '%s' password: %s", |
|
__func__, username, why); |
|
result[0] = '\0'; |
|
} |
|
|
|
static bool check_hash(USERS *users, char *passwordhash) |
|
{ |
|
char hex[SHA256SIZHEX+1]; |
|
|
|
if (*(users->salt)) { |
|
password_hash(users->username, passwordhash, users->salt, hex, sizeof(hex)); |
|
return (strcasecmp(hex, users->passwordhash) == 0); |
|
} else |
|
return (strcasecmp(passwordhash, users->passwordhash) == 0); |
|
} |
|
|
|
static bool users_pass_email(PGconn *conn, K_ITEM *u_item, char *oldhash, |
|
char *newhash, char *email, char *by, char *code, |
|
char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
K_TREE_CTX ctx[1]; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n; |
|
USERS *row, *users; |
|
char *upd, *ins; |
|
bool ok = false; |
|
char *params[5 + HISTORYDATECOUNT]; |
|
bool hash; |
|
int par; |
|
|
|
LOGDEBUG("%s(): change", __func__); |
|
|
|
if (oldhash != NULL) |
|
hash = true; |
|
else |
|
hash = false; |
|
|
|
DATA_USERS(users, u_item); |
|
// i.e. if oldhash == EMPTY, just overwrite with new |
|
if (hash && oldhash != EMPTY && !check_hash(users, oldhash)) |
|
return false; |
|
|
|
K_WLOCK(users_free); |
|
item = k_unlink_head(users_free); |
|
K_WUNLOCK(users_free); |
|
|
|
DATA_USERS(row, item); |
|
memcpy(row, users, sizeof(*row)); |
|
// Update one, leave the other |
|
if (hash) { |
|
// New salt each password change |
|
make_salt(row); |
|
password_hash(row->username, newhash, row->salt, |
|
row->passwordhash, sizeof(row->passwordhash)); |
|
} else |
|
STRNCPY(row->emailaddress, email); |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
upd = "update users set expirydate=$1 where userid=$2 and expirydate=$3"; |
|
par = 0; |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); |
|
PARCHKVAL(par, 3, params); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
// Beginning of a write txn |
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
goto unparam; |
|
} |
|
PQclear(res); |
|
|
|
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Update", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
// Copy them both in - one will be new and one will be old |
|
params[par++] = str_to_buf(row->emailaddress, NULL, 0); |
|
params[par++] = str_to_buf(row->passwordhash, NULL, 0); |
|
// New salt for each password change (or recopy old) |
|
params[par++] = str_to_buf(row->salt, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHKVAL(par, 5 + HISTORYDATECOUNT, params); // 10 as per ins |
|
|
|
ins = "insert into users " |
|
"(userid,username,status,emailaddress,joineddate," |
|
"passwordhash,secondaryuserid,salt" |
|
HISTORYDATECONTROL ") select " |
|
"userid,username,status,$3,joineddate," |
|
"$4,secondaryuserid,$5," |
|
"$6,$7,$8,$9,$10 from users where " |
|
"userid=$1 and expirydate=$2"; |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
goto unparam; |
|
} |
|
|
|
res = PQexec(conn, "Commit", CKPQ_WRITE); |
|
ok = true; |
|
unparam: |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
K_WLOCK(users_free); |
|
if (!ok) |
|
k_add_head(users_free, item); |
|
else { |
|
users_root = remove_from_ktree(users_root, u_item, cmp_users, ctx); |
|
userid_root = remove_from_ktree(userid_root, u_item, cmp_userid, ctx); |
|
copy_tv(&(users->expirydate), cd); |
|
users_root = add_to_ktree(users_root, u_item, cmp_users); |
|
userid_root = add_to_ktree(userid_root, u_item, cmp_userid); |
|
|
|
users_root = add_to_ktree(users_root, item, cmp_users); |
|
userid_root = add_to_ktree(userid_root, item, cmp_userid); |
|
k_add_head(users_store, item); |
|
} |
|
K_WUNLOCK(users_free); |
|
|
|
return ok; |
|
} |
|
|
|
static K_ITEM *users_add(PGconn *conn, char *username, char *emailaddress, |
|
char *passwordhash, char *by, char *code, char *inet, |
|
tv_t *cd, K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n; |
|
USERS *row; |
|
char *ins; |
|
char tohash[64]; |
|
uint64_t hash; |
|
__maybe_unused uint64_t tmp; |
|
bool ok = false; |
|
char *params[8 + HISTORYDATECOUNT]; |
|
int par; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(users_free); |
|
item = k_unlink_head(users_free); |
|
K_WUNLOCK(users_free); |
|
|
|
DATA_USERS(row, item); |
|
|
|
row->userid = nextid(conn, "userid", (int64_t)(666 + (random() % 334)), |
|
cd, by, code, inet); |
|
if (row->userid == 0) |
|
goto unitem; |
|
|
|
// TODO: pre-check the username exists? (to save finding out via a DB error) |
|
|
|
STRNCPY(row->username, username); |
|
row->status[0] = '\0'; |
|
STRNCPY(row->emailaddress, emailaddress); |
|
STRNCPY(row->passwordhash, passwordhash); |
|
|
|
snprintf(tohash, sizeof(tohash), "%s&#%s", username, emailaddress); |
|
HASH_BER(tohash, strlen(tohash), 1, hash, tmp); |
|
__bin2hex(row->secondaryuserid, (void *)(&hash), sizeof(hash)); |
|
|
|
make_salt(row); |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
// copy createdate |
|
row->joineddate.tv_sec = row->createdate.tv_sec; |
|
row->joineddate.tv_usec = row->createdate.tv_usec; |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->username, NULL, 0); |
|
params[par++] = str_to_buf(row->status, NULL, 0); |
|
params[par++] = str_to_buf(row->emailaddress, NULL, 0); |
|
params[par++] = tv_to_buf(&(row->joineddate), NULL, 0); |
|
params[par++] = str_to_buf(row->passwordhash, NULL, 0); |
|
params[par++] = str_to_buf(row->secondaryuserid, NULL, 0); |
|
params[par++] = str_to_buf(row->salt, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into users " |
|
"(userid,username,status,emailaddress,joineddate,passwordhash," |
|
"secondaryuserid,salt" |
|
HISTORYDATECONTROL ") values (" PQPARAM13 ")"; |
|
|
|
if (!conn) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
ok = true; |
|
unparam: |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
unitem: |
|
K_WLOCK(users_free); |
|
if (!ok) |
|
k_add_head(users_free, item); |
|
else { |
|
users_root = add_to_ktree(users_root, item, cmp_users); |
|
userid_root = add_to_ktree(userid_root, item, cmp_userid); |
|
k_add_head(users_store, item); |
|
} |
|
K_WUNLOCK(users_free); |
|
|
|
if (ok) |
|
return item; |
|
else |
|
return NULL; |
|
} |
|
|
|
static bool users_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
USERS *row; |
|
char *field; |
|
char *sel; |
|
int fields = 8; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
sel = "select " |
|
"userid,username,status,emailaddress,joineddate," |
|
"passwordhash,secondaryuserid,salt" |
|
HISTORYDATECONTROL |
|
" from users"; |
|
res = PQexec(conn, sel, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != (fields + HISTORYDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + HISTORYDATECOUNT, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
K_WLOCK(users_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(users_free); |
|
DATA_USERS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "userid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("userid", field, row->userid); |
|
|
|
PQ_GET_FLD(res, i, "username", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("username", field, row->username); |
|
|
|
PQ_GET_FLD(res, i, "status", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("status", field, row->status); |
|
|
|
PQ_GET_FLD(res, i, "emailaddress", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("emailaddress", field, row->emailaddress); |
|
|
|
PQ_GET_FLD(res, i, "joineddate", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_TV("joineddate", field, row->joineddate); |
|
|
|
PQ_GET_FLD(res, i, "passwordhash", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("passwordhash", field, row->passwordhash); |
|
|
|
PQ_GET_FLD(res, i, "secondaryuserid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("secondaryuserid", field, row->secondaryuserid); |
|
|
|
PQ_GET_FLD(res, i, "salt", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("salt", field, row->salt); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
users_root = add_to_ktree(users_root, item, cmp_users); |
|
userid_root = add_to_ktree(userid_root, item, cmp_userid); |
|
k_add_head(users_store, item); |
|
} |
|
if (!ok) |
|
k_add_head(users_free, item); |
|
|
|
K_WUNLOCK(users_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d users records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
void users_reload() |
|
{ |
|
PGconn *conn = dbconnect(); |
|
|
|
K_WLOCK(users_free); |
|
users_root = free_ktree(users_root, NULL); |
|
userid_root = free_ktree(userid_root, NULL); |
|
k_list_transfer_to_head(users_store, users_free); |
|
K_WUNLOCK(users_free); |
|
|
|
users_fill(conn); |
|
|
|
PQfinish(conn); |
|
} |
|
|
|
// default tree order by userid asc,attname asc,expirydate desc |
|
static cmp_t cmp_useratts(K_ITEM *a, K_ITEM *b) |
|
{ |
|
USERATTS *ua, *ub; |
|
DATA_USERATTS(ua, a); |
|
DATA_USERATTS(ub, b); |
|
cmp_t c = CMP_BIGINT(ua->userid, ub->userid); |
|
if (c == 0) { |
|
c = CMP_STR(ua->attname, ub->attname); |
|
if (c == 0) |
|
c = CMP_TV(ub->expirydate, ua->expirydate); |
|
} |
|
return c; |
|
} |
|
|
|
// Must be R or W locked before call |
|
static K_ITEM *find_useratts(int64_t userid, char *attname) |
|
{ |
|
USERATTS useratts; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look; |
|
|
|
useratts.userid = userid; |
|
STRNCPY(useratts.attname, attname); |
|
useratts.expirydate.tv_sec = default_expiry.tv_sec; |
|
useratts.expirydate.tv_usec = default_expiry.tv_usec; |
|
|
|
INIT_USERATTS(&look); |
|
look.data = (void *)(&useratts); |
|
return find_in_ktree(useratts_root, &look, cmp_useratts, ctx); |
|
} |
|
|
|
static bool useratts_item_add(PGconn *conn, K_ITEM *ua_item, tv_t *cd, bool begun) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
K_TREE_CTX ctx[1]; |
|
PGresult *res; |
|
K_ITEM *old_item; |
|
USERATTS *old_useratts, *useratts; |
|
char *upd, *ins; |
|
bool ok = false; |
|
char *params[9 + HISTORYDATECOUNT]; |
|
int n, par; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
DATA_USERATTS(useratts, ua_item); |
|
|
|
K_RLOCK(useratts_free); |
|
old_item = find_useratts(useratts->userid, useratts->attname); |
|
K_RUNLOCK(useratts_free); |
|
DATA_USERATTS_NULL(old_useratts, old_item); |
|
|
|
/* N.B. the values of the old ua_item record, if it exists, |
|
* are completely ignored i.e. you must provide all values required */ |
|
|
|
if (!conn) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
if (!begun) { |
|
// Beginning of a write txn |
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
goto unparam; |
|
} |
|
PQclear(res); |
|
} |
|
|
|
if (old_item) { |
|
upd = "update useratts set expirydate=$1 where userid=$2 and " |
|
"attname=$3 and expirydate=$4"; |
|
par = 0; |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = bigint_to_buf(old_useratts->userid, NULL, 0); |
|
params[par++] = str_to_buf(old_useratts->attname, NULL, 0); |
|
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); |
|
PARCHKVAL(par, 4, params); |
|
|
|
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Update", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
} |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(useratts->userid, NULL, 0); |
|
params[par++] = str_to_buf(useratts->attname, NULL, 0); |
|
params[par++] = str_to_buf(useratts->status, NULL, 0); |
|
params[par++] = str_to_buf(useratts->attstr, NULL, 0); |
|
params[par++] = str_to_buf(useratts->attstr2, NULL, 0); |
|
params[par++] = bigint_to_buf(useratts->attnum, NULL, 0); |
|
params[par++] = bigint_to_buf(useratts->attnum2, NULL, 0); |
|
params[par++] = tv_to_buf(&(useratts->attdate), NULL, 0); |
|
params[par++] = tv_to_buf(&(useratts->attdate2), NULL, 0); |
|
HISTORYDATEPARAMS(params, par, useratts); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into useratts " |
|
"(userid,attname,status,attstr,attstr2,attnum,attnum2," |
|
"attdate,attdate2" |
|
HISTORYDATECONTROL ") values (" PQPARAM14 ")"; |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!begun) { |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
goto unparam; |
|
} |
|
|
|
res = PQexec(conn, "Commit", CKPQ_WRITE); |
|
ok = true; |
|
} else { |
|
if (PGOK(rescode)) |
|
ok = true; |
|
} |
|
unparam: |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
K_WLOCK(useratts_free); |
|
if (ok) { |
|
if (old_item) { |
|
useratts_root = remove_from_ktree(useratts_root, old_item, cmp_useratts, ctx); |
|
copy_tv(&(old_useratts->expirydate), cd); |
|
useratts_root = add_to_ktree(useratts_root, old_item, cmp_useratts); |
|
} |
|
useratts_root = add_to_ktree(useratts_root, ua_item, cmp_useratts); |
|
k_add_head(useratts_store, ua_item); |
|
} |
|
K_WUNLOCK(useratts_free); |
|
|
|
return ok; |
|
} |
|
|
|
static __maybe_unused K_ITEM *useratts_add(PGconn *conn, char *username, char *attname, |
|
char *status, char *attstr, char *attstr2, |
|
char *attnum, char *attnum2, char *attdate, |
|
char *attdate2, char *by, char *code, |
|
char *inet, tv_t *cd, K_TREE *trf_root, |
|
bool begun) |
|
{ |
|
K_ITEM *item, *u_item; |
|
USERATTS *row; |
|
USERS *users; |
|
bool ok = false; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(useratts_free); |
|
item = k_unlink_head(useratts_free); |
|
K_WUNLOCK(useratts_free); |
|
DATA_USERATTS(row, item); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) |
|
goto unitem; |
|
DATA_USERS(users, u_item); |
|
|
|
row->userid = users->userid; |
|
STRNCPY(row->attname, attname); |
|
if (status == NULL) |
|
row->status[0] = '\0'; |
|
else |
|
STRNCPY(row->status, status); |
|
if (attstr == NULL) |
|
row->attstr[0] = '\0'; |
|
else |
|
STRNCPY(row->attstr, attstr); |
|
if (attstr2 == NULL) |
|
row->attstr2[0] = '\0'; |
|
else |
|
STRNCPY(row->attstr2, attstr2); |
|
if (attnum == NULL || attnum[0] == '\0') |
|
row->attnum = 0; |
|
else |
|
TXT_TO_BIGINT("attnum", attnum, row->attnum); |
|
if (attnum2 == NULL || attnum2[0] == '\0') |
|
row->attnum2 = 0; |
|
else |
|
TXT_TO_BIGINT("attnum2", attnum2, row->attnum2); |
|
if (attdate == NULL || attdate[0] == '\0') |
|
row->attdate.tv_sec = row->attdate.tv_usec = 0L; |
|
else |
|
TXT_TO_TV("attdate", attdate, row->attdate); |
|
if (attdate2 == NULL || attdate2[0] == '\0') |
|
row->attdate2.tv_sec = row->attdate2.tv_usec = 0L; |
|
else |
|
TXT_TO_TV("attdate2", attdate2, row->attdate2); |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
ok = useratts_item_add(conn, item, cd, begun); |
|
unitem: |
|
K_WLOCK(useratts_free); |
|
if (!ok) |
|
k_add_head(useratts_free, item); |
|
K_WUNLOCK(useratts_free); |
|
|
|
if (ok) |
|
return item; |
|
else |
|
return NULL; |
|
} |
|
|
|
static bool useratts_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
USERATTS *row; |
|
char *field; |
|
char *sel; |
|
int fields = 9; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
sel = "select " |
|
"userid,attname,status,attstr,attstr2,attnum,attnum2" |
|
",attdate,attdate2" |
|
HISTORYDATECONTROL |
|
" from useratts"; |
|
res = PQexec(conn, sel, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != (fields + HISTORYDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + HISTORYDATECOUNT, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
K_WLOCK(useratts_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(useratts_free); |
|
DATA_USERATTS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "userid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("userid", field, row->userid); |
|
|
|
PQ_GET_FLD(res, i, "attname", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("attname", field, row->attname); |
|
|
|
PQ_GET_FLD(res, i, "status", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("status", field, row->status); |
|
|
|
PQ_GET_FLD(res, i, "attstr", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("attstr", field, row->attstr); |
|
|
|
PQ_GET_FLD(res, i, "attstr2", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("attstr2", field, row->attstr2); |
|
|
|
PQ_GET_FLD(res, i, "attnum", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("attnum", field, row->attnum); |
|
|
|
PQ_GET_FLD(res, i, "attnum2", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("attnum2", field, row->attnum2); |
|
|
|
PQ_GET_FLD(res, i, "attdate", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_TV("attdate", field, row->attdate); |
|
|
|
PQ_GET_FLD(res, i, "attdate2", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_TV("attdate2", field, row->attdate2); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
useratts_root = add_to_ktree(useratts_root, item, cmp_useratts); |
|
k_add_head(useratts_store, item); |
|
} |
|
if (!ok) |
|
k_add_head(useratts_free, item); |
|
|
|
K_WUNLOCK(useratts_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d useratts records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
void useratts_reload() |
|
{ |
|
PGconn *conn = dbconnect(); |
|
|
|
K_WLOCK(useratts_free); |
|
useratts_root = free_ktree(useratts_root, NULL); |
|
k_list_transfer_to_head(useratts_store, useratts_free); |
|
K_WUNLOCK(useratts_free); |
|
|
|
useratts_fill(conn); |
|
|
|
PQfinish(conn); |
|
} |
|
|
|
// order by userid asc,workername asc,expirydate desc |
|
static cmp_t cmp_workers(K_ITEM *a, K_ITEM *b) |
|
{ |
|
WORKERS *wa, *wb; |
|
DATA_WORKERS(wa, a); |
|
DATA_WORKERS(wb, b); |
|
cmp_t c = CMP_BIGINT(wa->userid, wb->userid); |
|
if (c == 0) { |
|
c = CMP_STR(wa->workername, wb->workername); |
|
if (c == 0) |
|
c = CMP_TV(wb->expirydate, wa->expirydate); |
|
} |
|
return c; |
|
} |
|
|
|
static K_ITEM *find_workers(int64_t userid, char *workername) |
|
{ |
|
WORKERS workers; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look; |
|
|
|
workers.userid = userid; |
|
STRNCPY(workers.workername, workername); |
|
workers.expirydate.tv_sec = default_expiry.tv_sec; |
|
workers.expirydate.tv_usec = default_expiry.tv_usec; |
|
|
|
INIT_WORKERS(&look); |
|
look.data = (void *)(&workers); |
|
return find_in_ktree(workers_root, &look, cmp_workers, ctx); |
|
} |
|
|
|
static K_ITEM *workers_add(PGconn *conn, int64_t userid, char *workername, |
|
char *difficultydefault, char *idlenotificationenabled, |
|
char *idlenotificationtime, char *by, |
|
char *code, char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
K_ITEM *item, *ret = NULL; |
|
int n; |
|
WORKERS *row; |
|
char *ins; |
|
char *params[6 + HISTORYDATECOUNT]; |
|
int par; |
|
int32_t diffdef; |
|
int32_t nottime; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(workers_free); |
|
item = k_unlink_head(workers_free); |
|
K_WUNLOCK(workers_free); |
|
|
|
DATA_WORKERS(row, item); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
row->workerid = nextid(conn, "workerid", (int64_t)1, cd, by, code, inet); |
|
if (row->workerid == 0) |
|
goto unitem; |
|
|
|
row->userid = userid; |
|
STRNCPY(row->workername, workername); |
|
if (difficultydefault && *difficultydefault) { |
|
diffdef = atoi(difficultydefault); |
|
if (diffdef < DIFFICULTYDEFAULT_MIN) |
|
diffdef = DIFFICULTYDEFAULT_MIN; |
|
if (diffdef > DIFFICULTYDEFAULT_MAX) |
|
diffdef = DIFFICULTYDEFAULT_MAX; |
|
row->difficultydefault = diffdef; |
|
} else |
|
row->difficultydefault = DIFFICULTYDEFAULT_DEF; |
|
|
|
row->idlenotificationenabled[1] = '\0'; |
|
if (idlenotificationenabled && *idlenotificationenabled) { |
|
if (tolower(*idlenotificationenabled) == IDLENOTIFICATIONENABLED[0]) |
|
row->idlenotificationenabled[0] = IDLENOTIFICATIONENABLED[0]; |
|
else |
|
row->idlenotificationenabled[0] = IDLENOTIFICATIONDISABLED[0]; |
|
} else |
|
row->idlenotificationenabled[0] = IDLENOTIFICATIONENABLED_DEF[0]; |
|
|
|
if (idlenotificationtime && *idlenotificationtime) { |
|
nottime = atoi(idlenotificationtime); |
|
if (nottime < DIFFICULTYDEFAULT_MIN) { |
|
row->idlenotificationenabled[0] = IDLENOTIFICATIONDISABLED[0]; |
|
nottime = DIFFICULTYDEFAULT_MIN; |
|
} else if (nottime > IDLENOTIFICATIONTIME_MAX) |
|
nottime = row->idlenotificationtime; |
|
row->idlenotificationtime = nottime; |
|
} else |
|
row->idlenotificationtime = IDLENOTIFICATIONTIME_DEF; |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(row->workerid, NULL, 0); |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = int_to_buf(row->difficultydefault, NULL, 0); |
|
params[par++] = str_to_buf(row->idlenotificationenabled, NULL, 0); |
|
params[par++] = int_to_buf(row->idlenotificationtime, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into workers " |
|
"(workerid,userid,workername,difficultydefault," |
|
"idlenotificationenabled,idlenotificationtime" |
|
HISTORYDATECONTROL ") values (" PQPARAM11 ")"; |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
ret = item; |
|
unparam: |
|
PQclear(res); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
unitem: |
|
if (conned) |
|
PQfinish(conn); |
|
K_WLOCK(workers_free); |
|
if (!ret) |
|
k_add_head(workers_free, item); |
|
else { |
|
workers_root = add_to_ktree(workers_root, item, cmp_workers); |
|
k_add_head(workers_store, item); |
|
// Ensure there is a matching workerstatus |
|
find_create_workerstatus(userid, workername, |
|
__FILE__, __func__, __LINE__); |
|
} |
|
K_WUNLOCK(workers_free); |
|
|
|
return ret; |
|
} |
|
|
|
static bool workers_update(PGconn *conn, K_ITEM *item, char *difficultydefault, |
|
char *idlenotificationenabled, char *idlenotificationtime, |
|
char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
int n; |
|
WORKERS *row; |
|
char *upd, *ins; |
|
bool ok = false; |
|
char *params[6 + HISTORYDATECOUNT]; |
|
int par; |
|
int32_t diffdef; |
|
char idlenot; |
|
int32_t nottime; |
|
|
|
LOGDEBUG("%s(): update", __func__); |
|
|
|
DATA_WORKERS(row, item); |
|
|
|
if (difficultydefault && *difficultydefault) { |
|
diffdef = atoi(difficultydefault); |
|
if (diffdef < DIFFICULTYDEFAULT_MIN) |
|
diffdef = row->difficultydefault; |
|
if (diffdef > DIFFICULTYDEFAULT_MAX) |
|
diffdef = row->difficultydefault; |
|
} else |
|
diffdef = row->difficultydefault; |
|
|
|
if (idlenotificationenabled && *idlenotificationenabled) { |
|
if (tolower(*idlenotificationenabled) == IDLENOTIFICATIONENABLED[0]) |
|
idlenot = IDLENOTIFICATIONENABLED[0]; |
|
else |
|
idlenot = IDLENOTIFICATIONDISABLED[0]; |
|
} else |
|
idlenot = row->idlenotificationenabled[0]; |
|
|
|
if (idlenotificationtime && *idlenotificationtime) { |
|
nottime = atoi(idlenotificationtime); |
|
if (nottime < IDLENOTIFICATIONTIME_MIN) |
|
nottime = row->idlenotificationtime; |
|
if (nottime > IDLENOTIFICATIONTIME_MAX) |
|
nottime = row->idlenotificationtime; |
|
} else |
|
nottime = row->idlenotificationtime; |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
if (diffdef == row->difficultydefault && |
|
idlenot == row->idlenotificationenabled[0] && |
|
nottime == row->idlenotificationtime) { |
|
ok = true; |
|
goto early; |
|
} else { |
|
upd = "update workers set expirydate=$1 where workerid=$2 and expirydate=$3"; |
|
par = 0; |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = bigint_to_buf(row->workerid, NULL, 0); |
|
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); |
|
PARCHKVAL(par, 3, params); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
goto unparam; |
|
} |
|
PQclear(res); |
|
|
|
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Update", rescode, conn); |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
goto unparam; |
|
} |
|
|
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
ins = "insert into workers " |
|
"(workerid,userid,workername,difficultydefault," |
|
"idlenotificationenabled,idlenotificationtime" |
|
HISTORYDATECONTROL ") values (" PQPARAM11 ")"; |
|
|
|
row->difficultydefault = diffdef; |
|
row->idlenotificationenabled[0] = idlenot; |
|
row->idlenotificationenabled[1] = '\0'; |
|
row->idlenotificationtime = nottime; |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(row->workerid, NULL, 0); |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = int_to_buf(row->difficultydefault, NULL, 0); |
|
params[par++] = str_to_buf(row->idlenotificationenabled, NULL, 0); |
|
params[par++] = int_to_buf(row->idlenotificationtime, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
goto unparam; |
|
} |
|
|
|
res = PQexec(conn, "Commit", CKPQ_WRITE); |
|
} |
|
|
|
ok = true; |
|
unparam: |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
early: |
|
return ok; |
|
} |
|
|
|
static K_ITEM *new_worker(PGconn *conn, bool update, int64_t userid, char *workername, |
|
char *diffdef, char *idlenotificationenabled, |
|
char *idlenotificationtime, char *by, |
|
char *code, char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *item; |
|
|
|
item = find_workers(userid, workername); |
|
if (item) { |
|
if (!confirm_sharesummary && update) { |
|
workers_update(conn, item, diffdef, idlenotificationenabled, |
|
idlenotificationtime, by, code, inet, cd, |
|
trf_root); |
|
} |
|
} else { |
|
if (confirm_sharesummary) { |
|
// Shouldn't be possible since the sharesummary is already aged |
|
LOGERR("%s() %"PRId64"/%s workername not found during confirm", |
|
__func__, userid, workername); |
|
return NULL; |
|
} |
|
|
|
// TODO: limit how many? |
|
item = workers_add(conn, userid, workername, diffdef, |
|
idlenotificationenabled, idlenotificationtime, |
|
by, code, inet, cd, trf_root); |
|
} |
|
return item; |
|
} |
|
|
|
static K_ITEM *new_default_worker(PGconn *conn, bool update, int64_t userid, char *workername, |
|
char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
return new_worker(conn, update, userid, workername, DIFFICULTYDEFAULT_DEF_STR, |
|
IDLENOTIFICATIONENABLED_DEF, IDLENOTIFICATIONTIME_DEF_STR, |
|
by, code, inet, cd, trf_root); |
|
} |
|
|
|
/* unused |
|
static K_ITEM *new_worker_find_user(PGconn *conn, bool update, char *username, |
|
char *workername, char *diffdef, |
|
char *idlenotificationenabled, |
|
char *idlenotificationtime, |
|
char *by, char *code, char *inet, |
|
tv_t *cd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *item; |
|
USERS *users; |
|
|
|
K_RLOCK(users_free); |
|
item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!item) |
|
return NULL; |
|
|
|
DATA_USERS(users, item); |
|
return new_worker(conn, update, users->userid, workername, diffdef, |
|
idlenotificationenabled, idlenotificationtime, |
|
by, code, inet, cd, trf_root); |
|
} |
|
*/ |
|
|
|
static bool workers_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
WORKERS *row; |
|
char *field; |
|
char *sel; |
|
int fields = 6; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
sel = "select " |
|
"userid,workername,difficultydefault," |
|
"idlenotificationenabled,idlenotificationtime" |
|
HISTORYDATECONTROL |
|
",workerid from workers"; |
|
res = PQexec(conn, sel, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != (fields + HISTORYDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + HISTORYDATECOUNT, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
K_WLOCK(workers_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(workers_free); |
|
DATA_WORKERS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "userid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("userid", field, row->userid); |
|
|
|
PQ_GET_FLD(res, i, "workername", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("workername", field, row->workername); |
|
|
|
PQ_GET_FLD(res, i, "difficultydefault", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("difficultydefault", field, row->difficultydefault); |
|
|
|
PQ_GET_FLD(res, i, "idlenotificationenabled", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("idlenotificationenabled", field, row->idlenotificationenabled); |
|
|
|
PQ_GET_FLD(res, i, "idlenotificationtime", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("idlenotificationtime", field, row->idlenotificationtime); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
PQ_GET_FLD(res, i, "workerid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("workerid", field, row->workerid); |
|
|
|
workers_root = add_to_ktree(workers_root, item, cmp_workers); |
|
k_add_head(workers_store, item); |
|
|
|
/* Make sure a workerstatus exists for each worker |
|
* This is to ensure that code can use the workerstatus tree |
|
* to reference other tables and not miss workers in the |
|
* other tables */ |
|
find_create_workerstatus(row->userid, row->workername, |
|
__FILE__, __func__, __LINE__); |
|
} |
|
if (!ok) |
|
k_add_head(workers_free, item); |
|
|
|
K_WUNLOCK(workers_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d workers records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
void workers_reload() |
|
{ |
|
PGconn *conn = dbconnect(); |
|
|
|
K_WLOCK(workers_free); |
|
workers_root = free_ktree(workers_root, NULL); |
|
k_list_transfer_to_head(workers_store, workers_free); |
|
K_WUNLOCK(workers_free); |
|
|
|
workers_fill(conn); |
|
|
|
PQfinish(conn); |
|
} |
|
|
|
// order by userid asc,expirydate desc,payaddress asc |
|
static cmp_t cmp_paymentaddresses(K_ITEM *a, K_ITEM *b) |
|
{ |
|
PAYMENTADDRESSES *pa, *pb; |
|
DATA_PAYMENTADDRESSES(pa, a); |
|
DATA_PAYMENTADDRESSES(pb, b); |
|
cmp_t c = CMP_BIGINT(pa->userid, pb->userid); |
|
if (c == 0) { |
|
c = CMP_TV(pb->expirydate, pa->expirydate); |
|
if (c == 0) |
|
c = CMP_STR(pa->payaddress, pb->payaddress); |
|
} |
|
return c; |
|
} |
|
|
|
static K_ITEM *find_paymentaddresses(int64_t userid) |
|
{ |
|
PAYMENTADDRESSES paymentaddresses, *pa; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look, *item; |
|
|
|
paymentaddresses.userid = userid; |
|
paymentaddresses.payaddress[0] = '\0'; |
|
paymentaddresses.expirydate.tv_sec = DATE_S_EOT; |
|
|
|
INIT_PAYMENTADDRESSES(&look); |
|
look.data = (void *)(&paymentaddresses); |
|
item = find_after_in_ktree(paymentaddresses_root, &look, cmp_paymentaddresses, ctx); |
|
if (item) { |
|
DATA_PAYMENTADDRESSES(pa, item); |
|
if (pa->userid == userid && CURRENT(&(pa->expirydate))) |
|
return item; |
|
else |
|
return NULL; |
|
} else |
|
return NULL; |
|
} |
|
|
|
// Whatever the current paymentaddresses are, replace them with this one |
|
static K_ITEM *paymentaddresses_set(PGconn *conn, int64_t userid, char *payaddress, |
|
char *by, char *code, char *inet, tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
K_TREE_CTX ctx[1], ctx2[1]; |
|
K_ITEM *item, *old, *this, look; |
|
PAYMENTADDRESSES *row, pa, *thispa; |
|
char *upd, *ins; |
|
bool ok = false; |
|
char *params[4 + HISTORYDATECOUNT]; |
|
int par; |
|
int n; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(paymentaddresses_free); |
|
item = k_unlink_head(paymentaddresses_free); |
|
K_WUNLOCK(paymentaddresses_free); |
|
|
|
DATA_PAYMENTADDRESSES(row, item); |
|
|
|
row->paymentaddressid = nextid(conn, "paymentaddressid", 1, |
|
cd, by, code, inet); |
|
if (row->paymentaddressid == 0) |
|
goto unitem; |
|
|
|
row->userid = userid; |
|
STRNCPY(row->payaddress, payaddress); |
|
row->payratio = 1000000; |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
upd = "update paymentaddresses set expirydate=$1 where userid=$2 and expirydate=$3"; |
|
par = 0; |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); |
|
PARCHKVAL(par, 3, params); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
goto unparam; |
|
} |
|
PQclear(res); |
|
|
|
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Update", rescode, conn); |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
goto unparam; |
|
} |
|
|
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
ins = "insert into paymentaddresses " |
|
"(paymentaddressid,userid,payaddress,payratio" |
|
HISTORYDATECONTROL ") values (" PQPARAM9 ")"; |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(row->paymentaddressid, NULL, 0); |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->payaddress, NULL, 0); |
|
params[par++] = int_to_buf(row->payratio, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
res = PQexec(conn, "Commit", CKPQ_WRITE); |
|
|
|
ok = true; |
|
unparam: |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
unitem: |
|
K_WLOCK(paymentaddresses_free); |
|
if (!ok) |
|
k_add_head(paymentaddresses_free, item); |
|
else { |
|
// Remove from ram, old (unneeded) records |
|
pa.userid = userid; |
|
pa.expirydate.tv_sec = 0L; |
|
pa.payaddress[0] = '\0'; |
|
INIT_PAYMENTADDRESSES(&look); |
|
look.data = (void *)(&pa); |
|
old = find_after_in_ktree(paymentaddresses_root, &look, |
|
cmp_paymentaddresses, ctx); |
|
while (old) { |
|
this = old; |
|
DATA_PAYMENTADDRESSES(thispa, this); |
|
if (thispa->userid != userid) |
|
break; |
|
old = next_in_ktree(ctx); |
|
paymentaddresses_root = remove_from_ktree(paymentaddresses_root, this, |
|
cmp_paymentaddresses, ctx2); |
|
k_add_head(paymentaddresses_free, this); |
|
} |
|
paymentaddresses_root = add_to_ktree(paymentaddresses_root, item, |
|
cmp_paymentaddresses); |
|
k_add_head(paymentaddresses_store, item); |
|
} |
|
K_WUNLOCK(paymentaddresses_free); |
|
|
|
if (ok) |
|
return item; |
|
else |
|
return NULL; |
|
} |
|
|
|
static bool paymentaddresses_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
PAYMENTADDRESSES *row; |
|
char *params[1]; |
|
int par; |
|
char *field; |
|
char *sel; |
|
int fields = 4; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
sel = "select " |
|
"paymentaddressid,userid,payaddress,payratio" |
|
HISTORYDATECONTROL |
|
" from paymentaddresses where expirydate=$1"; |
|
par = 0; |
|
params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); |
|
PARCHK(par, params); |
|
res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != (fields + HISTORYDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + HISTORYDATECOUNT, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
K_WLOCK(paymentaddresses_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(paymentaddresses_free); |
|
DATA_PAYMENTADDRESSES(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "paymentaddressid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("paymentaddressid", field, row->paymentaddressid); |
|
|
|
PQ_GET_FLD(res, i, "userid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("userid", field, row->userid); |
|
|
|
PQ_GET_FLD(res, i, "payaddress", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("payaddress", field, row->payaddress); |
|
|
|
PQ_GET_FLD(res, i, "payratio", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("payratio", field, row->payratio); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
paymentaddresses_root = add_to_ktree(paymentaddresses_root, item, cmp_paymentaddresses); |
|
k_add_head(paymentaddresses_store, item); |
|
} |
|
if (!ok) |
|
k_add_head(paymentaddresses_free, item); |
|
|
|
K_WUNLOCK(paymentaddresses_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d paymentaddresses records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
// order by userid asc,paydate asc,payaddress asc,expirydate desc |
|
static cmp_t cmp_payments(K_ITEM *a, K_ITEM *b) |
|
{ |
|
PAYMENTS *pa, *pb; |
|
DATA_PAYMENTS(pa, a); |
|
DATA_PAYMENTS(pb, b); |
|
cmp_t c = CMP_BIGINT(pa->userid, pb->userid); |
|
if (c == 0) { |
|
c = CMP_TV(pa->paydate, pb->paydate); |
|
if (c == 0) { |
|
c = CMP_STR(pa->payaddress, pb->payaddress); |
|
if (c == 0) |
|
c = CMP_TV(pb->expirydate, pa->expirydate); |
|
} |
|
} |
|
return c; |
|
} |
|
|
|
static bool payments_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
PAYMENTS *row; |
|
char *params[1]; |
|
int par; |
|
char *field; |
|
char *sel; |
|
int fields = 8; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
// TODO: handle selecting a subset, eg 20 per web page (in blocklist also) |
|
sel = "select " |
|
"userid,paydate,payaddress,originaltxn,amount,committxn,commitblockhash" |
|
HISTORYDATECONTROL |
|
",paymentid from payments where expirydate=$1"; |
|
par = 0; |
|
params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); |
|
PARCHK(par, params); |
|
res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != (fields + HISTORYDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + HISTORYDATECOUNT, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
K_WLOCK(payments_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(payments_free); |
|
DATA_PAYMENTS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "userid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("userid", field, row->userid); |
|
|
|
PQ_GET_FLD(res, i, "paydate", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_TV("paydate", field, row->paydate); |
|
|
|
PQ_GET_FLD(res, i, "payaddress", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("payaddress", field, row->payaddress); |
|
|
|
PQ_GET_FLD(res, i, "originaltxn", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("originaltxn", field, row->originaltxn); |
|
|
|
PQ_GET_FLD(res, i, "amount", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("amount", field, row->amount); |
|
|
|
PQ_GET_FLD(res, i, "committxn", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("committxn", field, row->committxn); |
|
|
|
PQ_GET_FLD(res, i, "commitblockhash", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("commitblockhash", field, row->commitblockhash); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
PQ_GET_FLD(res, i, "paymentid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("paymentid", field, row->paymentid); |
|
|
|
payments_root = add_to_ktree(payments_root, item, cmp_payments); |
|
k_add_head(payments_store, item); |
|
} |
|
if (!ok) |
|
k_add_head(payments_free, item); |
|
|
|
K_WUNLOCK(payments_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d payments records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
void payments_reload() |
|
{ |
|
PGconn *conn = dbconnect(); |
|
|
|
K_WLOCK(payments_free); |
|
payments_root = free_ktree(payments_root, NULL); |
|
k_list_transfer_to_head(payments_store, payments_free); |
|
K_WUNLOCK(payments_free); |
|
|
|
payments_fill(conn); |
|
|
|
PQfinish(conn); |
|
} |
|
|
|
// order by workinfoid asc,expirydate asc |
|
static cmp_t cmp_workinfo(K_ITEM *a, K_ITEM *b) |
|
{ |
|
WORKINFO *wa, *wb; |
|
DATA_WORKINFO(wa, a); |
|
DATA_WORKINFO(wb, b); |
|
cmp_t c = CMP_BIGINT(wa->workinfoid, wb->workinfoid); |
|
if (c == 0) |
|
c = CMP_TV(wa->expirydate, wb->expirydate); |
|
return c; |
|
} |
|
|
|
inline int32_t _coinbase1height(char *coinbase1, WHERE_FFL_ARGS) |
|
{ |
|
int32_t height = 0; |
|
uchar *cb1; |
|
int siz; |
|
|
|
cb1 = ((uchar *)coinbase1) + 84; |
|
siz = ((hex2bin_tbl[*cb1]) << 4) + (hex2bin_tbl[*(cb1+1)]); |
|
|
|
// limit to 4 for int32_t and since ... that should last a while :) |
|
if (siz < 1 || siz > 4) { |
|
LOGERR("%s(): Invalid coinbase1 block height size (%d)" |
|
" require: 1..4 (cb1 %s)" WHERE_FFL, |
|
__func__, siz, coinbase1, WHERE_FFL_PASS); |
|
return height; |
|
} |
|
|
|
siz *= 2; |
|
while (siz-- > 0) { |
|
height <<= 4; |
|
height += (int32_t)hex2bin_tbl[*(cb1+(siz^1)+2)]; |
|
} |
|
|
|
return height; |
|
} |
|
|
|
static cmp_t _cmp_height(char *coinbase1a, char *coinbase1b, WHERE_FFL_ARGS) |
|
{ |
|
return CMP_INT(_coinbase1height(coinbase1a, WHERE_FFL_PASS), |
|
_coinbase1height(coinbase1b, WHERE_FFL_PASS)); |
|
} |
|
|
|
// order by height asc,createdate asc |
|
static cmp_t cmp_workinfo_height(K_ITEM *a, K_ITEM *b) |
|
{ |
|
WORKINFO *wa, *wb; |
|
DATA_WORKINFO(wa, a); |
|
DATA_WORKINFO(wb, b); |
|
cmp_t c = cmp_height(wa->coinbase1, wb->coinbase1); |
|
if (c == 0) |
|
c = CMP_TV(wa->createdate, wb->createdate); |
|
return c; |
|
} |
|
|
|
static K_ITEM *find_workinfo(int64_t workinfoid) |
|
{ |
|
WORKINFO workinfo; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look, *item; |
|
|
|
workinfo.workinfoid = workinfoid; |
|
workinfo.expirydate.tv_sec = default_expiry.tv_sec; |
|
workinfo.expirydate.tv_usec = default_expiry.tv_usec; |
|
|
|
INIT_WORKINFO(&look); |
|
look.data = (void *)(&workinfo); |
|
K_RLOCK(workinfo_free); |
|
item = find_in_ktree(workinfo_root, &look, cmp_workinfo, ctx); |
|
K_RUNLOCK(workinfo_free); |
|
return item; |
|
} |
|
|
|
static int64_t workinfo_add(PGconn *conn, char *workinfoidstr, char *poolinstance, |
|
char *transactiontree, char *merklehash, char *prevhash, |
|
char *coinbase1, char *coinbase2, char *version, |
|
char *bits, char *ntime, char *reward, char *by, |
|
char *code, char *inet, tv_t *cd, bool igndup, |
|
K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
K_TREE_CTX ctx[1]; |
|
PGresult *res; |
|
K_ITEM *item; |
|
char cd_buf[DATE_BUFSIZ]; |
|
char ndiffbin[TXT_SML+1]; |
|
int n; |
|
int64_t workinfoid = -1; |
|
WORKINFO *row; |
|
char *ins; |
|
char *params[11 + HISTORYDATECOUNT]; |
|
int par; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(workinfo_free); |
|
item = k_unlink_head(workinfo_free); |
|
K_WUNLOCK(workinfo_free); |
|
|
|
DATA_WORKINFO(row, item); |
|
|
|
TXT_TO_BIGINT("workinfoid", workinfoidstr, row->workinfoid); |
|
STRNCPY(row->poolinstance, poolinstance); |
|
row->transactiontree = strdup(transactiontree); |
|
row->merklehash = strdup(merklehash); |
|
STRNCPY(row->prevhash, prevhash); |
|
STRNCPY(row->coinbase1, coinbase1); |
|
STRNCPY(row->coinbase2, coinbase2); |
|
STRNCPY(row->version, version); |
|
STRNCPY(row->bits, bits); |
|
STRNCPY(row->ntime, ntime); |
|
TXT_TO_BIGINT("reward", reward, row->reward); |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
K_WLOCK(workinfo_free); |
|
if (find_in_ktree(workinfo_root, item, cmp_workinfo, ctx)) { |
|
free(row->transactiontree); |
|
free(row->merklehash); |
|
workinfoid = row->workinfoid; |
|
k_add_head(workinfo_free, item); |
|
K_WUNLOCK(workinfo_free); |
|
|
|
if (!igndup) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s(): Duplicate workinfo ignored %s/%s/%s", |
|
__func__, workinfoidstr, poolinstance, cd_buf); |
|
} |
|
|
|
return workinfoid; |
|
} |
|
K_WUNLOCK(workinfo_free); |
|
|
|
par = 0; |
|
if (!confirm_sharesummary) { |
|
params[par++] = bigint_to_buf(row->workinfoid, NULL, 0); |
|
params[par++] = str_to_buf(row->poolinstance, NULL, 0); |
|
params[par++] = str_to_buf(row->transactiontree, NULL, 0); |
|
params[par++] = str_to_buf(row->merklehash, NULL, 0); |
|
params[par++] = str_to_buf(row->prevhash, NULL, 0); |
|
params[par++] = str_to_buf(row->coinbase1, NULL, 0); |
|
params[par++] = str_to_buf(row->coinbase2, NULL, 0); |
|
params[par++] = str_to_buf(row->version, NULL, 0); |
|
params[par++] = str_to_buf(row->bits, NULL, 0); |
|
params[par++] = str_to_buf(row->ntime, NULL, 0); |
|
params[par++] = bigint_to_buf(row->reward, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into workinfo " |
|
"(workinfoid,poolinstance,transactiontree,merklehash," |
|
"prevhash,coinbase1,coinbase2,version,bits,ntime,reward" |
|
HISTORYDATECONTROL ") values (" PQPARAM16 ")"; |
|
|
|
if (!conn) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
} |
|
|
|
workinfoid = row->workinfoid; |
|
|
|
unparam: |
|
if (par) { |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
} |
|
|
|
K_WLOCK(workinfo_free); |
|
if (workinfoid == -1) { |
|
free(row->transactiontree); |
|
free(row->merklehash); |
|
k_add_head(workinfo_free, item); |
|
} else { |
|
if (row->transactiontree && *(row->transactiontree)) { |
|
// Not currently needed in RAM |
|
free(row->transactiontree); |
|
row->transactiontree = strdup(EMPTY); |
|
} |
|
|
|
hex2bin(ndiffbin, row->bits, 4); |
|
current_ndiff = diff_from_nbits(ndiffbin); |
|
|
|
workinfo_root = add_to_ktree(workinfo_root, item, cmp_workinfo); |
|
k_add_head(workinfo_store, item); |
|
|
|
// Remember the bc = 'cd' when the height changes |
|
if (workinfo_current) { |
|
WORKINFO *wic; |
|
DATA_WORKINFO(wic, workinfo_current); |
|
if (cmp_height(wic->coinbase1, row->coinbase1) != 0) |
|
copy_tv(&last_bc, cd); |
|
} |
|
|
|
workinfo_current = item; |
|
} |
|
K_WUNLOCK(workinfo_free); |
|
|
|
return workinfoid; |
|
} |
|
|
|
#define sharesummary_update(_conn, _s_row, _e_row, _ss_item, _by, _code, _inet, _cd) \ |
|
_sharesummary_update(_conn, _s_row, _e_row, _ss_item, _by, _code, _inet, _cd, \ |
|
WHERE_FFL_HERE) |
|
|
|
static bool _sharesummary_update(PGconn *conn, SHARES *s_row, SHAREERRORS *e_row, K_ITEM *ss_item, |
|
char *by, char *code, char *inet, tv_t *cd, WHERE_FFL_ARGS); |
|
static cmp_t cmp_sharesummary_workinfoid(K_ITEM *a, K_ITEM *b); |
|
static cmp_t cmp_shares(K_ITEM *a, K_ITEM *b); |
|
|
|
static bool workinfo_age(PGconn *conn, int64_t workinfoid, char *poolinstance, |
|
char *by, char *code, char *inet, tv_t *cd, |
|
tv_t *ss_first, tv_t *ss_last, int64_t *ss_count, |
|
int64_t *s_count, int64_t *s_diff) |
|
{ |
|
K_ITEM *wi_item, ss_look, *ss_item, s_look, *s_item, *tmp_item; |
|
K_TREE_CTX ss_ctx[1], s_ctx[1], tmp_ctx[1]; |
|
char cd_buf[DATE_BUFSIZ]; |
|
int64_t ss_tot, ss_already, ss_failed, shares_tot, shares_dumped; |
|
SHARESUMMARY looksharesummary, *sharesummary; |
|
WORKINFO *workinfo; |
|
SHARES lookshares, *shares; |
|
bool ok = false, conned = false, skipupdate; |
|
char error[1024]; |
|
|
|
LOGDEBUG("%s(): age", __func__); |
|
|
|
ss_first->tv_sec = ss_first->tv_usec = |
|
ss_last->tv_sec = ss_last->tv_usec = 0; |
|
*ss_count = *s_count = *s_diff = 0; |
|
|
|
wi_item = find_workinfo(workinfoid); |
|
if (!wi_item) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s() %"PRId64"/%s/%ld,%ld %.19s no workinfo! Age discarded!", |
|
__func__, workinfoid, poolinstance, |
|
cd->tv_sec, cd->tv_usec, cd_buf); |
|
goto bye; |
|
} |
|
|
|
DATA_WORKINFO(workinfo, wi_item); |
|
if (strcmp(poolinstance, workinfo->poolinstance) != 0) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s() %"PRId64"/%s/%ld,%ld %.19s Poolinstance changed " |
|
"(from %s)! Age discarded!", |
|
__func__, workinfoid, poolinstance, |
|
cd->tv_sec, cd->tv_usec, cd_buf, |
|
workinfo->poolinstance); |
|
goto bye; |
|
} |
|
|
|
INIT_SHARESUMMARY(&ss_look); |
|
INIT_SHARES(&s_look); |
|
|
|
// Find the first matching sharesummary |
|
looksharesummary.workinfoid = workinfoid; |
|
looksharesummary.userid = -1; |
|
looksharesummary.workername[0] = '\0'; |
|
|
|
ok = true; |
|
ss_tot = ss_already = ss_failed = shares_tot = shares_dumped = 0; |
|
ss_look.data = (void *)(&looksharesummary); |
|
K_RLOCK(sharesummary_free); |
|
ss_item = find_after_in_ktree(sharesummary_workinfoid_root, &ss_look, cmp_sharesummary_workinfoid, ss_ctx); |
|
K_RUNLOCK(sharesummary_free); |
|
DATA_SHARESUMMARY_NULL(sharesummary, ss_item); |
|
while (ss_item && sharesummary->workinfoid == workinfoid) { |
|
ss_tot++; |
|
error[0] = '\0'; |
|
skipupdate = false; |
|
/* Reloading during a confirm will not have any old data |
|
* so finding an aged sharesummary here is an error */ |
|
if (reloading) { |
|
if (sharesummary->complete[0] == SUMMARY_COMPLETE) { |
|
ss_already++; |
|
skipupdate = true; |
|
if (confirm_sharesummary) { |
|
LOGERR("%s(): Duplicate %s found during confirm %"PRId64"/%s/%"PRId64, |
|
__func__, __func__, |
|
sharesummary->userid, |
|
sharesummary->workername, |
|
sharesummary->workinfoid); |
|
} |
|
} |
|
} |
|
|
|
if (!skipupdate) { |
|
if (conn == NULL && !confirm_sharesummary) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
if (!sharesummary_update(conn, NULL, NULL, ss_item, by, code, inet, cd)) { |
|
ss_failed++; |
|
LOGERR("%s(): Failed to age share summary %"PRId64"/%s/%"PRId64, |
|
__func__, sharesummary->userid, |
|
sharesummary->workername, |
|
sharesummary->workinfoid); |
|
ok = false; |
|
} else { |
|
(*ss_count)++; |
|
*s_count += sharesummary->sharecount; |
|
*s_diff += sharesummary->diffacc; |
|
if (ss_first->tv_sec == 0 || |
|
!tv_newer(ss_first, &(sharesummary->firstshare))) |
|
copy_tv(ss_first, &(sharesummary->firstshare)); |
|
if (tv_newer(ss_last, &(sharesummary->lastshare))) |
|
copy_tv(ss_last, &(sharesummary->lastshare)); |
|
} |
|
} |
|
|
|
// Discard the shares either way |
|
lookshares.workinfoid = workinfoid; |
|
lookshares.userid = sharesummary->userid; |
|
strcpy(lookshares.workername, sharesummary->workername); |
|
lookshares.createdate.tv_sec = 0; |
|
lookshares.createdate.tv_usec = 0; |
|
|
|
s_look.data = (void *)(&lookshares); |
|
K_WLOCK(shares_free); |
|
s_item = find_after_in_ktree(shares_root, &s_look, cmp_shares, s_ctx); |
|
while (s_item) { |
|
DATA_SHARES(shares, s_item); |
|
if (shares->workinfoid != workinfoid || |
|
shares->userid != lookshares.userid || |
|
strcmp(shares->workername, lookshares.workername) != 0) |
|
break; |
|
|
|
shares_tot++; |
|
tmp_item = next_in_ktree(s_ctx); |
|
shares_root = remove_from_ktree(shares_root, s_item, cmp_shares, tmp_ctx); |
|
k_unlink_item(shares_store, s_item); |
|
if (reloading && skipupdate) |
|
shares_dumped++; |
|
if (reloading && skipupdate && !error[0]) { |
|
snprintf(error, sizeof(error), |
|
"reload found aged shares: %"PRId64"/%"PRId64"/%s", |
|
shares->workinfoid, |
|
shares->userid, |
|
shares->workername); |
|
} |
|
k_add_head(shares_free, s_item); |
|
s_item = tmp_item; |
|
} |
|
K_WUNLOCK(shares_free); |
|
K_RLOCK(sharesummary_free); |
|
ss_item = next_in_ktree(ss_ctx); |
|
K_RUNLOCK(sharesummary_free); |
|
DATA_SHARESUMMARY_NULL(sharesummary, ss_item); |
|
|
|
if (error[0]) |
|
LOGERR("%s(): %s", __func__, error); |
|
} |
|
|
|
if (conned) |
|
PQfinish(conn); |
|
|
|
if (ss_already || ss_failed || shares_dumped) { |
|
/* If all were already aged, and no shares |
|
* then we don't want a message */ |
|
if (!(ss_already == ss_tot && shares_tot == 0)) { |
|
LOGERR("%s(): Summary aging of %"PRId64"/%s sstotal=%"PRId64 |
|
" already=%"PRId64" failed=%"PRId64 |
|
", sharestotal=%"PRId64" dumped=%"PRId64, |
|
__func__, workinfoid, poolinstance, ss_tot, |
|
ss_already, ss_failed, shares_tot, |
|
shares_dumped); |
|
} |
|
} |
|
bye: |
|
return ok; |
|
} |
|
|
|
static void auto_age_older(PGconn *conn, int64_t workinfoid, char *poolinstance, |
|
char *by, char *code, char *inet, tv_t *cd) |
|
{ |
|
static int64_t last_attempted_id = -1; |
|
static int64_t prev_found = 0; |
|
static int repeat; |
|
|
|
char min_buf[DATE_BUFSIZ], max_buf[DATE_BUFSIZ]; |
|
int64_t ss_count_tot, s_count_tot, s_diff_tot; |
|
int64_t ss_count, s_count, s_diff; |
|
tv_t ss_first_min, ss_last_max; |
|
tv_t ss_first, ss_last; |
|
int32_t wid_count; |
|
SHARESUMMARY looksharesummary, *sharesummary; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look, *ss_item; |
|
int64_t age_id, do_id, to_id; |
|
bool ok, found; |
|
|
|
LOGDEBUG("%s(): workinfoid=%"PRId64" prev=%"PRId64, __func__, workinfoid, prev_found); |
|
|
|
age_id = prev_found; |
|
|
|
// Find the oldest 'unaged' sharesummary < workinfoid and >= prev_found |
|
looksharesummary.workinfoid = prev_found; |
|
looksharesummary.userid = -1; |
|
looksharesummary.workername[0] = '\0'; |
|
INIT_SHARESUMMARY(&look); |
|
look.data = (void *)(&looksharesummary); |
|
|
|
K_RLOCK(sharesummary_free); |
|
ss_item = find_after_in_ktree(sharesummary_workinfoid_root, &look, |
|
cmp_sharesummary_workinfoid, ctx); |
|
DATA_SHARESUMMARY_NULL(sharesummary, ss_item); |
|
|
|
ss_first_min.tv_sec = ss_first_min.tv_usec = |
|
ss_last_max.tv_sec = ss_last_max.tv_usec = 0; |
|
ss_count_tot = s_count_tot = s_diff_tot = 0; |
|
|
|
found = false; |
|
while (ss_item && sharesummary->workinfoid < workinfoid) { |
|
if (sharesummary->complete[0] == SUMMARY_NEW) { |
|
age_id = sharesummary->workinfoid; |
|
prev_found = age_id; |
|
found = true; |
|
break; |
|
} |
|
ss_item = next_in_ktree(ctx); |
|
DATA_SHARESUMMARY_NULL(sharesummary, ss_item); |
|
} |
|
K_RUNLOCK(sharesummary_free); |
|
|
|
LOGDEBUG("%s(): age_id=%"PRId64" found=%d", __func__, age_id, found); |
|
// Don't repeat searching old items to avoid accessing their ram |
|
if (!found) |
|
prev_found = workinfoid; |
|
else { |
|
/* Process all the consecutive sharesummaries that's aren't aged |
|
* This way we find each oldest 'batch' of sharesummaries that have |
|
* been missed and can report the range of data that was aged, |
|
* which would normally just be an approx 10min set of workinfoids |
|
* from the last time ckpool stopped |
|
* Each next group of unaged sharesummaries following this, will be |
|
* picked up by each next aging */ |
|
wid_count = 0; |
|
do_id = age_id; |
|
to_id = 0; |
|
do { |
|
ok = workinfo_age(conn, do_id, poolinstance, |
|
by, code, inet, cd, |
|
&ss_first, &ss_last, |
|
&ss_count, &s_count, &s_diff); |
|
|
|
ss_count_tot += ss_count; |
|
s_count_tot += s_count; |
|
s_diff_tot += s_diff; |
|
if (ss_first_min.tv_sec == 0 || !tv_newer(&ss_first_min, &ss_first)) |
|
copy_tv(&ss_first_min, &ss_first); |
|
if (tv_newer(&ss_last_max, &ss_last)) |
|
copy_tv(&ss_last_max, &ss_last); |
|
|
|
if (!ok) |
|
break; |
|
|
|
to_id = do_id; |
|
wid_count++; |
|
K_RLOCK(sharesummary_free); |
|
while (ss_item && sharesummary->workinfoid == to_id) { |
|
ss_item = next_in_ktree(ctx); |
|
DATA_SHARESUMMARY_NULL(sharesummary, ss_item); |
|
} |
|
K_RUNLOCK(sharesummary_free); |
|
|
|
if (ss_item) { |
|
do_id = sharesummary->workinfoid; |
|
if (do_id >= workinfoid) |
|
break; |
|
if (sharesummary->complete[0] != SUMMARY_NEW) |
|
break; |
|
} |
|
} while (ss_item); |
|
if (to_id == 0) { |
|
if (last_attempted_id != age_id || ++repeat >= 10) { |
|
// Approx once every 5min since workinfo defaults to ~30s |
|
LOGWARNING("%s() Auto-age failed to age %"PRId64, |
|
__func__, age_id); |
|
last_attempted_id = age_id; |
|
repeat = 0; |
|
} |
|
} else { |
|
char idrange[64]; |
|
char sharerange[256]; |
|
if (to_id != age_id) { |
|
snprintf(idrange, sizeof(idrange), |
|
"from %"PRId64" to %"PRId64, |
|
age_id, to_id); |
|
} else { |
|
snprintf(idrange, sizeof(idrange), |
|
"%"PRId64, age_id); |
|
} |
|
tv_to_buf(&ss_first_min, min_buf, sizeof(min_buf)); |
|
if (tv_equal(&ss_first_min, &ss_last_max)) { |
|
snprintf(sharerange, sizeof(sharerange), |
|
"share date %s", min_buf); |
|
} else { |
|
tv_to_buf(&ss_last_max, max_buf, sizeof(max_buf)); |
|
snprintf(sharerange, sizeof(sharerange), |
|
"share dates %s to %s", |
|
min_buf, max_buf); |
|
} |
|
LOGWARNING("%s() Auto-aged %"PRId64"(%"PRId64") " |
|
"share%s %d sharesummar%s %d workinfoid%s " |
|
"%s %s", |
|
__func__, |
|
s_count_tot, s_diff_tot, |
|
(s_count_tot == 1) ? "" : "s", |
|
ss_count_tot, |
|
(ss_count_tot == 1) ? "y" : "ies", |
|
wid_count, |
|
(wid_count == 1) ? "" : "s", |
|
idrange, sharerange); |
|
} |
|
} |
|
} |
|
|
|
static bool workinfo_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
WORKINFO *row; |
|
char *params[1]; |
|
int par; |
|
char *field; |
|
char *sel; |
|
int fields = 10; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
// TODO: select the data based on sharesummary since old data isn't needed |
|
// however, the ageing rules for workinfo will decide that also |
|
// keep the last block + current? Rules will depend on payout scheme also |
|
sel = "select " |
|
// "workinfoid,poolinstance,transactiontree,merklehash,prevhash," |
|
"workinfoid,poolinstance,merklehash,prevhash," |
|
"coinbase1,coinbase2,version,bits,ntime,reward" |
|
HISTORYDATECONTROL |
|
" from workinfo where expirydate=$1"; |
|
par = 0; |
|
params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); |
|
PARCHK(par, params); |
|
res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != (fields + HISTORYDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + HISTORYDATECOUNT, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
K_WLOCK(workinfo_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(workinfo_free); |
|
DATA_WORKINFO(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "workinfoid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("workinfoid", field, row->workinfoid); |
|
|
|
PQ_GET_FLD(res, i, "poolinstance", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("poolinstance", field, row->poolinstance); |
|
|
|
/* Not currently needed in RAM |
|
PQ_GET_FLD(res, i, "transactiontree", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BLOB("transactiontree", field, row->transactiontree); |
|
*/ |
|
row->transactiontree = strdup(EMPTY); |
|
|
|
PQ_GET_FLD(res, i, "merklehash", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BLOB("merklehash", field, row->merklehash); |
|
|
|
PQ_GET_FLD(res, i, "prevhash", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("prevhash", field, row->prevhash); |
|
|
|
PQ_GET_FLD(res, i, "coinbase1", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("coinbase1", field, row->coinbase1); |
|
|
|
PQ_GET_FLD(res, i, "coinbase2", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("coinbase2", field, row->coinbase2); |
|
|
|
PQ_GET_FLD(res, i, "version", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("version", field, row->version); |
|
|
|
PQ_GET_FLD(res, i, "bits", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("bits", field, row->bits); |
|
|
|
PQ_GET_FLD(res, i, "ntime", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("ntime", field, row->ntime); |
|
|
|
PQ_GET_FLD(res, i, "reward", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("reward", field, row->reward); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
workinfo_root = add_to_ktree(workinfo_root, item, cmp_workinfo); |
|
if (!confirm_sharesummary) |
|
workinfo_height_root = add_to_ktree(workinfo_height_root, item, cmp_workinfo_height); |
|
k_add_head(workinfo_store, item); |
|
|
|
if (tv_newer(&(dbstatus.newest_createdate_workinfo), &(row->createdate))) |
|
copy_tv(&(dbstatus.newest_createdate_workinfo), &(row->createdate)); |
|
|
|
tick(); |
|
} |
|
if (!ok) |
|
k_add_head(workinfo_free, item); |
|
|
|
K_WUNLOCK(workinfo_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d workinfo records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
void workinfo_reload() |
|
{ |
|
// TODO: ??? a bad idea? |
|
/* |
|
PGconn *conn = dbconnect(); |
|
|
|
K_WLOCK(workinfo_free); |
|
workinfo_root = free_ktree(workinfo_root, ???); free transactiontree and merklehash |
|
k_list_transfer_to_head(workinfo_store, workinfo_free); |
|
K_WUNLOCK(workinfo_free); |
|
|
|
workinfo_fill(conn); |
|
|
|
PQfinish(conn); |
|
*/ |
|
} |
|
|
|
// order by workinfoid asc,userid asc,workername asc,createdate asc,nonce asc,expirydate desc |
|
static cmp_t cmp_shares(K_ITEM *a, K_ITEM *b) |
|
{ |
|
SHARES *sa, *sb; |
|
DATA_SHARES(sa, a); |
|
DATA_SHARES(sb, b); |
|
cmp_t c = CMP_BIGINT(sa->workinfoid, sb->workinfoid); |
|
if (c == 0) { |
|
c = CMP_BIGINT(sa->userid, sb->userid); |
|
if (c == 0) { |
|
c = CMP_STR(sa->workername, sb->workername); |
|
if (c == 0) { |
|
c = CMP_TV(sa->createdate, sb->createdate); |
|
if (c == 0) { |
|
c = CMP_STR(sa->nonce, sb->nonce); |
|
if (c == 0) { |
|
c = CMP_TV(sb->expirydate, |
|
sa->expirydate); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
return c; |
|
} |
|
|
|
static void zero_sharesummary(SHARESUMMARY *row, tv_t *cd, double diff) |
|
{ |
|
row->diffacc = row->diffsta = row->diffdup = row->diffhi = |
|
row->diffrej = row->shareacc = row->sharesta = row->sharedup = |
|
row->sharehi = row->sharerej = 0.0; |
|
row->sharecount = row->errorcount = row->countlastupdate = 0; |
|
row->reset = false; |
|
row->firstshare.tv_sec = cd->tv_sec; |
|
row->firstshare.tv_usec = cd->tv_usec; |
|
row->lastshare.tv_sec = row->firstshare.tv_sec; |
|
row->lastshare.tv_usec = row->firstshare.tv_usec; |
|
row->lastdiffacc = diff; |
|
row->complete[0] = SUMMARY_NEW; |
|
row->complete[1] = '\0'; |
|
} |
|
|
|
static K_ITEM *find_sharesummary(int64_t userid, char *workername, int64_t workinfoid); |
|
|
|
// Memory (and log file) only |
|
static bool shares_add(PGconn *conn, char *workinfoid, char *username, char *workername, |
|
char *clientid, char *errn, char *enonce1, char *nonce2, |
|
char *nonce, char *diff, char *sdiff, char *secondaryuserid, |
|
char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *s_item, *u_item, *wi_item, *w_item, *ss_item; |
|
char cd_buf[DATE_BUFSIZ]; |
|
SHARESUMMARY *sharesummary; |
|
SHARES *shares; |
|
USERS *users; |
|
bool ok = false; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(shares_free); |
|
s_item = k_unlink_head(shares_free); |
|
K_WUNLOCK(shares_free); |
|
|
|
DATA_SHARES(shares, s_item); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s() %s/%ld,%ld %.19s no user! Share discarded!", |
|
__func__, username, |
|
cd->tv_sec, cd->tv_usec, cd_buf); |
|
goto unitem; |
|
} |
|
DATA_USERS(users, u_item); |
|
|
|
shares->userid = users->userid; |
|
|
|
TXT_TO_BIGINT("workinfoid", workinfoid, shares->workinfoid); |
|
STRNCPY(shares->workername, workername); |
|
TXT_TO_INT("clientid", clientid, shares->clientid); |
|
TXT_TO_INT("errn", errn, shares->errn); |
|
STRNCPY(shares->enonce1, enonce1); |
|
STRNCPY(shares->nonce2, nonce2); |
|
STRNCPY(shares->nonce, nonce); |
|
TXT_TO_DOUBLE("diff", diff, shares->diff); |
|
TXT_TO_DOUBLE("sdiff", sdiff, shares->sdiff); |
|
STRNCPY(shares->secondaryuserid, secondaryuserid); |
|
|
|
if (!(*secondaryuserid)) { |
|
STRNCPY(shares->secondaryuserid, users->secondaryuserid); |
|
if (!tv_newer(&missing_secuser_min, cd) || |
|
!tv_newer(cd, &missing_secuser_max)) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s() %s/%ld,%ld %.19s missing secondaryuserid! " |
|
"Share corrected", |
|
__func__, username, |
|
cd->tv_sec, cd->tv_usec, cd_buf); |
|
} |
|
} |
|
|
|
HISTORYDATEINIT(shares, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, shares); |
|
|
|
wi_item = find_workinfo(shares->workinfoid); |
|
if (!wi_item) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s() %"PRId64"/%s/%ld,%ld %.19s no workinfo! Share discarded!", |
|
__func__, shares->workinfoid, workername, |
|
cd->tv_sec, cd->tv_usec, cd_buf); |
|
goto unitem; |
|
} |
|
|
|
w_item = new_default_worker(conn, false, shares->userid, shares->workername, |
|
by, code, inet, cd, trf_root); |
|
if (!w_item) |
|
goto unitem; |
|
|
|
if (reloading && !confirm_sharesummary) { |
|
ss_item = find_sharesummary(shares->userid, shares->workername, shares->workinfoid); |
|
if (ss_item) { |
|
DATA_SHARESUMMARY(sharesummary, ss_item); |
|
if (sharesummary->complete[0] != SUMMARY_NEW) { |
|
K_WLOCK(shares_free); |
|
k_add_head(shares_free, s_item); |
|
K_WUNLOCK(shares_free); |
|
return true; |
|
} |
|
|
|
if (!sharesummary->reset) { |
|
zero_sharesummary(sharesummary, cd, shares->diff); |
|
sharesummary->reset = true; |
|
} |
|
} |
|
} |
|
|
|
if (!confirm_sharesummary) |
|
workerstatus_update(NULL, shares, NULL); |
|
|
|
sharesummary_update(conn, shares, NULL, NULL, by, code, inet, cd); |
|
|
|
ok = true; |
|
unitem: |
|
K_WLOCK(shares_free); |
|
if (!ok) |
|
k_add_head(shares_free, s_item); |
|
else { |
|
shares_root = add_to_ktree(shares_root, s_item, cmp_shares); |
|
k_add_head(shares_store, s_item); |
|
} |
|
K_WUNLOCK(shares_free); |
|
|
|
return ok; |
|
} |
|
|
|
static bool shares_fill() |
|
{ |
|
return true; |
|
} |
|
|
|
// order by workinfoid asc,userid asc,createdate asc,nonce asc,expirydate desc |
|
static cmp_t cmp_shareerrors(K_ITEM *a, K_ITEM *b) |
|
{ |
|
SHAREERRORS *sa, *sb; |
|
DATA_SHAREERRORS(sa, a); |
|
DATA_SHAREERRORS(sb, b); |
|
cmp_t c = CMP_BIGINT(sa->workinfoid, sb->workinfoid); |
|
if (c == 0) { |
|
c = CMP_BIGINT(sa->userid, sb->userid); |
|
if (c == 0) { |
|
c = CMP_TV(sa->createdate, sb->createdate); |
|
if (c == 0) |
|
c = CMP_TV(sb->expirydate, sa->expirydate); |
|
} |
|
} |
|
return c; |
|
} |
|
|
|
// Memory (and log file) only |
|
// TODO: handle shareerrors that appear after a workinfoid is aged or doesn't exist? |
|
static bool shareerrors_add(PGconn *conn, char *workinfoid, char *username, |
|
char *workername, char *clientid, char *errn, |
|
char *error, char *secondaryuserid, char *by, |
|
char *code, char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
K_ITEM *s_item, *u_item, *wi_item, *w_item, *ss_item; |
|
char cd_buf[DATE_BUFSIZ]; |
|
SHARESUMMARY *sharesummary; |
|
SHAREERRORS *shareerrors; |
|
USERS *users; |
|
bool ok = false; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(shareerrors_free); |
|
s_item = k_unlink_head(shareerrors_free); |
|
K_WUNLOCK(shareerrors_free); |
|
|
|
DATA_SHAREERRORS(shareerrors, s_item); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s() %s/%ld,%ld %.19s no user! Shareerror discarded!", |
|
__func__, username, |
|
cd->tv_sec, cd->tv_usec, cd_buf); |
|
goto unitem; |
|
} |
|
DATA_USERS(users, u_item); |
|
|
|
shareerrors->userid = users->userid; |
|
|
|
TXT_TO_BIGINT("workinfoid", workinfoid, shareerrors->workinfoid); |
|
STRNCPY(shareerrors->workername, workername); |
|
TXT_TO_INT("clientid", clientid, shareerrors->clientid); |
|
TXT_TO_INT("errn", errn, shareerrors->errn); |
|
STRNCPY(shareerrors->error, error); |
|
STRNCPY(shareerrors->secondaryuserid, secondaryuserid); |
|
|
|
if (!(*secondaryuserid)) { |
|
STRNCPY(shareerrors->secondaryuserid, users->secondaryuserid); |
|
if (!tv_newer(&missing_secuser_min, cd) || |
|
!tv_newer(cd, &missing_secuser_max)) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s() %s/%ld,%ld %.19s missing secondaryuserid! " |
|
"Sharerror corrected", |
|
__func__, username, |
|
cd->tv_sec, cd->tv_usec, cd_buf); |
|
} |
|
} |
|
|
|
HISTORYDATEINIT(shareerrors, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, shareerrors); |
|
|
|
wi_item = find_workinfo(shareerrors->workinfoid); |
|
if (!wi_item) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s() %"PRId64"/%s/%ld,%ld %.19s no workinfo! Shareerror discarded!", |
|
__func__, shareerrors->workinfoid, workername, |
|
cd->tv_sec, cd->tv_usec, cd_buf); |
|
goto unitem; |
|
} |
|
|
|
w_item = new_default_worker(NULL, false, shareerrors->userid, shareerrors->workername, |
|
by, code, inet, cd, trf_root); |
|
if (!w_item) |
|
goto unitem; |
|
|
|
if (reloading && !confirm_sharesummary) { |
|
ss_item = find_sharesummary(shareerrors->userid, shareerrors->workername, shareerrors->workinfoid); |
|
if (ss_item) { |
|
DATA_SHARESUMMARY(sharesummary, ss_item); |
|
if (sharesummary->complete[0] != SUMMARY_NEW) { |
|
K_WLOCK(shareerrors_free); |
|
k_add_head(shareerrors_free, s_item); |
|
K_WUNLOCK(shareerrors_free); |
|
return true; |
|
} |
|
|
|
if (!sharesummary->reset) { |
|
zero_sharesummary(sharesummary, cd, 0.0); |
|
sharesummary->reset = true; |
|
} |
|
} |
|
} |
|
|
|
sharesummary_update(conn, NULL, shareerrors, NULL, by, code, inet, cd); |
|
|
|
ok = true; |
|
unitem: |
|
K_WLOCK(shareerrors_free); |
|
if (!ok) |
|
k_add_head(shareerrors_free, s_item); |
|
else { |
|
shareerrors_root = add_to_ktree(shareerrors_root, s_item, cmp_shareerrors); |
|
k_add_head(shareerrors_store, s_item); |
|
} |
|
K_WUNLOCK(shareerrors_free); |
|
|
|
return ok; |
|
} |
|
|
|
static bool shareerrors_fill() |
|
{ |
|
return true; |
|
} |
|
|
|
static void dsp_sharesummary(K_ITEM *item, FILE *stream) |
|
{ |
|
char createdate_buf[DATE_BUFSIZ]; |
|
SHARESUMMARY *s; |
|
|
|
if (!item) |
|
fprintf(stream, "%s() called with (null) item\n", __func__); |
|
else { |
|
DATA_SHARESUMMARY(s, item); |
|
|
|
tv_to_buf(&(s->createdate), createdate_buf, sizeof(createdate_buf)); |
|
fprintf(stream, " uid=%"PRId64" wn='%s' wid=%"PRId64" " |
|
"da=%f ds=%f ss=%f c='%s' cd=%s\n", |
|
s->userid, s->workername, s->workinfoid, |
|
s->diffacc, s->diffsta, s->sharesta, |
|
s->complete, createdate_buf); |
|
} |
|
} |
|
|
|
// default tree order by userid asc,workername asc,workinfoid asc for reporting |
|
static cmp_t cmp_sharesummary(K_ITEM *a, K_ITEM *b) |
|
{ |
|
SHARESUMMARY *sa, *sb; |
|
DATA_SHARESUMMARY(sa, a); |
|
DATA_SHARESUMMARY(sb, b); |
|
cmp_t c = CMP_BIGINT(sa->userid, sb->userid); |
|
if (c == 0) { |
|
c = CMP_STR(sa->workername, sb->workername); |
|
if (c == 0) |
|
c = CMP_BIGINT(sa->workinfoid, sb->workinfoid); |
|
} |
|
return c; |
|
} |
|
|
|
// order by workinfoid asc,userid asc,workername asc for flagging complete |
|
static cmp_t cmp_sharesummary_workinfoid(K_ITEM *a, K_ITEM *b) |
|
{ |
|
SHARESUMMARY *sa, *sb; |
|
DATA_SHARESUMMARY(sa, a); |
|
DATA_SHARESUMMARY(sb, b); |
|
cmp_t c = CMP_BIGINT(sa->workinfoid, sb->workinfoid); |
|
if (c == 0) { |
|
c = CMP_BIGINT(sa->userid, sb->userid); |
|
if (c == 0) |
|
c = CMP_STR(sa->workername, sb->workername); |
|
} |
|
return c; |
|
} |
|
|
|
static K_ITEM *find_sharesummary(int64_t userid, char *workername, int64_t workinfoid) |
|
{ |
|
SHARESUMMARY sharesummary; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look; |
|
|
|
sharesummary.userid = userid; |
|
STRNCPY(sharesummary.workername, workername); |
|
sharesummary.workinfoid = workinfoid; |
|
|
|
INIT_SHARESUMMARY(&look); |
|
look.data = (void *)(&sharesummary); |
|
return find_in_ktree(sharesummary_root, &look, cmp_sharesummary, ctx); |
|
} |
|
|
|
static bool _sharesummary_update(PGconn *conn, SHARES *s_row, SHAREERRORS *e_row, K_ITEM *ss_item, |
|
char *by, char *code, char *inet, tv_t *cd, WHERE_FFL_ARGS) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res = NULL; |
|
SHARESUMMARY *row; |
|
K_ITEM *item; |
|
char *ins, *upd; |
|
bool ok = false, new; |
|
char *params[19 + MODIFYDATECOUNT]; |
|
int n, par = 0; |
|
int64_t userid, workinfoid; |
|
char *workername; |
|
tv_t *sharecreatedate; |
|
bool must_update = false, conned = false; |
|
double diff = 0; |
|
|
|
LOGDEBUG("%s(): update", __func__); |
|
|
|
if (ss_item) { |
|
if (s_row || e_row) { |
|
quithere(1, "ERR: only one of s_row, e_row and " |
|
"ss_item allowed" WHERE_FFL, |
|
WHERE_FFL_PASS); |
|
} |
|
new = false; |
|
item = ss_item; |
|
DATA_SHARESUMMARY(row, item); |
|
must_update = true; |
|
row->complete[0] = SUMMARY_COMPLETE; |
|
row->complete[1] = '\0'; |
|
} else { |
|
if (s_row) { |
|
if (e_row) { |
|
quithere(1, "ERR: only one of s_row, e_row " |
|
"(and ss_item) allowed" WHERE_FFL, |
|
WHERE_FFL_PASS); |
|
} |
|
userid = s_row->userid; |
|
workername = s_row->workername; |
|
workinfoid = s_row->workinfoid; |
|
diff = s_row->diff; |
|
sharecreatedate = &(s_row->createdate); |
|
} else { |
|
if (!e_row) { |
|
quithere(1, "ERR: all s_row, e_row and " |
|
"ss_item are NULL" WHERE_FFL, |
|
WHERE_FFL_PASS); |
|
} |
|
userid = e_row->userid; |
|
workername = e_row->workername; |
|
workinfoid = e_row->workinfoid; |
|
sharecreatedate = &(e_row->createdate); |
|
} |
|
|
|
K_RLOCK(sharesummary_free); |
|
item = find_sharesummary(userid, workername, workinfoid); |
|
K_RUNLOCK(sharesummary_free); |
|
if (item) { |
|
new = false; |
|
DATA_SHARESUMMARY(row, item); |
|
} else { |
|
new = true; |
|
K_WLOCK(sharesummary_free); |
|
item = k_unlink_head(sharesummary_free); |
|
K_WUNLOCK(sharesummary_free); |
|
DATA_SHARESUMMARY(row, item); |
|
row->userid = userid; |
|
STRNCPY(row->workername, workername); |
|
row->workinfoid = workinfoid; |
|
zero_sharesummary(row, sharecreatedate, diff); |
|
row->inserted = false; |
|
row->saveaged = false; |
|
} |
|
|
|
if (e_row) |
|
row->errorcount += 1; |
|
else { |
|
row->sharecount += 1; |
|
switch (s_row->errn) { |
|
case SE_NONE: |
|
row->diffacc += s_row->diff; |
|
row->shareacc++; |
|
break; |
|
case SE_STALE: |
|
row->diffsta += s_row->diff; |
|
row->sharesta++; |
|
break; |
|
case SE_DUPE: |
|
row->diffdup += s_row->diff; |
|
row->sharedup++; |
|
break; |
|
case SE_HIGH_DIFF: |
|
row->diffhi += s_row->diff; |
|
row->sharehi++; |
|
break; |
|
default: |
|
row->diffrej += s_row->diff; |
|
row->sharerej++; |
|
break; |
|
} |
|
} |
|
|
|
if (!new) { |
|
double td; |
|
td = tvdiff(sharecreatedate, &(row->firstshare)); |
|
// don't LOGERR '=' in case shares come from ckpool with the same timestamp |
|
if (td < 0.0) { |
|
char *tmp1, *tmp2; |
|
LOGERR("%s(): %s createdate (%s) is < summary firstshare (%s)", |
|
__func__, s_row ? "shares" : "shareerrors", |
|
(tmp1 = ctv_to_buf(sharecreatedate, NULL, 0)), |
|
(tmp2 = ctv_to_buf(&(row->firstshare), NULL, 0))); |
|
free(tmp2); |
|
free(tmp1); |
|
row->firstshare.tv_sec = sharecreatedate->tv_sec; |
|
row->firstshare.tv_usec = sharecreatedate->tv_usec; |
|
// Don't change lastdiffacc |
|
} |
|
td = tvdiff(sharecreatedate, &(row->lastshare)); |
|
// don't LOGERR '=' in case shares come from ckpool with the same timestamp |
|
if (td >= 0.0) { |
|
row->lastshare.tv_sec = sharecreatedate->tv_sec; |
|
row->lastshare.tv_usec = sharecreatedate->tv_usec; |
|
row->lastdiffacc = diff; |
|
} else { |
|
char *tmp1, *tmp2; |
|
LOGERR("%s(): %s createdate (%s) is < summary lastshare (%s)", |
|
__func__, s_row ? "shares" : "shareerrors", |
|
(tmp1 = ctv_to_buf(sharecreatedate, NULL, 0)), |
|
(tmp2 = ctv_to_buf(&(row->lastshare), NULL, 0))); |
|
free(tmp2); |
|
free(tmp1); |
|
} |
|
if (row->complete[0] != SUMMARY_NEW) { |
|
LOGDEBUG("%s(): updating sharesummary not '%c' %"PRId64"/%s/%"PRId64"/%s", |
|
__func__, SUMMARY_NEW, row->userid, row->workername, |
|
row->workinfoid, row->complete); |
|
} |
|
} |
|
} |
|
|
|
// During startup, don't save 'new' sharesummaries, to reduce DB I/O |
|
if (!startup_complete && row->complete[0] == SUMMARY_NEW) |
|
goto startupskip; |
|
|
|
if (conn == NULL && !confirm_sharesummary) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
if (new || !(row->inserted)) { |
|
MODIFYDATEINIT(row, cd, by, code, inet); |
|
|
|
par = 0; |
|
|
|
if (!confirm_sharesummary) { |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = bigint_to_buf(row->workinfoid, NULL, 0); |
|
params[par++] = double_to_buf(row->diffacc, NULL, 0); |
|
params[par++] = double_to_buf(row->diffsta, NULL, 0); |
|
params[par++] = double_to_buf(row->diffdup, NULL, 0); |
|
params[par++] = double_to_buf(row->diffhi, NULL, 0); |
|
params[par++] = double_to_buf(row->diffrej, NULL, 0); |
|
params[par++] = double_to_buf(row->shareacc, NULL, 0); |
|
params[par++] = double_to_buf(row->sharesta, NULL, 0); |
|
params[par++] = double_to_buf(row->sharedup, NULL, 0); |
|
params[par++] = double_to_buf(row->sharehi, NULL, 0); |
|
params[par++] = double_to_buf(row->sharerej, NULL, 0); |
|
params[par++] = bigint_to_buf(row->sharecount, NULL, 0); |
|
params[par++] = bigint_to_buf(row->errorcount, NULL, 0); |
|
params[par++] = tv_to_buf(&(row->firstshare), NULL, 0); |
|
params[par++] = tv_to_buf(&(row->lastshare), NULL, 0); |
|
params[par++] = double_to_buf(row->lastdiffacc, NULL, 0); |
|
params[par++] = str_to_buf(row->complete, NULL, 0); |
|
MODIFYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into sharesummary " |
|
"(userid,workername,workinfoid,diffacc,diffsta,diffdup,diffhi," |
|
"diffrej,shareacc,sharesta,sharedup,sharehi,sharerej," |
|
"sharecount,errorcount,firstshare,lastshare," |
|
"lastdiffacc,complete" |
|
MODIFYDATECONTROL ") values (" PQPARAM27 ")"; |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
} |
|
|
|
row->countlastupdate = row->sharecount + row->errorcount; |
|
row->inserted = true; |
|
if (row->complete[0] == SUMMARY_COMPLETE) |
|
row->saveaged = true; |
|
} else { |
|
bool stats_update = false; |
|
|
|
MODIFYUPDATE(row, cd, by, code, inet); |
|
|
|
if ((row->countlastupdate + SHARESUMMARY_UPDATE_EVERY) < |
|
(row->sharecount + row->errorcount)) |
|
stats_update = true; |
|
|
|
if (must_update && row->countlastupdate < (row->sharecount + row->errorcount)) |
|
stats_update = true; |
|
|
|
if (stats_update) { |
|
par = 0; |
|
if (!confirm_sharesummary) { |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = bigint_to_buf(row->workinfoid, NULL, 0); |
|
params[par++] = double_to_buf(row->diffacc, NULL, 0); |
|
params[par++] = double_to_buf(row->diffsta, NULL, 0); |
|
params[par++] = double_to_buf(row->diffdup, NULL, 0); |
|
params[par++] = double_to_buf(row->diffhi, NULL, 0); |
|
params[par++] = double_to_buf(row->diffrej, NULL, 0); |
|
params[par++] = double_to_buf(row->shareacc, NULL, 0); |
|
params[par++] = double_to_buf(row->sharesta, NULL, 0); |
|
params[par++] = double_to_buf(row->sharedup, NULL, 0); |
|
params[par++] = double_to_buf(row->sharehi, NULL, 0); |
|
params[par++] = double_to_buf(row->sharerej, NULL, 0); |
|
params[par++] = tv_to_buf(&(row->firstshare), NULL, 0); |
|
params[par++] = tv_to_buf(&(row->lastshare), NULL, 0); |
|
params[par++] = bigint_to_buf(row->sharecount, NULL, 0); |
|
params[par++] = bigint_to_buf(row->errorcount, NULL, 0); |
|
params[par++] = double_to_buf(row->lastdiffacc, NULL, 0); |
|
params[par++] = str_to_buf(row->complete, NULL, 0); |
|
MODIFYUPDATEPARAMS(params, par, row); |
|
PARCHKVAL(par, 23, params); |
|
|
|
upd = "update sharesummary " |
|
"set diffacc=$4,diffsta=$5,diffdup=$6,diffhi=$7,diffrej=$8," |
|
"shareacc=$9,sharesta=$10,sharedup=$11,sharehi=$12," |
|
"sharerej=$13,firstshare=$14,lastshare=$15," |
|
"sharecount=$16,errorcount=$17,lastdiffacc=$18,complete=$19" |
|
",modifydate=$20,modifyby=$21,modifycode=$22,modifyinet=$23 " |
|
"where userid=$1 and workername=$2 and workinfoid=$3"; |
|
|
|
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Update", rescode, conn); |
|
goto unparam; |
|
} |
|
} |
|
row->countlastupdate = row->sharecount + row->errorcount; |
|
if (row->complete[0] == SUMMARY_COMPLETE) |
|
row->saveaged = true; |
|
} else { |
|
if (!must_update) { |
|
ok = true; |
|
goto late; |
|
} else { |
|
par = 0; |
|
if (!confirm_sharesummary) { |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = bigint_to_buf(row->workinfoid, NULL, 0); |
|
params[par++] = str_to_buf(row->complete, NULL, 0); |
|
MODIFYUPDATEPARAMS(params, par, row); |
|
PARCHKVAL(par, 8, params); |
|
|
|
upd = "update sharesummary " |
|
"set complete=$4,modifydate=$5,modifyby=$6,modifycode=$7,modifyinet=$8 " |
|
"where userid=$1 and workername=$2 and workinfoid=$3"; |
|
|
|
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("MustUpdate", rescode, conn); |
|
goto unparam; |
|
} |
|
} |
|
row->countlastupdate = row->sharecount + row->errorcount; |
|
if (row->complete[0] == SUMMARY_COMPLETE) |
|
row->saveaged = true; |
|
} |
|
} |
|
} |
|
startupskip: |
|
ok = true; |
|
unparam: |
|
if (par) { |
|
PQclear(res); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
} |
|
late: |
|
if (conned) |
|
PQfinish(conn); |
|
|
|
// We keep the new item no matter what 'ok' is, since it will be inserted later |
|
K_WLOCK(sharesummary_free); |
|
if (new) { |
|
sharesummary_root = add_to_ktree(sharesummary_root, item, cmp_sharesummary); |
|
sharesummary_workinfoid_root = add_to_ktree(sharesummary_workinfoid_root, |
|
item, |
|
cmp_sharesummary_workinfoid); |
|
k_add_head(sharesummary_store, item); |
|
} |
|
K_WUNLOCK(sharesummary_free); |
|
|
|
return ok; |
|
} |
|
|
|
static bool sharesummary_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
SHARESUMMARY *row; |
|
char *field; |
|
char *sel; |
|
int fields = 19; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
// TODO: limit how far back |
|
sel = "select " |
|
"userid,workername,workinfoid,diffacc,diffsta,diffdup,diffhi," |
|
"diffrej,shareacc,sharesta,sharedup,sharehi,sharerej," |
|
"sharecount,errorcount,firstshare,lastshare," |
|
"lastdiffacc,complete" |
|
MODIFYDATECONTROL |
|
" from sharesummary"; |
|
res = PQexec(conn, sel, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != (fields + MODIFYDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + MODIFYDATECOUNT, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(sharesummary_free); |
|
DATA_SHARESUMMARY(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
row->inserted = true; |
|
|
|
PQ_GET_FLD(res, i, "userid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("userid", field, row->userid); |
|
|
|
PQ_GET_FLD(res, i, "workername", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("workername", field, row->workername); |
|
|
|
PQ_GET_FLD(res, i, "workinfoid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("workinfoid", field, row->workinfoid); |
|
|
|
PQ_GET_FLD(res, i, "diffacc", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffacc", field, row->diffacc); |
|
|
|
PQ_GET_FLD(res, i, "diffsta", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffsta", field, row->diffsta); |
|
|
|
PQ_GET_FLD(res, i, "diffdup", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffdup", field, row->diffdup); |
|
|
|
PQ_GET_FLD(res, i, "diffhi", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffhi", field, row->diffhi); |
|
|
|
PQ_GET_FLD(res, i, "diffrej", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffrej", field, row->diffrej); |
|
|
|
PQ_GET_FLD(res, i, "shareacc", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("shareacc", field, row->shareacc); |
|
|
|
PQ_GET_FLD(res, i, "sharesta", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("sharesta", field, row->sharesta); |
|
|
|
PQ_GET_FLD(res, i, "sharedup", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("sharedup", field, row->sharedup); |
|
|
|
PQ_GET_FLD(res, i, "sharehi", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("sharehi", field, row->sharehi); |
|
|
|
PQ_GET_FLD(res, i, "sharerej", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("sharerej", field, row->sharerej); |
|
|
|
PQ_GET_FLD(res, i, "sharecount", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("sharecount", field, row->sharecount); |
|
|
|
PQ_GET_FLD(res, i, "errorcount", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("errorcount", field, row->errorcount); |
|
|
|
row->countlastupdate = row->sharecount + row->errorcount; |
|
|
|
PQ_GET_FLD(res, i, "firstshare", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_TV("firstshare", field, row->firstshare); |
|
|
|
PQ_GET_FLD(res, i, "lastshare", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_TV("lastshare", field, row->lastshare); |
|
|
|
PQ_GET_FLD(res, i, "lastdiffacc", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("lastdiffacc", field, row->lastdiffacc); |
|
|
|
PQ_GET_FLD(res, i, "complete", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("complete", field, row->complete); |
|
|
|
MODIFYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
sharesummary_root = add_to_ktree(sharesummary_root, item, cmp_sharesummary); |
|
sharesummary_workinfoid_root = add_to_ktree(sharesummary_workinfoid_root, item, cmp_sharesummary_workinfoid); |
|
k_add_head(sharesummary_store, item); |
|
|
|
// A share summary is currently only shares in a single workinfo, at all 3 levels n,a,y |
|
if (tolower(row->complete[0]) == SUMMARY_NEW) { |
|
if (dbstatus.oldest_sharesummary_firstshare_n.tv_sec == 0 || |
|
!tv_newer(&(dbstatus.oldest_sharesummary_firstshare_n), &(row->firstshare))) { |
|
copy_tv(&(dbstatus.oldest_sharesummary_firstshare_n), &(row->firstshare)); |
|
dbstatus.oldest_workinfoid_n = row->workinfoid; |
|
} |
|
} else { |
|
if (tv_newer(&(dbstatus.newest_sharesummary_firstshare_ay), &(row->firstshare))) |
|
copy_tv(&(dbstatus.newest_sharesummary_firstshare_ay), &(row->firstshare)); |
|
if (tolower(row->complete[0]) == SUMMARY_COMPLETE) { |
|
if (dbstatus.oldest_sharesummary_firstshare_a.tv_sec == 0 || |
|
!tv_newer(&(dbstatus.oldest_sharesummary_firstshare_a), &(row->firstshare))) { |
|
copy_tv(&(dbstatus.oldest_sharesummary_firstshare_a), &(row->firstshare)); |
|
dbstatus.oldest_workinfoid_a = row->workinfoid; |
|
} |
|
if (tv_newer(&(dbstatus.newest_sharesummary_firstshare_a), &(row->firstshare))) { |
|
copy_tv(&(dbstatus.newest_sharesummary_firstshare_a), &(row->firstshare)); |
|
dbstatus.newest_workinfoid_a = row->workinfoid; |
|
} |
|
} else /* SUMMARY_CONFIRM */ { |
|
if (tv_newer(&(dbstatus.newest_sharesummary_firstshare_y), &(row->firstshare))) { |
|
copy_tv(&(dbstatus.newest_sharesummary_firstshare_y), &(row->firstshare)); |
|
dbstatus.newest_workinfoid_y = row->workinfoid; |
|
} |
|
} |
|
} |
|
|
|
tick(); |
|
} |
|
if (!ok) |
|
k_add_head(sharesummary_free, item); |
|
|
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d sharesummary records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
void sharesummary_reload() |
|
{ |
|
PGconn *conn = dbconnect(); |
|
|
|
sharesummary_root = free_ktree(sharesummary_root, NULL); |
|
sharesummary_workinfoid_root = free_ktree(sharesummary_workinfoid_root, NULL); |
|
k_list_transfer_to_head(sharesummary_store, sharesummary_free); |
|
|
|
sharesummary_fill(conn); |
|
|
|
PQfinish(conn); |
|
} |
|
|
|
// TODO: do this better ... :) |
|
static void dsp_hash(char *hash, char *buf, size_t siz) |
|
{ |
|
char *ptr; |
|
|
|
ptr = hash + strlen(hash) - (siz - 1) - 8; |
|
if (ptr < hash) |
|
ptr = hash; |
|
STRNCPYSIZ(buf, ptr, siz); |
|
} |
|
|
|
static void dsp_blocks(K_ITEM *item, FILE *stream) |
|
{ |
|
char createdate_buf[DATE_BUFSIZ], expirydate_buf[DATE_BUFSIZ]; |
|
BLOCKS *b = NULL; |
|
char hash_dsp[16+1]; |
|
|
|
if (!item) |
|
fprintf(stream, "%s() called with (null) item\n", __func__); |
|
else { |
|
DATA_BLOCKS(b, item); |
|
|
|
dsp_hash(b->blockhash, hash_dsp, sizeof(hash_dsp)); |
|
tv_to_buf(&(b->createdate), createdate_buf, sizeof(createdate_buf)); |
|
tv_to_buf(&(b->expirydate), expirydate_buf, sizeof(expirydate_buf)); |
|
fprintf(stream, " hi=%d hash='%.16s' conf=%s uid=%"PRId64 |
|
" w='%s' sconf=%s cd=%s ed=%s\n", |
|
b->height, hash_dsp, b->confirmed, b->userid, |
|
b->workername, b->statsconfirmed, |
|
createdate_buf, expirydate_buf); |
|
} |
|
} |
|
|
|
// order by height asc,blockhash asc,expirydate desc |
|
static cmp_t cmp_blocks(K_ITEM *a, K_ITEM *b) |
|
{ |
|
BLOCKS *ba, *bb; |
|
DATA_BLOCKS(ba, a); |
|
DATA_BLOCKS(bb, b); |
|
cmp_t c = CMP_INT(ba->height, bb->height); |
|
if (c == 0) { |
|
c = CMP_STR(ba->blockhash, bb->blockhash); |
|
if (c == 0) |
|
c = CMP_TV(bb->expirydate, ba->expirydate); |
|
} |
|
return c; |
|
} |
|
|
|
/* TODO: and make sure all block searches use these |
|
* or add new ones as required here */ |
|
|
|
// Must be R or W locked before call - gets current status (default_expiry) |
|
static K_ITEM *find_blocks(int32_t height, char *blockhash) |
|
{ |
|
BLOCKS blocks; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look; |
|
|
|
blocks.height = height; |
|
STRNCPY(blocks.blockhash, blockhash); |
|
blocks.expirydate.tv_sec = default_expiry.tv_sec; |
|
blocks.expirydate.tv_usec = default_expiry.tv_usec; |
|
|
|
INIT_BLOCKS(&look); |
|
look.data = (void *)(&blocks); |
|
return find_in_ktree(blocks_root, &look, cmp_blocks, ctx); |
|
} |
|
|
|
// Must be R or W locked before call |
|
static K_ITEM *find_prev_blocks(int32_t height) |
|
{ |
|
BLOCKS lookblocks, *blocks; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look, *b_item; |
|
|
|
/* TODO: For self orphaned (if that ever happens) |
|
* this will find based on blockhash order if it has two, |
|
* not NEW, blocks, which might not find the right one */ |
|
lookblocks.height = height; |
|
lookblocks.blockhash[0] = '\0'; |
|
lookblocks.expirydate.tv_sec = 0L; |
|
lookblocks.expirydate.tv_usec = 0L; |
|
|
|
INIT_BLOCKS(&look); |
|
look.data = (void *)(&lookblocks); |
|
b_item = find_before_in_ktree(blocks_root, &look, cmp_blocks, ctx); |
|
while (b_item) { |
|
DATA_BLOCKS(blocks, b_item); |
|
if (blocks->confirmed[0] != BLOCKS_NEW && |
|
CURRENT(&(blocks->expirydate))) |
|
return b_item; |
|
b_item = prev_in_ktree(ctx); |
|
} |
|
return NULL; |
|
} |
|
|
|
static const char *blocks_confirmed(char *confirmed) |
|
{ |
|
switch (confirmed[0]) { |
|
case BLOCKS_NEW: |
|
return blocks_new; |
|
case BLOCKS_CONFIRM: |
|
return blocks_confirm; |
|
case BLOCKS_42: |
|
return blocks_42; |
|
case BLOCKS_ORPHAN: |
|
return blocks_orphan; |
|
} |
|
return blocks_unknown; |
|
} |
|
|
|
static bool blocks_stats(PGconn *conn, int32_t height, char *blockhash, |
|
double diffacc, double diffinv, double shareacc, |
|
double shareinv, int64_t elapsed, |
|
char *by, char *code, char *inet, tv_t *cd) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res = NULL; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM *b_item, *old_b_item; |
|
BLOCKS *row, *oldblocks; |
|
char hash_dsp[16+1]; |
|
char *upd, *ins; |
|
char *params[8 + HISTORYDATECOUNT]; |
|
bool ok = false, update_old = false; |
|
int par = 0; |
|
int n; |
|
|
|
LOGDEBUG("%s(): confirm", __func__); |
|
|
|
dsp_hash(blockhash, hash_dsp, sizeof(hash_dsp)); |
|
|
|
K_RLOCK(blocks_free); |
|
old_b_item = find_blocks(height, blockhash); |
|
K_RUNLOCK(blocks_free); |
|
|
|
if (!old_b_item) { |
|
LOGERR("%s(): Non-existent Block: %d/...%s", |
|
__func__, height, hash_dsp); |
|
return false; |
|
} |
|
|
|
DATA_BLOCKS(oldblocks, old_b_item); |
|
|
|
K_WLOCK(blocks_free); |
|
b_item = k_unlink_head(blocks_free); |
|
K_WUNLOCK(blocks_free); |
|
|
|
DATA_BLOCKS(row, b_item); |
|
memcpy(row, oldblocks, sizeof(*row)); |
|
row->diffacc = diffacc; |
|
row->diffinv = diffinv; |
|
row->shareacc = shareacc; |
|
row->shareinv = shareinv; |
|
row->elapsed = elapsed; |
|
row->statsconfirmed[0] = BLOCKS_STATSCONFIRMED; |
|
row->statsconfirmed[1] = '\0'; |
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
|
|
upd = "update blocks set expirydate=$1 where blockhash=$2 and expirydate=$3"; |
|
par = 0; |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = str_to_buf(row->blockhash, NULL, 0); |
|
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); |
|
PARCHKVAL(par, 3, params); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
goto unparam; |
|
} |
|
PQclear(res); |
|
|
|
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Update", rescode, conn); |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
goto unparam; |
|
} |
|
|
|
update_old = true; |
|
|
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
par = 0; |
|
params[par++] = str_to_buf(row->blockhash, NULL, 0); |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = double_to_buf(row->diffacc, NULL, 0); |
|
params[par++] = double_to_buf(row->diffinv, NULL, 0); |
|
params[par++] = double_to_buf(row->shareacc, NULL, 0); |
|
params[par++] = double_to_buf(row->shareinv, NULL, 0); |
|
params[par++] = bigint_to_buf(row->elapsed, NULL, 0); |
|
params[par++] = str_to_buf(row->statsconfirmed, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHKVAL(par, 8 + HISTORYDATECOUNT, params); // 13 as per ins |
|
|
|
ins = "insert into blocks " |
|
"(height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward,confirmed," |
|
"diffacc,diffinv,shareacc,shareinv,elapsed," |
|
"statsconfirmed" |
|
HISTORYDATECONTROL ") select " |
|
"height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward,confirmed," |
|
"$3,$4,$5,$6,$7,$8," |
|
"$9,$10,$11,$12,$13 from blocks where " |
|
"blockhash=$1 and expirydate=$2"; |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
goto unparam; |
|
} |
|
|
|
res = PQexec(conn, "Commit", CKPQ_WRITE); |
|
|
|
ok = true; |
|
unparam: |
|
PQclear(res); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
if (conned) |
|
PQfinish(conn); |
|
|
|
K_WLOCK(blocks_free); |
|
if (!ok) |
|
k_add_head(blocks_free, b_item); |
|
else { |
|
if (update_old) { |
|
blocks_root = remove_from_ktree(blocks_root, old_b_item, cmp_blocks, ctx); |
|
copy_tv(&(oldblocks->expirydate), cd); |
|
blocks_root = add_to_ktree(blocks_root, old_b_item, cmp_blocks); |
|
} |
|
blocks_root = add_to_ktree(blocks_root, b_item, cmp_blocks); |
|
k_add_head(blocks_store, b_item); |
|
} |
|
K_WUNLOCK(blocks_free); |
|
|
|
return ok; |
|
} |
|
|
|
static bool blocks_add(PGconn *conn, char *height, char *blockhash, |
|
char *confirmed, char *workinfoid, char *username, |
|
char *workername, char *clientid, char *enonce1, |
|
char *nonce2, char *nonce, char *reward, |
|
char *by, char *code, char *inet, tv_t *cd, |
|
bool igndup, char *id, K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res = NULL; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM *b_item, *u_item, *old_b_item; |
|
char cd_buf[DATE_BUFSIZ]; |
|
char hash_dsp[16+1]; |
|
BLOCKS *row, *oldblocks; |
|
USERS *users; |
|
char *upd, *ins; |
|
char *params[17 + HISTORYDATECOUNT]; |
|
bool ok = false, update_old = false; |
|
int par = 0; |
|
char want = '?'; |
|
int n; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(blocks_free); |
|
b_item = k_unlink_head(blocks_free); |
|
K_WUNLOCK(blocks_free); |
|
|
|
DATA_BLOCKS(row, b_item); |
|
|
|
TXT_TO_INT("height", height, row->height); |
|
STRNCPY(row->blockhash, blockhash); |
|
|
|
dsp_hash(blockhash, hash_dsp, sizeof(hash_dsp)); |
|
|
|
K_RLOCK(blocks_free); |
|
old_b_item = find_blocks(row->height, blockhash); |
|
K_RUNLOCK(blocks_free); |
|
DATA_BLOCKS_NULL(oldblocks, old_b_item); |
|
|
|
switch (confirmed[0]) { |
|
case BLOCKS_NEW: |
|
// None should exist - so must be a duplicate |
|
if (old_b_item) { |
|
K_WLOCK(blocks_free); |
|
k_add_head(blocks_free, b_item); |
|
K_WUNLOCK(blocks_free); |
|
if (!igndup) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s(): Duplicate (%s) blocks ignored, Status: " |
|
"%s, Block: %s/...%s/%s", |
|
__func__, |
|
blocks_confirmed(oldblocks->confirmed), |
|
blocks_confirmed(confirmed), |
|
height, hash_dsp, cd_buf); |
|
} |
|
return true; |
|
} |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) |
|
row->userid = KANO; |
|
else { |
|
DATA_USERS(users, u_item); |
|
row->userid = users->userid; |
|
} |
|
|
|
STRNCPY(row->confirmed, confirmed); |
|
TXT_TO_BIGINT("workinfoid", workinfoid, row->workinfoid); |
|
STRNCPY(row->workername, workername); |
|
TXT_TO_INT("clientid", clientid, row->clientid); |
|
STRNCPY(row->enonce1, enonce1); |
|
STRNCPY(row->nonce2, nonce2); |
|
STRNCPY(row->nonce, nonce); |
|
TXT_TO_BIGINT("reward", reward, row->reward); |
|
// Specify them |
|
row->diffacc = 0; |
|
row->diffinv = 0; |
|
row->shareacc = 0; |
|
row->shareinv = 0; |
|
row->elapsed = 0; |
|
STRNCPY(row->statsconfirmed, BLOCKS_STATSPENDING_STR); |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
par = 0; |
|
params[par++] = int_to_buf(row->height, NULL, 0); |
|
params[par++] = str_to_buf(row->blockhash, NULL, 0); |
|
params[par++] = bigint_to_buf(row->workinfoid, NULL, 0); |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = int_to_buf(row->clientid, NULL, 0); |
|
params[par++] = str_to_buf(row->enonce1, NULL, 0); |
|
params[par++] = str_to_buf(row->nonce2, NULL, 0); |
|
params[par++] = str_to_buf(row->nonce, NULL, 0); |
|
params[par++] = bigint_to_buf(row->reward, NULL, 0); |
|
params[par++] = str_to_buf(row->confirmed, NULL, 0); |
|
params[par++] = double_to_buf(row->diffacc, NULL, 0); |
|
params[par++] = double_to_buf(row->diffinv, NULL, 0); |
|
params[par++] = double_to_buf(row->shareacc, NULL, 0); |
|
params[par++] = double_to_buf(row->shareinv, NULL, 0); |
|
params[par++] = bigint_to_buf(row->elapsed, NULL, 0); |
|
params[par++] = str_to_buf(row->statsconfirmed, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into blocks " |
|
"(height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward,confirmed," |
|
"diffacc,diffinv,shareacc,shareinv,elapsed," |
|
"statsconfirmed" |
|
HISTORYDATECONTROL ") values (" PQPARAM22 ")"; |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
break; |
|
case BLOCKS_ORPHAN: |
|
case BLOCKS_42: |
|
// These shouldn't be possible until startup completes |
|
if (!startup_complete) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s(): Status: %s invalid during startup. " |
|
"Ignored: Block: %s/...%s/%s", |
|
__func__, |
|
blocks_confirmed(confirmed), |
|
height, hash_dsp, cd_buf); |
|
goto flail; |
|
} |
|
want = BLOCKS_CONFIRM; |
|
case BLOCKS_CONFIRM: |
|
if (!old_b_item) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s(): Can't %s a non-existent Block: %s/...%s/%s", |
|
__func__, blocks_confirmed(confirmed), |
|
height, hash_dsp, cd_buf); |
|
goto flail; |
|
} |
|
if (confirmed[0] == BLOCKS_CONFIRM) |
|
want = BLOCKS_NEW; |
|
if (oldblocks->confirmed[0] != want) { |
|
// No mismatch messages during startup |
|
if (startup_complete) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s(): New Status: %s requires Status: %c. " |
|
"Ignored: Status: %s, Block: %s/...%s/%s", |
|
__func__, |
|
blocks_confirmed(confirmed), want, |
|
blocks_confirmed(oldblocks->confirmed), |
|
height, hash_dsp, cd_buf); |
|
} |
|
goto flail; |
|
} |
|
|
|
upd = "update blocks set expirydate=$1 where blockhash=$2 and expirydate=$3"; |
|
par = 0; |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = str_to_buf(row->blockhash, NULL, 0); |
|
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); |
|
PARCHKVAL(par, 3, params); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
// New is mostly a copy of the old |
|
memcpy(row, oldblocks, sizeof(*row)); |
|
STRNCPY(row->confirmed, confirmed); |
|
if (confirmed[0] == BLOCKS_CONFIRM) { |
|
row->diffacc = pool.diffacc; |
|
row->diffinv = pool.diffinv; |
|
row->shareacc = pool.shareacc; |
|
row->shareinv = pool.shareinv; |
|
} |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
res = PQexec(conn, "Begin", CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Begin", rescode, conn); |
|
goto unparam; |
|
} |
|
PQclear(res); |
|
|
|
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Update", rescode, conn); |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
goto unparam; |
|
} |
|
|
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
par = 0; |
|
params[par++] = str_to_buf(row->blockhash, NULL, 0); |
|
params[par++] = tv_to_buf(cd, NULL, 0); |
|
params[par++] = str_to_buf(row->confirmed, NULL, 0); |
|
|
|
if (confirmed[0] == BLOCKS_CONFIRM) { |
|
params[par++] = double_to_buf(row->diffacc, NULL, 0); |
|
params[par++] = double_to_buf(row->diffinv, NULL, 0); |
|
params[par++] = double_to_buf(row->shareacc, NULL, 0); |
|
params[par++] = double_to_buf(row->shareinv, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHKVAL(par, 7 + HISTORYDATECOUNT, params); // 12 as per ins |
|
|
|
ins = "insert into blocks " |
|
"(height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward,confirmed," |
|
"diffacc,diffinv,shareacc,shareinv,elapsed," |
|
"statsconfirmed" |
|
HISTORYDATECONTROL ") select " |
|
"height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward," |
|
"$3,$4,$5,$6,$7,elapsed,statsconfirmed," |
|
"$8,$9,$10,$11,$12 from blocks where " |
|
"blockhash=$1 and expirydate=$2"; |
|
} else { |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHKVAL(par, 3 + HISTORYDATECOUNT, params); // 8 as per ins |
|
|
|
ins = "insert into blocks " |
|
"(height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward,confirmed," |
|
"diffacc,diffinv,shareacc,shareinv,elapsed," |
|
"statsconfirmed" |
|
HISTORYDATECONTROL ") select " |
|
"height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward," |
|
"$3,diffacc,diffinv,shareacc,shareinv,elapsed," |
|
"statsconfirmed," |
|
"$4,$5,$6,$7,$8 from blocks where " |
|
"blockhash=$1 and expirydate=$2"; |
|
} |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
PQclear(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
goto unparam; |
|
} |
|
|
|
update_old = true; |
|
|
|
res = PQexec(conn, "Commit", CKPQ_WRITE); |
|
break; |
|
default: |
|
LOGERR("%s(): %s.failed.invalid confirm='%s'", |
|
__func__, id, confirmed); |
|
goto flail; |
|
} |
|
|
|
ok = true; |
|
unparam: |
|
PQclear(res); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
flail: |
|
if (conned) |
|
PQfinish(conn); |
|
|
|
K_WLOCK(blocks_free); |
|
if (!ok) |
|
k_add_head(blocks_free, b_item); |
|
else { |
|
if (update_old) { |
|
blocks_root = remove_from_ktree(blocks_root, old_b_item, cmp_blocks, ctx); |
|
copy_tv(&(oldblocks->expirydate), cd); |
|
blocks_root = add_to_ktree(blocks_root, old_b_item, cmp_blocks); |
|
} |
|
blocks_root = add_to_ktree(blocks_root, b_item, cmp_blocks); |
|
k_add_head(blocks_store, b_item); |
|
} |
|
K_WUNLOCK(blocks_free); |
|
|
|
if (ok) { |
|
char pct[16] = "?"; |
|
char est[16] = ""; |
|
K_ITEM *w_item; |
|
char tmp[256]; |
|
bool blk; |
|
|
|
switch (confirmed[0]) { |
|
case BLOCKS_NEW: |
|
blk = true; |
|
tv_to_buf(&(row->createdate), cd_buf, sizeof(cd_buf)); |
|
snprintf(tmp, sizeof(tmp), " UTC:%s", cd_buf); |
|
break; |
|
case BLOCKS_CONFIRM: |
|
blk = true; |
|
w_item = find_workinfo(row->workinfoid); |
|
if (w_item) { |
|
char wdiffbin[TXT_SML+1]; |
|
double wdiff; |
|
WORKINFO *workinfo; |
|
DATA_WORKINFO(workinfo, w_item); |
|
hex2bin(wdiffbin, workinfo->bits, 4); |
|
wdiff = diff_from_nbits(wdiffbin); |
|
if (wdiff > 0.0) { |
|
snprintf(pct, sizeof(pct), "%.2f", |
|
100.0 * pool.diffacc / wdiff); |
|
} |
|
} |
|
if (pool.diffacc >= 1000.0) { |
|
suffix_string(pool.diffacc, est, sizeof(est)-1, 0); |
|
strcat(est, " "); |
|
} |
|
tv_to_buf(&(row->createdate), cd_buf, sizeof(cd_buf)); |
|
snprintf(tmp, sizeof(tmp), |
|
" Reward: %f, Worker: %s, ShareEst: %.1f %s%s%% UTC:%s", |
|
BTC_TO_D(row->reward), |
|
row->workername, |
|
pool.diffacc, est, pct, cd_buf); |
|
if (pool.workinfoid < row->workinfoid) { |
|
pool.workinfoid = row->workinfoid; |
|
zero_on_new_block(); |
|
} |
|
break; |
|
case BLOCKS_ORPHAN: |
|
case BLOCKS_42: |
|
default: |
|
blk = false; |
|
tmp[0] = '\0'; |
|
break; |
|
} |
|
|
|
LOGWARNING("%s(): %sStatus: %s, Block: %s/...%s%s", |
|
__func__, blk ? "BLOCK! " : "", |
|
blocks_confirmed(confirmed), |
|
height, hash_dsp, tmp); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
static bool blocks_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
BLOCKS *row; |
|
char *field; |
|
char *sel; |
|
int fields = 17; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
sel = "select " |
|
"height,blockhash,workinfoid,userid,workername," |
|
"clientid,enonce1,nonce2,nonce,reward,confirmed," |
|
"diffacc,diffinv,shareacc,shareinv,elapsed,statsconfirmed" |
|
HISTORYDATECONTROL |
|
" from blocks"; |
|
res = PQexec(conn, sel, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != (fields + HISTORYDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + HISTORYDATECOUNT, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(blocks_free); |
|
DATA_BLOCKS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "height", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("height", field, row->height); |
|
|
|
PQ_GET_FLD(res, i, "blockhash", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("blockhash", field, row->blockhash); |
|
|
|
PQ_GET_FLD(res, i, "workinfoid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("workinfoid", field, row->workinfoid); |
|
|
|
PQ_GET_FLD(res, i, "userid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("userid", field, row->userid); |
|
|
|
PQ_GET_FLD(res, i, "workername", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("workername", field, row->workername); |
|
|
|
PQ_GET_FLD(res, i, "clientid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("clientid", field, row->clientid); |
|
|
|
PQ_GET_FLD(res, i, "enonce1", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("enonce1", field, row->enonce1); |
|
|
|
PQ_GET_FLD(res, i, "nonce2", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("nonce2", field, row->nonce2); |
|
|
|
PQ_GET_FLD(res, i, "nonce", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("nonce", field, row->nonce); |
|
|
|
PQ_GET_FLD(res, i, "reward", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("reward", field, row->reward); |
|
|
|
PQ_GET_FLD(res, i, "confirmed", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("confirmed", field, row->confirmed); |
|
|
|
PQ_GET_FLD(res, i, "diffacc", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffacc", field, row->diffacc); |
|
|
|
PQ_GET_FLD(res, i, "diffinv", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("diffinv", field, row->diffinv); |
|
|
|
PQ_GET_FLD(res, i, "shareacc", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("shareacc", field, row->shareacc); |
|
|
|
PQ_GET_FLD(res, i, "shareinv", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("shareinv", field, row->shareinv); |
|
|
|
PQ_GET_FLD(res, i, "elapsed", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("elapsed", field, row->elapsed); |
|
|
|
PQ_GET_FLD(res, i, "statsconfirmed", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("statsconfirmed", field, row->statsconfirmed); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
blocks_root = add_to_ktree(blocks_root, item, cmp_blocks); |
|
k_add_head(blocks_store, item); |
|
|
|
if (tv_newer(&(dbstatus.newest_createdate_blocks), &(row->createdate))) |
|
copy_tv(&(dbstatus.newest_createdate_blocks), &(row->createdate)); |
|
|
|
if (pool.workinfoid < row->workinfoid) |
|
pool.workinfoid = row->workinfoid; |
|
} |
|
if (!ok) |
|
k_add_head(blocks_free, item); |
|
|
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d blocks records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
void blocks_reload() |
|
{ |
|
PGconn *conn = dbconnect(); |
|
|
|
blocks_root = free_ktree(blocks_root, NULL); |
|
k_list_transfer_to_head(blocks_store, blocks_free); |
|
|
|
blocks_fill(conn); |
|
|
|
PQfinish(conn); |
|
} |
|
|
|
/* order by height asc,userid asc,expirydate asc |
|
* i.e. only one payout amount per block per user */ |
|
static cmp_t cmp_miningpayouts(K_ITEM *a, K_ITEM *b) |
|
{ |
|
MININGPAYOUTS *ma, *mb; |
|
DATA_MININGPAYOUTS(ma, a); |
|
DATA_MININGPAYOUTS(mb, b); |
|
cmp_t c = CMP_INT(ma->height, mb->height); |
|
if (c == 0) { |
|
c = CMP_BIGINT(ma->userid, mb->userid); |
|
if (c == 0) |
|
c = CMP_TV(ma->expirydate, mb->expirydate); |
|
} |
|
return c; |
|
} |
|
|
|
__maybe_unused static bool miningpayouts_add(PGconn *conn, char *username, char *height, |
|
char *blockhash, char *amount, char *by, |
|
char *code, char *inet, tv_t *cd, K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
K_ITEM *m_item, *u_item; |
|
bool ok = false; |
|
int n; |
|
MININGPAYOUTS *row; |
|
USERS *users; |
|
char *ins; |
|
char *params[5 + HISTORYDATECOUNT]; |
|
int par; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(miningpayouts_free); |
|
m_item = k_unlink_head(miningpayouts_free); |
|
K_WUNLOCK(miningpayouts_free); |
|
|
|
DATA_MININGPAYOUTS(row, m_item); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
row->miningpayoutid = nextid(conn, "miningpayoutid", (int64_t)1, cd, by, code, inet); |
|
if (row->miningpayoutid == 0) |
|
goto unitem; |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) |
|
goto unitem; |
|
DATA_USERS(users, u_item); |
|
|
|
row->userid = users->userid; |
|
TXT_TO_INT("height", height, row->height); |
|
STRNCPY(row->blockhash, blockhash); |
|
TXT_TO_BIGINT("amount", amount, row->amount); |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(row->miningpayoutid, NULL, 0); |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = int_to_buf(row->height, NULL, 0); |
|
params[par++] = str_to_buf(row->blockhash, NULL, 0); |
|
params[par++] = bigint_to_buf(row->amount, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into miningpayouts " |
|
"(miningpayoutid,userid,height,blockhash,amount" |
|
HISTORYDATECONTROL ") values (" PQPARAM10 ")"; |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
ok = true; |
|
|
|
unparam: |
|
PQclear(res); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
unitem: |
|
if (conned) |
|
PQfinish(conn); |
|
K_WLOCK(miningpayouts_free); |
|
if (!ok) |
|
k_add_head(miningpayouts_free, m_item); |
|
else { |
|
miningpayouts_root = add_to_ktree(miningpayouts_root, m_item, cmp_miningpayouts); |
|
k_add_head(miningpayouts_store, m_item); |
|
} |
|
K_WUNLOCK(miningpayouts_free); |
|
|
|
return ok; |
|
} |
|
|
|
static bool miningpayouts_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
MININGPAYOUTS *row; |
|
char *params[1]; |
|
int par; |
|
char *field; |
|
char *sel; |
|
int fields = 5; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
sel = "select " |
|
"miningpayoutid,userid,height,blockhash,amount" |
|
HISTORYDATECONTROL |
|
" from miningpayouts where expirydate=$1"; |
|
par = 0; |
|
params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); |
|
PARCHK(par, params); |
|
res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != (fields + HISTORYDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + HISTORYDATECOUNT, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
K_WLOCK(miningpayouts_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(miningpayouts_free); |
|
DATA_MININGPAYOUTS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "miningpayoutid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("miningpayoutid", field, row->miningpayoutid); |
|
|
|
PQ_GET_FLD(res, i, "userid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("userid", field, row->userid); |
|
|
|
PQ_GET_FLD(res, i, "height", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("height", field, row->height); |
|
|
|
PQ_GET_FLD(res, i, "blockhash", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("blockhash", field, row->blockhash); |
|
|
|
PQ_GET_FLD(res, i, "amount", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("amount", field, row->amount); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
miningpayouts_root = add_to_ktree(miningpayouts_root, item, cmp_miningpayouts); |
|
k_add_head(miningpayouts_store, item); |
|
|
|
tick(); |
|
} |
|
if (!ok) |
|
k_add_head(miningpayouts_free, item); |
|
|
|
K_WUNLOCK(miningpayouts_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d miningpayout records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
void miningpayouts_reload() |
|
{ |
|
PGconn *conn = dbconnect(); |
|
|
|
K_WLOCK(miningpayouts_free); |
|
miningpayouts_root = free_ktree(miningpayouts_root, NULL); |
|
k_list_transfer_to_head(miningpayouts_store, miningpayouts_free); |
|
K_WUNLOCK(miningpayouts_free); |
|
|
|
miningpayouts_fill(conn); |
|
|
|
PQfinish(conn); |
|
} |
|
|
|
// order by userid asc,createdate asc,authid asc,expirydate desc |
|
static cmp_t cmp_auths(K_ITEM *a, K_ITEM *b) |
|
{ |
|
AUTHS *aa, *ab; |
|
DATA_AUTHS(aa, a); |
|
DATA_AUTHS(ab, b); |
|
cmp_t c = CMP_BIGINT(aa->userid, ab->userid); |
|
if (c == 0) { |
|
c = CMP_TV(aa->createdate, ab->createdate); |
|
if (c == 0) { |
|
c = CMP_BIGINT(aa->authid, ab->authid); |
|
if (c == 0) |
|
c = CMP_TV(ab->expirydate, aa->expirydate); |
|
} |
|
} |
|
return c; |
|
} |
|
|
|
static char *auths_add(PGconn *conn, char *poolinstance, char *username, |
|
char *workername, char *clientid, char *enonce1, |
|
char *useragent, char *preauth, char *by, char *code, |
|
char *inet, tv_t *cd, bool igndup, K_TREE *trf_root, |
|
bool addressuser) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM *a_item, *u_item; |
|
char cd_buf[DATE_BUFSIZ]; |
|
int n; |
|
USERS *users; |
|
AUTHS *row; |
|
char *ins; |
|
char *secuserid = NULL; |
|
char *params[8 + HISTORYDATECOUNT]; |
|
int par; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(auths_free); |
|
a_item = k_unlink_head(auths_free); |
|
K_WUNLOCK(auths_free); |
|
|
|
DATA_AUTHS(row, a_item); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
if (addressuser) { |
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
u_item = users_add(conn, username, EMPTY, EMPTY, |
|
by, code, inet, cd, trf_root); |
|
} |
|
if (!u_item) |
|
goto unitem; |
|
} |
|
DATA_USERS(users, u_item); |
|
|
|
STRNCPY(row->poolinstance, poolinstance); |
|
row->userid = users->userid; |
|
// since update=false, a dup will be ok and do nothing when igndup=true |
|
new_worker(conn, false, row->userid, workername, DIFFICULTYDEFAULT_DEF_STR, |
|
IDLENOTIFICATIONENABLED_DEF, IDLENOTIFICATIONTIME_DEF_STR, |
|
by, code, inet, cd, trf_root); |
|
STRNCPY(row->workername, workername); |
|
TXT_TO_INT("clientid", clientid, row->clientid); |
|
STRNCPY(row->enonce1, enonce1); |
|
STRNCPY(row->useragent, useragent); |
|
STRNCPY(row->preauth, preauth); |
|
|
|
HISTORYDATEINIT(row, cd, by, code, inet); |
|
HISTORYDATETRANSFER(trf_root, row); |
|
|
|
K_WLOCK(auths_free); |
|
if (find_in_ktree(auths_root, a_item, cmp_auths, ctx)) { |
|
k_add_head(auths_free, a_item); |
|
K_WUNLOCK(auths_free); |
|
|
|
if (conned) |
|
PQfinish(conn); |
|
|
|
if (!igndup) { |
|
tv_to_buf(cd, cd_buf, sizeof(cd_buf)); |
|
LOGERR("%s(): Duplicate auths ignored %s/%s/%s", |
|
__func__, poolinstance, workername, cd_buf); |
|
} |
|
|
|
return users->secondaryuserid; |
|
} |
|
K_WUNLOCK(auths_free); |
|
|
|
// Update even if DB fails |
|
workerstatus_update(row, NULL, NULL); |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
row->authid = nextid(conn, "authid", (int64_t)1, cd, by, code, inet); |
|
if (row->authid == 0) |
|
goto unitem; |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(row->authid, NULL, 0); |
|
params[par++] = str_to_buf(row->poolinstance, NULL, 0); |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = int_to_buf(row->clientid, NULL, 0); |
|
params[par++] = str_to_buf(row->enonce1, NULL, 0); |
|
params[par++] = str_to_buf(row->useragent, NULL, 0); |
|
params[par++] = str_to_buf(row->preauth, NULL, 0); |
|
HISTORYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into auths " |
|
"(authid,poolinstance,userid,workername,clientid,enonce1,useragent,preauth" |
|
HISTORYDATECONTROL ") values (" PQPARAM13 ")"; |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
secuserid = users->secondaryuserid; |
|
|
|
unparam: |
|
PQclear(res); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
unitem: |
|
if (conned) |
|
PQfinish(conn); |
|
K_WLOCK(auths_free); |
|
if (!secuserid) |
|
k_add_head(auths_free, a_item); |
|
else { |
|
auths_root = add_to_ktree(auths_root, a_item, cmp_auths); |
|
k_add_head(auths_store, a_item); |
|
} |
|
K_WUNLOCK(auths_free); |
|
|
|
return secuserid; |
|
} |
|
|
|
static bool auths_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
AUTHS *row; |
|
char *params[1]; |
|
int par; |
|
char *field; |
|
char *sel; |
|
int fields = 7; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
// TODO: add/update a (single) fake auth every ~10min or 10min after the last one? |
|
sel = "select " |
|
"authid,userid,workername,clientid,enonce1,useragent,preauth" |
|
HISTORYDATECONTROL |
|
" from auths where expirydate=$1"; |
|
par = 0; |
|
params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); |
|
PARCHK(par, params); |
|
res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != (fields + HISTORYDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + HISTORYDATECOUNT, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
K_WLOCK(auths_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(auths_free); |
|
DATA_AUTHS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
PQ_GET_FLD(res, i, "authid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("authid", field, row->authid); |
|
|
|
PQ_GET_FLD(res, i, "userid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("userid", field, row->userid); |
|
|
|
PQ_GET_FLD(res, i, "workername", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("workername", field, row->workername); |
|
|
|
PQ_GET_FLD(res, i, "clientid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("clientid", field, row->clientid); |
|
|
|
PQ_GET_FLD(res, i, "enonce1", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("enonce1", field, row->enonce1); |
|
|
|
PQ_GET_FLD(res, i, "useragent", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("useragent", field, row->useragent); |
|
|
|
PQ_GET_FLD(res, i, "preauth", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("preauth", field, row->preauth); |
|
|
|
HISTORYDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
auths_root = add_to_ktree(auths_root, item, cmp_auths); |
|
k_add_head(auths_store, item); |
|
workerstatus_update(row, NULL, NULL); |
|
|
|
if (tv_newer(&(dbstatus.newest_createdate_auths), &(row->createdate))) |
|
copy_tv(&(dbstatus.newest_createdate_auths), &(row->createdate)); |
|
|
|
tick(); |
|
} |
|
if (!ok) |
|
k_add_head(auths_free, item); |
|
|
|
K_WUNLOCK(auths_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d auth records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
void auths_reload() |
|
{ |
|
PGconn *conn = dbconnect(); |
|
|
|
K_WLOCK(auths_free); |
|
auths_root = free_ktree(auths_root, NULL); |
|
k_list_transfer_to_head(auths_store, auths_free); |
|
K_WUNLOCK(auths_free); |
|
|
|
auths_fill(conn); |
|
|
|
PQfinish(conn); |
|
} |
|
|
|
// order by poolinstance asc,createdate asc |
|
static cmp_t cmp_poolstats(K_ITEM *a, K_ITEM *b) |
|
{ |
|
POOLSTATS *pa, *pb; |
|
DATA_POOLSTATS(pa, a); |
|
DATA_POOLSTATS(pb, b); |
|
cmp_t c = CMP_STR(pa->poolinstance, pb->poolinstance); |
|
if (c == 0) |
|
c = CMP_TV(pa->createdate, pb->createdate); |
|
return c; |
|
} |
|
|
|
static bool poolstats_add(PGconn *conn, bool store, char *poolinstance, |
|
char *elapsed, char *users, char *workers, |
|
char *hashrate, char *hashrate5m, |
|
char *hashrate1hr, char *hashrate24hr, |
|
char *by, char *code, char *inet, tv_t *cd, |
|
bool igndup, K_TREE *trf_root) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM *p_item; |
|
int n; |
|
POOLSTATS *row; |
|
char *ins; |
|
char *params[8 + SIMPLEDATECOUNT]; |
|
int par; |
|
bool ok = false; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(poolstats_free); |
|
p_item = k_unlink_head(poolstats_free); |
|
K_WUNLOCK(poolstats_free); |
|
|
|
DATA_POOLSTATS(row, p_item); |
|
|
|
row->stored = false; |
|
|
|
STRNCPY(row->poolinstance, poolinstance); |
|
TXT_TO_BIGINT("elapsed", elapsed, row->elapsed); |
|
TXT_TO_INT("users", users, row->users); |
|
TXT_TO_INT("workers", workers, row->workers); |
|
TXT_TO_DOUBLE("hashrate", hashrate, row->hashrate); |
|
TXT_TO_DOUBLE("hashrate5m", hashrate5m, row->hashrate5m); |
|
TXT_TO_DOUBLE("hashrate1hr", hashrate1hr, row->hashrate1hr); |
|
TXT_TO_DOUBLE("hashrate24hr", hashrate24hr, row->hashrate24hr); |
|
|
|
SIMPLEDATEINIT(row, cd, by, code, inet); |
|
SIMPLEDATETRANSFER(trf_root, row); |
|
|
|
if (igndup && find_in_ktree(poolstats_root, p_item, cmp_poolstats, ctx)) { |
|
K_WLOCK(poolstats_free); |
|
k_add_head(poolstats_free, p_item); |
|
K_WUNLOCK(poolstats_free); |
|
return true; |
|
} |
|
|
|
par = 0; |
|
if (store) { |
|
params[par++] = str_to_buf(row->poolinstance, NULL, 0); |
|
params[par++] = bigint_to_buf(row->elapsed, NULL, 0); |
|
params[par++] = int_to_buf(row->users, NULL, 0); |
|
params[par++] = int_to_buf(row->workers, NULL, 0); |
|
params[par++] = bigint_to_buf(row->hashrate, NULL, 0); |
|
params[par++] = bigint_to_buf(row->hashrate5m, NULL, 0); |
|
params[par++] = bigint_to_buf(row->hashrate1hr, NULL, 0); |
|
params[par++] = bigint_to_buf(row->hashrate24hr, NULL, 0); |
|
SIMPLEDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into poolstats " |
|
"(poolinstance,elapsed,users,workers,hashrate," |
|
"hashrate5m,hashrate1hr,hashrate24hr" |
|
SIMPLEDATECONTROL ") values (" PQPARAM12 ")"; |
|
|
|
if (!conn) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
row->stored = true; |
|
} |
|
|
|
ok = true; |
|
unparam: |
|
if (store) { |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
} |
|
|
|
K_WLOCK(poolstats_free); |
|
if (!ok) |
|
k_add_head(poolstats_free, p_item); |
|
else { |
|
poolstats_root = add_to_ktree(poolstats_root, p_item, cmp_poolstats); |
|
k_add_head(poolstats_store, p_item); |
|
} |
|
K_WUNLOCK(poolstats_free); |
|
|
|
return ok; |
|
} |
|
|
|
// TODO: data selection - only require ? |
|
static bool poolstats_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
POOLSTATS *row; |
|
char *field; |
|
char *sel; |
|
int fields = 8; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
sel = "select " |
|
"poolinstance,elapsed,users,workers,hashrate,hashrate5m," |
|
"hashrate1hr,hashrate24hr" |
|
SIMPLEDATECONTROL |
|
" from poolstats"; |
|
res = PQexec(conn, sel, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != (fields + SIMPLEDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + SIMPLEDATECOUNT, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
K_WLOCK(poolstats_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(poolstats_free); |
|
DATA_POOLSTATS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
row->stored = true; |
|
|
|
PQ_GET_FLD(res, i, "poolinstance", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("poolinstance", field, row->poolinstance); |
|
|
|
PQ_GET_FLD(res, i, "elapsed", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("elapsed", field, row->elapsed); |
|
|
|
PQ_GET_FLD(res, i, "users", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("users", field, row->users); |
|
|
|
PQ_GET_FLD(res, i, "workers", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("workers", field, row->workers); |
|
|
|
PQ_GET_FLD(res, i, "hashrate", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate", field, row->hashrate); |
|
|
|
PQ_GET_FLD(res, i, "hashrate5m", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate5m", field, row->hashrate5m); |
|
|
|
PQ_GET_FLD(res, i, "hashrate1hr", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate1hr", field, row->hashrate1hr); |
|
|
|
PQ_GET_FLD(res, i, "hashrate24hr", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate24hr", field, row->hashrate24hr); |
|
|
|
SIMPLEDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
poolstats_root = add_to_ktree(poolstats_root, item, cmp_poolstats); |
|
k_add_head(poolstats_store, item); |
|
|
|
if (tv_newer(&(dbstatus.newest_createdate_poolstats), &(row->createdate))) |
|
copy_tv(&(dbstatus.newest_createdate_poolstats), &(row->createdate)); |
|
|
|
tick(); |
|
} |
|
if (!ok) |
|
k_add_head(poolstats_free, item); |
|
|
|
K_WUNLOCK(poolstats_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d poolstats records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
void poolstats_reload() |
|
{ |
|
PGconn *conn = dbconnect(); |
|
|
|
K_WLOCK(poolstats_free); |
|
poolstats_root = free_ktree(poolstats_root, NULL); |
|
k_list_transfer_to_head(poolstats_store, poolstats_free); |
|
K_WUNLOCK(poolstats_free); |
|
|
|
poolstats_fill(conn); |
|
|
|
PQfinish(conn); |
|
} |
|
|
|
static void dsp_userstats(K_ITEM *item, FILE *stream) |
|
{ |
|
char statsdate_buf[DATE_BUFSIZ], createdate_buf[DATE_BUFSIZ]; |
|
USERSTATS *u = NULL; |
|
|
|
if (!item) |
|
fprintf(stream, "%s() called with (null) item\n", __func__); |
|
else { |
|
DATA_USERSTATS(u, item); |
|
tv_to_buf(&(u->statsdate), statsdate_buf, sizeof(statsdate_buf)); |
|
tv_to_buf(&(u->createdate), createdate_buf, sizeof(createdate_buf)); |
|
fprintf(stream, " pi='%s' uid=%"PRId64" w='%s' e=%"PRId64" Hs=%f " |
|
"Hs5m=%f Hs1hr=%f Hs24hr=%f sl=%s sc=%d sd=%s cd=%s\n", |
|
u->poolinstance, u->userid, u->workername, |
|
u->elapsed, u->hashrate, u->hashrate5m, |
|
u->hashrate1hr, u->hashrate24hr, u->summarylevel, |
|
u->summarycount, statsdate_buf, createdate_buf); |
|
} |
|
} |
|
|
|
/* order by userid asc,statsdate asc,poolinstance asc,workername asc |
|
as per required for userstats homepage summarisation */ |
|
static cmp_t cmp_userstats(K_ITEM *a, K_ITEM *b) |
|
{ |
|
USERSTATS *ua, *ub; |
|
DATA_USERSTATS(ua, a); |
|
DATA_USERSTATS(ub, b); |
|
cmp_t c = CMP_BIGINT(ua->userid, ub->userid); |
|
if (c == 0) { |
|
c = CMP_TV(ua->statsdate, ub->statsdate); |
|
if (c == 0) { |
|
c = CMP_STR(ua->poolinstance, ub->poolinstance); |
|
if (c == 0) |
|
c = CMP_STR(ua->workername, ub->workername); |
|
} |
|
} |
|
return c; |
|
} |
|
|
|
/* order by userid asc,workername asc |
|
temporary tree for summing userstats when sending user homepage info */ |
|
static cmp_t cmp_userstats_workername(K_ITEM *a, K_ITEM *b) |
|
{ |
|
USERSTATS *ua, *ub; |
|
DATA_USERSTATS(ua, a); |
|
DATA_USERSTATS(ub, b); |
|
cmp_t c = CMP_BIGINT(ua->userid, ub->userid); |
|
if (c == 0) |
|
c = CMP_STR(ua->workername, ub->workername); |
|
return c; |
|
} |
|
|
|
/* order by statsdate,userid asc,statsdate asc,workername asc,poolinstance asc |
|
as per required for DB summarisation */ |
|
static cmp_t cmp_userstats_statsdate(K_ITEM *a, K_ITEM *b) |
|
{ |
|
USERSTATS *ua, *ub; |
|
DATA_USERSTATS(ua, a); |
|
DATA_USERSTATS(ub, b); |
|
cmp_t c = CMP_TV(ua->statsdate, ub->statsdate); |
|
if (c == 0) { |
|
c = CMP_BIGINT(ua->userid, ub->userid); |
|
if (c == 0) { |
|
c = CMP_STR(ua->workername, ub->workername); |
|
if (c == 0) |
|
c = CMP_STR(ua->poolinstance, ub->poolinstance); |
|
} |
|
} |
|
return c; |
|
} |
|
|
|
/* order by userid asc,workername asc,statsdate asc,poolinstance asc |
|
built during data load to update workerstatus at the end of the load |
|
and used during reload to discard stats already in the DB */ |
|
static cmp_t cmp_userstats_workerstatus(K_ITEM *a, K_ITEM *b) |
|
{ |
|
USERSTATS *ua, *ub; |
|
DATA_USERSTATS(ua, a); |
|
DATA_USERSTATS(ub, b); |
|
cmp_t c = CMP_BIGINT(ua->userid, ub->userid); |
|
if (c == 0) { |
|
c = CMP_STR(ua->workername, ub->workername); |
|
if (c == 0) { |
|
c = CMP_TV(ua->statsdate, ub->statsdate); |
|
if (c == 0) |
|
c = CMP_STR(ua->poolinstance, ub->poolinstance); |
|
} |
|
} |
|
return c; |
|
} |
|
|
|
static bool userstats_add_db(PGconn *conn, USERSTATS *row) |
|
{ |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
char *ins; |
|
bool ok = false; |
|
char *params[10 + SIMPLEDATECOUNT]; |
|
int par; |
|
int n; |
|
|
|
LOGDEBUG("%s(): store", __func__); |
|
|
|
par = 0; |
|
params[par++] = bigint_to_buf(row->userid, NULL, 0); |
|
params[par++] = str_to_buf(row->workername, NULL, 0); |
|
params[par++] = bigint_to_buf(row->elapsed, NULL, 0); |
|
params[par++] = double_to_buf(row->hashrate, NULL, 0); |
|
params[par++] = double_to_buf(row->hashrate5m, NULL, 0); |
|
params[par++] = double_to_buf(row->hashrate1hr, NULL, 0); |
|
params[par++] = double_to_buf(row->hashrate24hr, NULL, 0); |
|
params[par++] = str_to_buf(row->summarylevel, NULL, 0); |
|
params[par++] = int_to_buf(row->summarycount, NULL, 0); |
|
params[par++] = tv_to_buf(&(row->statsdate), NULL, 0); |
|
SIMPLEDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into userstats " |
|
"(userid,workername,elapsed,hashrate,hashrate5m,hashrate1hr," |
|
"hashrate24hr,summarylevel,summarycount,statsdate" |
|
SIMPLEDATECONTROL ") values (" PQPARAM14 ")"; |
|
|
|
if (conn == NULL) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto unparam; |
|
} |
|
|
|
ok = true; |
|
unparam: |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
return ok; |
|
} |
|
|
|
static bool userstats_add(char *poolinstance, char *elapsed, char *username, |
|
char *workername, char *hashrate, char *hashrate5m, |
|
char *hashrate1hr, char *hashrate24hr, bool idle, |
|
bool eos, char *by, char *code, char *inet, tv_t *cd, |
|
K_TREE *trf_root) |
|
{ |
|
K_ITEM *us_item, *u_item, *us_match, *us_next, look; |
|
tv_t eosdate; |
|
USERSTATS *row, cmp, *match, *next; |
|
USERS *users; |
|
K_TREE_CTX ctx[1]; |
|
|
|
LOGDEBUG("%s(): add", __func__); |
|
|
|
K_WLOCK(userstats_free); |
|
us_item = k_unlink_head(userstats_free); |
|
K_WUNLOCK(userstats_free); |
|
|
|
DATA_USERSTATS(row, us_item); |
|
|
|
STRNCPY(row->poolinstance, poolinstance); |
|
TXT_TO_BIGINT("elapsed", elapsed, row->elapsed); |
|
K_RLOCK(users_free); |
|
u_item = find_users(username); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) |
|
return false; |
|
DATA_USERS(users, u_item); |
|
row->userid = users->userid; |
|
TXT_TO_STR("workername", workername, row->workername); |
|
TXT_TO_DOUBLE("hashrate", hashrate, row->hashrate); |
|
TXT_TO_DOUBLE("hashrate5m", hashrate5m, row->hashrate5m); |
|
TXT_TO_DOUBLE("hashrate1hr", hashrate1hr, row->hashrate1hr); |
|
TXT_TO_DOUBLE("hashrate24hr", hashrate24hr, row->hashrate24hr); |
|
row->idle = idle; |
|
row->summarylevel[0] = SUMMARY_NONE; |
|
row->summarylevel[1] = '\0'; |
|
row->summarycount = 1; |
|
SIMPLEDATEINIT(row, cd, by, code, inet); |
|
SIMPLEDATETRANSFER(trf_root, row); |
|
copy_tv(&(row->statsdate), &(row->createdate)); |
|
|
|
if (eos) { |
|
// Save it for end processing |
|
eosdate.tv_sec = row->createdate.tv_sec; |
|
eosdate.tv_usec = row->createdate.tv_usec; |
|
} |
|
|
|
// confirm_summaries() doesn't call this |
|
if (reloading) { |
|
memcpy(&cmp, row, sizeof(cmp)); |
|
INIT_USERSTATS(&look); |
|
look.data = (void *)(&cmp); |
|
// Just zero it to ensure the DB record is after it, not equal to it |
|
cmp.statsdate.tv_usec = 0; |
|
/* If there is a matching user+worker DB record summarising this row, |
|
* or a matching user+worker DB record next after this row, discard it */ |
|
us_match = find_after_in_ktree(userstats_workerstatus_root, &look, |
|
cmp_userstats_workerstatus, ctx); |
|
DATA_USERSTATS_NULL(match, us_match); |
|
if (us_match && |
|
match->userid == row->userid && |
|
strcmp(match->workername, row->workername) == 0 && |
|
match->summarylevel[0] != SUMMARY_NONE) { |
|
K_WLOCK(userstats_free); |
|
k_add_head(userstats_free, us_item); |
|
K_WUNLOCK(userstats_free); |
|
|
|
/* If this was an eos record and eos_store has data, |
|
* it means we need to process the eos_store */ |
|
if (eos && userstats_eos_store->count > 0) |
|
goto advancetogo; |
|
|
|
return true; |
|
} |
|
} |
|
|
|
workerstatus_update(NULL, NULL, row); |
|
|
|
/* group at full key: userid,createdate,poolinstance,workername |
|
i.e. ignore instance and group together down at workername */ |
|
us_match = userstats_eos_store->head; |
|
while (us_match && cmp_userstats(us_item, us_match) != 0.0) |
|
us_match = us_match->next; |
|
|
|
if (us_match) { |
|
DATA_USERSTATS(match, us_match); |
|
match->hashrate += row->hashrate; |
|
match->hashrate5m += row->hashrate5m; |
|
match->hashrate1hr += row->hashrate1hr; |
|
match->hashrate24hr += row->hashrate24hr; |
|
// Minimum elapsed of the data set |
|
if (match->elapsed > row->elapsed) |
|
match->elapsed = row->elapsed; |
|
// Unused |
|
K_WLOCK(userstats_free); |
|
k_add_head(userstats_free, us_item); |
|
K_WUNLOCK(userstats_free); |
|
} else { |
|
// New worker |
|
K_WLOCK(userstats_free); |
|
k_add_head(userstats_eos_store, us_item); |
|
K_WUNLOCK(userstats_free); |
|
} |
|
|
|
if (eos) { |
|
advancetogo: |
|
K_WLOCK(userstats_free); |
|
us_next = userstats_eos_store->head; |
|
while (us_next) { |
|
DATA_USERSTATS(next, us_next); |
|
if (tvdiff(&(next->createdate), &eosdate) != 0.0) { |
|
char date_buf[DATE_BUFSIZ]; |
|
LOGERR("userstats != eos '%s' discarded: %s/%"PRId64"/%s", |
|
tv_to_buf(&eosdate, date_buf, DATE_BUFSIZ), |
|
next->poolinstance, |
|
next->userid, |
|
next->workername); |
|
us_next = us_next->next; |
|
} else { |
|
us_match = us_next; |
|
us_next = us_match->next; |
|
k_unlink_item(userstats_eos_store, us_match); |
|
userstats_root = add_to_ktree(userstats_root, us_match, |
|
cmp_userstats); |
|
userstats_statsdate_root = add_to_ktree(userstats_statsdate_root, us_match, |
|
cmp_userstats_statsdate); |
|
if (!startup_complete) { |
|
userstats_workerstatus_root = add_to_ktree(userstats_workerstatus_root, us_match, |
|
cmp_userstats_workerstatus); |
|
} |
|
k_add_head(userstats_store, us_match); |
|
} |
|
} |
|
// Discard them |
|
if (userstats_eos_store->count > 0) |
|
k_list_transfer_to_head(userstats_eos_store, userstats_free); |
|
K_WUNLOCK(userstats_free); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static bool userstats_starttimeband(USERSTATS *row, tv_t *statsdate) |
|
{ |
|
char buf[DATE_BUFSIZ+1]; |
|
|
|
copy_tv(statsdate, &(row->statsdate)); |
|
// Start of this timeband |
|
switch (row->summarylevel[0]) { |
|
case SUMMARY_DB: |
|
statsdate->tv_sec -= statsdate->tv_sec % USERSTATS_DB_S; |
|
statsdate->tv_usec = 0; |
|
break; |
|
case SUMMARY_FULL: |
|
statsdate->tv_sec -= statsdate->tv_sec % USERSTATS_DB_DS; |
|
statsdate->tv_usec = 0; |
|
break; |
|
default: |
|
tv_to_buf(statsdate, buf, sizeof(buf)); |
|
// Bad userstats are not fatal |
|
LOGERR("Unknown userstats summarylevel 0x%02x '%c' " |
|
"userid %"PRId64" workername %s statsdate %s", |
|
row->summarylevel[0], row->summarylevel[0], |
|
row->userid, row->workername, buf); |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
// TODO: data selection - only require ? |
|
static bool userstats_fill(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
K_ITEM *item; |
|
int n, i; |
|
USERSTATS *row; |
|
tv_t statsdate; |
|
char *field; |
|
char *sel; |
|
int fields = 10; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
sel = "select " |
|
"userid,workername,elapsed,hashrate,hashrate5m,hashrate1hr," |
|
"hashrate24hr,summarylevel,summarycount,statsdate" |
|
SIMPLEDATECONTROL |
|
" from userstats"; |
|
res = PQexec(conn, sel, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Select", rescode, conn); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != (fields + SIMPLEDATECOUNT)) { |
|
LOGERR("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields + SIMPLEDATECOUNT, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
LOGDEBUG("%s(): tree build count %d", __func__, n); |
|
ok = true; |
|
K_WLOCK(userstats_free); |
|
for (i = 0; i < n; i++) { |
|
item = k_unlink_head(userstats_free); |
|
DATA_USERSTATS(row, item); |
|
|
|
if (everyone_die) { |
|
ok = false; |
|
break; |
|
} |
|
|
|
// Not a DB field |
|
row->poolinstance[0] = '\0'; |
|
|
|
PQ_GET_FLD(res, i, "userid", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("userid", field, row->userid); |
|
|
|
PQ_GET_FLD(res, i, "workername", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("workername", field, row->workername); |
|
|
|
PQ_GET_FLD(res, i, "elapsed", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_BIGINT("elapsed", field, row->elapsed); |
|
|
|
PQ_GET_FLD(res, i, "hashrate", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate", field, row->hashrate); |
|
|
|
PQ_GET_FLD(res, i, "hashrate5m", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate5m", field, row->hashrate5m); |
|
|
|
PQ_GET_FLD(res, i, "hashrate1hr", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate1hr", field, row->hashrate1hr); |
|
|
|
PQ_GET_FLD(res, i, "hashrate24hr", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_DOUBLE("hashrate24hr", field, row->hashrate24hr); |
|
|
|
PQ_GET_FLD(res, i, "summarylevel", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_STR("summarylevel", field, row->summarylevel); |
|
|
|
PQ_GET_FLD(res, i, "summarycount", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_INT("summarycount", field, row->summarycount); |
|
|
|
PQ_GET_FLD(res, i, "statsdate", field, ok); |
|
if (!ok) |
|
break; |
|
TXT_TO_TV("statsdate", field, row->statsdate); |
|
|
|
// From DB - 1hr means it must have been idle > 10m |
|
if (row->hashrate5m == 0.0 && row->hashrate1hr == 0.0) |
|
row->idle = true; |
|
else |
|
row->idle = false; |
|
|
|
SIMPLEDATEFLDS(res, i, row, ok); |
|
if (!ok) |
|
break; |
|
|
|
userstats_root = add_to_ktree(userstats_root, item, cmp_userstats); |
|
userstats_statsdate_root = add_to_ktree(userstats_statsdate_root, item, |
|
cmp_userstats_statsdate); |
|
userstats_workerstatus_root = add_to_ktree(userstats_workerstatus_root, item, |
|
cmp_userstats_workerstatus); |
|
k_add_head(userstats_store, item); |
|
|
|
workerstatus_update(NULL, NULL, row); |
|
if (userstats_starttimeband(row, &statsdate)) { |
|
if (tv_newer(&(dbstatus.newest_starttimeband_userstats), &statsdate)) |
|
copy_tv(&(dbstatus.newest_starttimeband_userstats), &statsdate); |
|
} |
|
|
|
tick(); |
|
} |
|
if (!ok) |
|
k_add_head(userstats_free, item); |
|
|
|
K_WUNLOCK(userstats_free); |
|
PQclear(res); |
|
|
|
if (ok) { |
|
LOGDEBUG("%s(): built", __func__); |
|
LOGWARNING("%s(): loaded %d userstats records", __func__, n); |
|
} |
|
|
|
return ok; |
|
} |
|
|
|
void userstats_reload() |
|
{ |
|
PGconn *conn = dbconnect(); |
|
|
|
K_WLOCK(userstats_free); |
|
userstats_root = free_ktree(userstats_root, NULL); |
|
userstats_statsdate_root = free_ktree(userstats_statsdate_root, NULL); |
|
k_list_transfer_to_head(userstats_store, userstats_free); |
|
K_WUNLOCK(userstats_free); |
|
|
|
userstats_fill(conn); |
|
|
|
PQfinish(conn); |
|
} |
|
|
|
static bool check_db_version(PGconn *conn) |
|
{ |
|
ExecStatusType rescode; |
|
PGresult *res; |
|
char *field; |
|
char *sel; |
|
int fields = 2; |
|
bool ok; |
|
int n; |
|
|
|
LOGDEBUG("%s(): select", __func__); |
|
|
|
sel = "select * from version;"; |
|
res = PQexec(conn, sel, CKPQ_READ); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGEMERG("Select", rescode, conn); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQnfields(res); |
|
if (n != fields) { |
|
LOGEMERG("%s(): Invalid field count - should be %d, but is %d", |
|
__func__, fields, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
n = PQntuples(res); |
|
if (n != 1) { |
|
LOGEMERG("%s(): Invalid record count - should be %d, but is %d", |
|
__func__, 1, n); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
ok = true; |
|
PQ_GET_FLD(res, 0, "vlock", field, ok); |
|
if (!ok) { |
|
LOGEMERG("%s(): Missing field vlock", __func__); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
if (strcmp(field, DB_VLOCK)) { |
|
LOGEMERG("%s(): incorrect vlock '%s' - should be '%s'", |
|
__func__, field, DB_VLOCK); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
ok = true; |
|
PQ_GET_FLD(res, 0, "version", field, ok); |
|
if (!ok) { |
|
LOGEMERG("%s(): Missing field version", __func__); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
if (strcmp(field, DB_VERSION)) { |
|
LOGEMERG("%s(): incorrect version '%s' - should be '%s'", |
|
__func__, field, DB_VERSION); |
|
PQclear(res); |
|
return false; |
|
} |
|
|
|
PQclear(res); |
|
|
|
LOGWARNING("%s(): DB version (%s) correct (CKDB V%s)", |
|
__func__, DB_VERSION, CKDB_VERSION); |
|
|
|
return true; |
|
} |
|
|
|
/* Load tables required to support auths,adduser,chkpass and newid |
|
* N.B. idcontrol is DB internal so is always ready |
|
*/ |
|
static bool getdata1() |
|
{ |
|
PGconn *conn = dbconnect(); |
|
bool ok = true; |
|
|
|
if (!(ok = check_db_version(conn))) |
|
goto matane; |
|
if (!(ok = users_fill(conn))) |
|
goto matane; |
|
if (!(ok = workers_fill(conn))) |
|
goto matane; |
|
if (!confirm_sharesummary) |
|
ok = auths_fill(conn); |
|
|
|
matane: |
|
|
|
PQfinish(conn); |
|
return ok; |
|
} |
|
|
|
static bool getdata2() |
|
{ |
|
PGconn *conn = dbconnect(); |
|
bool ok = true; |
|
|
|
if (!(ok = blocks_fill(conn)) || everyone_die) |
|
goto sukamudai; |
|
if (!confirm_sharesummary) { |
|
if (!(ok = paymentaddresses_fill(conn)) || everyone_die) |
|
goto sukamudai; |
|
if (!(ok = payments_fill(conn)) || everyone_die) |
|
goto sukamudai; |
|
} |
|
if (!(ok = workinfo_fill(conn)) || everyone_die) |
|
goto sukamudai; |
|
if (!(ok = shares_fill()) || everyone_die) |
|
goto sukamudai; |
|
if (!(ok = shareerrors_fill()) || everyone_die) |
|
goto sukamudai; |
|
if (!(ok = sharesummary_fill(conn)) || everyone_die) |
|
goto sukamudai; |
|
if (!confirm_sharesummary) { |
|
if (!(ok = useratts_fill(conn)) || everyone_die) |
|
goto sukamudai; |
|
if (!(ok = poolstats_fill(conn)) || everyone_die) |
|
goto sukamudai; |
|
ok = userstats_fill(conn); |
|
} |
|
|
|
sukamudai: |
|
|
|
PQfinish(conn); |
|
return ok; |
|
} |
|
|
|
static bool reload_from(tv_t *start); |
|
|
|
static bool reload() |
|
{ |
|
char buf[DATE_BUFSIZ+1]; |
|
char *filename; |
|
tv_t start; |
|
char *reason; |
|
FILE *fp; |
|
|
|
tv_to_buf(&(dbstatus.oldest_sharesummary_firstshare_n), buf, sizeof(buf)); |
|
LOGWARNING("%s(): %s oldest DB incomplete sharesummary", __func__, buf); |
|
tv_to_buf(&(dbstatus.newest_sharesummary_firstshare_ay), buf, sizeof(buf)); |
|
LOGWARNING("%s(): %s newest DB complete sharesummary", __func__, buf); |
|
tv_to_buf(&(dbstatus.newest_createdate_workinfo), buf, sizeof(buf)); |
|
LOGWARNING("%s(): %s newest DB workinfo", __func__, buf); |
|
tv_to_buf(&(dbstatus.newest_createdate_auths), buf, sizeof(buf)); |
|
LOGWARNING("%s(): %s newest DB auths", __func__, buf); |
|
tv_to_buf(&(dbstatus.newest_createdate_poolstats), buf, sizeof(buf)); |
|
LOGWARNING("%s(): %s newest DB poolstats", __func__, buf); |
|
tv_to_buf(&(dbstatus.newest_starttimeband_userstats), buf, sizeof(buf)); |
|
LOGWARNING("%s(): %s newest DB userstats start timeband", __func__, buf); |
|
tv_to_buf(&(dbstatus.newest_createdate_blocks), buf, sizeof(buf)); |
|
LOGWARNING("%s(): %s newest DB blocks (ignored)", __func__, buf); |
|
|
|
if (dbstatus.oldest_sharesummary_firstshare_n.tv_sec) |
|
copy_tv(&(dbstatus.sharesummary_firstshare), &(dbstatus.oldest_sharesummary_firstshare_n)); |
|
else |
|
copy_tv(&(dbstatus.sharesummary_firstshare), &(dbstatus.newest_sharesummary_firstshare_ay)); |
|
|
|
copy_tv(&start, &(dbstatus.sharesummary_firstshare)); |
|
reason = "sharesummary"; |
|
if (!tv_newer(&start, &(dbstatus.newest_createdate_workinfo))) { |
|
copy_tv(&start, &(dbstatus.newest_createdate_workinfo)); |
|
reason = "workinfo"; |
|
} |
|
if (!tv_newer(&start, &(dbstatus.newest_createdate_auths))) { |
|
copy_tv(&start, &(dbstatus.newest_createdate_auths)); |
|
reason = "auths"; |
|
} |
|
if (!tv_newer(&start, &(dbstatus.newest_createdate_poolstats))) { |
|
copy_tv(&start, &(dbstatus.newest_createdate_poolstats)); |
|
reason = "poolstats"; |
|
} |
|
if (!tv_newer(&start, &(dbstatus.newest_starttimeband_userstats))) { |
|
copy_tv(&start, &(dbstatus.newest_starttimeband_userstats)); |
|
reason = "userstats"; |
|
} |
|
|
|
tv_to_buf(&start, buf, sizeof(buf)); |
|
LOGWARNING("%s() restart timestamp %s for %s", __func__, buf, reason); |
|
|
|
if (start.tv_sec < DATE_BEGIN) { |
|
start.tv_sec = DATE_BEGIN; |
|
start.tv_usec = 0L; |
|
filename = rotating_filename(restorefrom, start.tv_sec); |
|
fp = fopen(filename, "re"); |
|
if (fp) |
|
fclose(fp); |
|
else { |
|
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; |
|
int fd = open(filename, O_CREAT|O_RDONLY, mode); |
|
if (fd == -1) { |
|
int ern = errno; |
|
quithere(1, "Couldn't create '%s' (%d) %s", |
|
filename, ern, strerror(ern)); |
|
} |
|
close(fd); |
|
} |
|
free(filename); |
|
} |
|
return reload_from(&start); |
|
} |
|
|
|
/* TODO: |
|
static PGconn *dbquit(PGconn *conn) |
|
{ |
|
if (conn != NULL) |
|
PQfinish(conn); |
|
return NULL; |
|
} |
|
*/ |
|
|
|
/* Open the file in path, check if there is a pid in there that still exists |
|
* and if not, write the pid into that file. */ |
|
static bool write_pid(ckpool_t *ckp, const char *path, pid_t pid) |
|
{ |
|
struct stat statbuf; |
|
FILE *fp; |
|
int ret; |
|
|
|
if (!stat(path, &statbuf)) { |
|
int oldpid; |
|
|
|
LOGWARNING("File %s exists", path); |
|
fp = fopen(path, "re"); |
|
if (!fp) { |
|
LOGEMERG("Failed to open file %s", path); |
|
return false; |
|
} |
|
ret = fscanf(fp, "%d", &oldpid); |
|
fclose(fp); |
|
if (ret == 1 && !(kill(oldpid, 0))) { |
|
if (!ckp->killold) { |
|
LOGEMERG("Process %s pid %d still exists, start ckpool with -k if you wish to kill it", |
|
path, oldpid); |
|
return false; |
|
} |
|
if (kill(oldpid, 9)) { |
|
LOGEMERG("Unable to kill old process %s pid %d", path, oldpid); |
|
return false; |
|
} |
|
LOGWARNING("Killing off old process %s pid %d", path, oldpid); |
|
} |
|
} |
|
fp = fopen(path, "we"); |
|
if (!fp) { |
|
LOGERR("Failed to open file %s", path); |
|
return false; |
|
} |
|
fprintf(fp, "%d", pid); |
|
fclose(fp); |
|
|
|
return true; |
|
} |
|
|
|
static void create_process_unixsock(proc_instance_t *pi) |
|
{ |
|
unixsock_t *us = &pi->us; |
|
|
|
us->path = strdup(pi->ckp->socket_dir); |
|
realloc_strcat(&us->path, pi->sockname); |
|
LOGDEBUG("Opening %s", us->path); |
|
us->sockd = open_unix_server(us->path); |
|
if (unlikely(us->sockd < 0)) |
|
quit(1, "Failed to open %s socket", pi->sockname); |
|
} |
|
|
|
static void write_namepid(proc_instance_t *pi) |
|
{ |
|
char s[256]; |
|
|
|
pi->pid = getpid(); |
|
sprintf(s, "%s%s.pid", pi->ckp->socket_dir, pi->processname); |
|
if (!write_pid(pi->ckp, s, pi->pid)) |
|
quit(1, "Failed to write %s pid %d", pi->processname, pi->pid); |
|
} |
|
|
|
static void rm_namepid(proc_instance_t *pi) |
|
{ |
|
char s[256]; |
|
|
|
sprintf(s, "%s%s.pid", pi->ckp->socket_dir, pi->processname); |
|
unlink(s); |
|
|
|
} |
|
|
|
static void clean_up(ckpool_t *ckp) |
|
{ |
|
rm_namepid(&ckp->main); |
|
dealloc(ckp->socket_dir); |
|
fclose(ckp->logfp); |
|
} |
|
|
|
static void alloc_storage() |
|
{ |
|
workqueue_free = k_new_list("WorkQueue", sizeof(WORKQUEUE), |
|
ALLOC_WORKQUEUE, LIMIT_WORKQUEUE, true); |
|
workqueue_store = k_new_store(workqueue_free); |
|
|
|
transfer_free = k_new_list(Transfer, sizeof(TRANSFER), |
|
ALLOC_TRANSFER, LIMIT_TRANSFER, true); |
|
transfer_free->dsp_func = dsp_transfer; |
|
|
|
users_free = k_new_list("Users", sizeof(USERS), |
|
ALLOC_USERS, LIMIT_USERS, true); |
|
users_store = k_new_store(users_free); |
|
users_root = new_ktree(); |
|
userid_root = new_ktree(); |
|
|
|
useratts_free = k_new_list("Useratts", sizeof(USERATTS), |
|
ALLOC_USERATTS, LIMIT_USERATTS, true); |
|
useratts_store = k_new_store(useratts_free); |
|
useratts_root = new_ktree(); |
|
|
|
workers_free = k_new_list("Workers", sizeof(WORKERS), |
|
ALLOC_WORKERS, LIMIT_WORKERS, true); |
|
workers_store = k_new_store(workers_free); |
|
workers_root = new_ktree(); |
|
|
|
paymentaddresses_free = k_new_list("PaymentAddresses", |
|
sizeof(PAYMENTADDRESSES), |
|
ALLOC_PAYMENTADDRESSES, |
|
LIMIT_PAYMENTADDRESSES, true); |
|
paymentaddresses_store = k_new_store(paymentaddresses_free); |
|
paymentaddresses_root = new_ktree(); |
|
|
|
payments_free = k_new_list("Payments", sizeof(PAYMENTS), |
|
ALLOC_PAYMENTS, LIMIT_PAYMENTS, true); |
|
payments_store = k_new_store(payments_free); |
|
payments_root = new_ktree(); |
|
|
|
idcontrol_free = k_new_list("IDControl", sizeof(IDCONTROL), |
|
ALLOC_IDCONTROL, LIMIT_IDCONTROL, true); |
|
idcontrol_store = k_new_store(idcontrol_free); |
|
|
|
workinfo_free = k_new_list("WorkInfo", sizeof(WORKINFO), |
|
ALLOC_WORKINFO, LIMIT_WORKINFO, true); |
|
workinfo_store = k_new_store(workinfo_free); |
|
workinfo_root = new_ktree(); |
|
if (!confirm_sharesummary) |
|
workinfo_height_root = new_ktree(); |
|
|
|
shares_free = k_new_list("Shares", sizeof(SHARES), |
|
ALLOC_SHARES, LIMIT_SHARES, true); |
|
shares_store = k_new_store(shares_free); |
|
shares_root = new_ktree(); |
|
|
|
shareerrors_free = k_new_list("ShareErrors", sizeof(SHAREERRORS), |
|
ALLOC_SHAREERRORS, LIMIT_SHAREERRORS, true); |
|
shareerrors_store = k_new_store(shareerrors_free); |
|
shareerrors_root = new_ktree(); |
|
|
|
sharesummary_free = k_new_list("ShareSummary", sizeof(SHARESUMMARY), |
|
ALLOC_SHARESUMMARY, LIMIT_SHARESUMMARY, true); |
|
sharesummary_store = k_new_store(sharesummary_free); |
|
sharesummary_root = new_ktree(); |
|
sharesummary_workinfoid_root = new_ktree(); |
|
sharesummary_free->dsp_func = dsp_sharesummary; |
|
|
|
blocks_free = k_new_list("Blocks", sizeof(BLOCKS), |
|
ALLOC_BLOCKS, LIMIT_BLOCKS, true); |
|
blocks_store = k_new_store(blocks_free); |
|
blocks_root = new_ktree(); |
|
blocks_free->dsp_func = dsp_blocks; |
|
|
|
miningpayouts_free = k_new_list("MiningPayouts", sizeof(MININGPAYOUTS), |
|
ALLOC_MININGPAYOUTS, LIMIT_MININGPAYOUTS, true); |
|
miningpayouts_store = k_new_store(miningpayouts_free); |
|
miningpayouts_root = new_ktree(); |
|
|
|
auths_free = k_new_list("Auths", sizeof(AUTHS), |
|
ALLOC_AUTHS, LIMIT_AUTHS, true); |
|
auths_store = k_new_store(auths_free); |
|
auths_root = new_ktree(); |
|
|
|
poolstats_free = k_new_list("PoolStats", sizeof(POOLSTATS), |
|
ALLOC_POOLSTATS, LIMIT_POOLSTATS, true); |
|
poolstats_store = k_new_store(poolstats_free); |
|
poolstats_root = new_ktree(); |
|
|
|
userstats_free = k_new_list("UserStats", sizeof(USERSTATS), |
|
ALLOC_USERSTATS, LIMIT_USERSTATS, true); |
|
userstats_store = k_new_store(userstats_free); |
|
userstats_eos_store = k_new_store(userstats_free); |
|
userstats_summ = k_new_store(userstats_free); |
|
userstats_root = new_ktree(); |
|
userstats_statsdate_root = new_ktree(); |
|
userstats_workerstatus_root = new_ktree(); |
|
userstats_free->dsp_func = dsp_userstats; |
|
|
|
workerstatus_free = k_new_list("WorkerStatus", sizeof(WORKERSTATUS), |
|
ALLOC_WORKERSTATUS, LIMIT_WORKERSTATUS, true); |
|
workerstatus_store = k_new_store(workerstatus_free); |
|
workerstatus_root = new_ktree(); |
|
} |
|
|
|
static bool setup_data() |
|
{ |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM look, *found; |
|
WORKINFO wi, *wic, *wif; |
|
|
|
cklock_init(&fpm_lock); |
|
cksem_init(&socketer_sem); |
|
mutex_init(&wq_waitlock); |
|
cond_init(&wq_waitcond); |
|
|
|
alloc_storage(); |
|
|
|
if (!getdata1() || everyone_die) |
|
return false; |
|
|
|
db_auths_complete = true; |
|
cksem_post(&socketer_sem); |
|
|
|
if (!getdata2() || everyone_die) |
|
return false; |
|
|
|
db_load_complete = true; |
|
|
|
if (!reload() || everyone_die) |
|
return false; |
|
|
|
set_block_share_counters(); |
|
|
|
if (everyone_die) |
|
return false; |
|
|
|
workerstatus_ready(); |
|
|
|
userstats_workerstatus_root = free_ktree(userstats_workerstatus_root, NULL); |
|
|
|
workinfo_current = last_in_ktree(workinfo_height_root, ctx); |
|
if (workinfo_current) { |
|
DATA_WORKINFO(wic, workinfo_current); |
|
STRNCPY(wi.coinbase1, wic->coinbase1); |
|
wi.createdate.tv_sec = 0L; |
|
wi.createdate.tv_usec = 0L; |
|
INIT_WORKINFO(&look); |
|
look.data = (void *)(&wi); |
|
// Find the first workinfo for this height |
|
found = find_after_in_ktree(workinfo_height_root, &look, cmp_workinfo_height, ctx); |
|
if (found) { |
|
DATA_WORKINFO(wif, found); |
|
copy_tv(&last_bc, &(wif->createdate)); |
|
} |
|
// No longer needed |
|
workinfo_height_root = free_ktree(workinfo_height_root, NULL); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
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; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
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); |
|
|
|
u_item = users_add(conn, transfer_data(i_username), |
|
transfer_data(i_emailaddress), |
|
transfer_data(i_passwordhash), |
|
by, code, inet, now, trf_root); |
|
|
|
if (!u_item) { |
|
LOGERR("%s() %s.failed.DBE", __func__, id); |
|
return strdup("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, *u_item; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
bool ok = false; |
|
char *oldhash; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz); |
|
if (!i_username) |
|
return strdup(reply); |
|
|
|
i_oldhash = optional_name(trf_root, "oldhash", 64, (char *)hashpatt); |
|
if (i_oldhash) |
|
oldhash = transfer_data(i_oldhash); |
|
else |
|
oldhash = EMPTY; |
|
|
|
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) { |
|
ok = users_pass_email(NULL, u_item, |
|
oldhash, |
|
transfer_data(i_newhash), |
|
NULL, |
|
by, code, inet, now, trf_root); |
|
} |
|
|
|
if (!ok) { |
|
LOGERR("%s.failed.%s", id, transfer_data(i_username)); |
|
return strdup("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, *u_item; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
USERS *users; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", 3, (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); |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
|
|
if (!u_item) |
|
ok = false; |
|
else { |
|
DATA_USERS(users, u_item); |
|
ok = check_hash(users, transfer_data(i_passwordhash)); |
|
} |
|
|
|
if (!ok) { |
|
LOGERR("%s.failed.%s", id, transfer_data(i_username)); |
|
return strdup("failed."); |
|
} |
|
LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); |
|
return strdup("ok."); |
|
} |
|
|
|
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_address, *i_email, *u_item, *pa_item; |
|
char *email, *address; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
char tmp[1024]; |
|
PAYMENTADDRESSES *paymentaddresses; |
|
USERS *users; |
|
char *reason = NULL; |
|
char *answer = NULL; |
|
size_t len, off; |
|
bool ok; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", 3, (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); |
|
i_passwordhash = optional_name(trf_root, "passwordhash", |
|
64, (char *)hashpatt); |
|
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); |
|
|
|
K_RLOCK(paymentaddresses_free); |
|
pa_item = find_paymentaddresses(users->userid); |
|
K_RUNLOCK(paymentaddresses_free); |
|
|
|
if (pa_item) { |
|
DATA_PAYMENTADDRESSES(paymentaddresses, pa_item); |
|
snprintf(tmp, sizeof(tmp), "addr=%s", |
|
paymentaddresses->payaddress); |
|
APPEND_REALLOC(answer, off, len, tmp); |
|
} else { |
|
snprintf(tmp, sizeof(tmp), "addr="); |
|
APPEND_REALLOC(answer, off, len, tmp); |
|
} |
|
} else { |
|
if (!check_hash(users, transfer_data(i_passwordhash))) { |
|
reason = "Incorrect password"; |
|
goto struckout; |
|
} |
|
i_email = optional_name(trf_root, "email", 1, (char *)mailpatt); |
|
if (i_email) |
|
email = transfer_data(i_email); |
|
else |
|
email = NULL; |
|
i_address = optional_name(trf_root, "address", 1, NULL); |
|
if (i_address) |
|
address = transfer_data(i_address); |
|
else |
|
address = NULL; |
|
if ((email == NULL || *email == '\0') && |
|
(address == NULL || *address == '\0')) { |
|
reason = "Missing/Invalid value"; |
|
goto struckout; |
|
} |
|
|
|
// if (address && *address) |
|
// TODO: validate it |
|
|
|
if (email && *email) { |
|
ok = users_pass_email(conn, u_item, NULL, |
|
NULL, email, |
|
by, code, inet, |
|
now, trf_root); |
|
if (!ok) { |
|
reason = "email error"; |
|
goto struckout; |
|
} |
|
} |
|
|
|
if (address && *address) { |
|
ok = paymentaddresses_set(conn, users->userid, |
|
address, by, |
|
code, inet, |
|
now, trf_root); |
|
if (!ok) { |
|
reason = "address error"; |
|
goto struckout; |
|
} |
|
} |
|
answer = strdup("updated"); |
|
} |
|
} |
|
|
|
struckout: |
|
if (reason) { |
|
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); |
|
|
|
i_elapsed = optional_name(trf_root, "elapsed", 1, NULL); |
|
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); |
|
ps = find_before_in_ktree(poolstats_root, &look, cmp_poolstats, ctx); |
|
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) { |
|
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 |
|
if (reloading) { |
|
if (tv_equal(cd, &(dbstatus.newest_createdate_blocks))) |
|
igndup = true; |
|
else if (tv_newer(cd, &(dbstatus.newest_createdate_blocks))) |
|
return NULL; |
|
} |
|
|
|
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); |
|
|
|
i_elapsed = optional_name(trf_root, "elapsed", 1, NULL); |
|
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); |
|
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); |
|
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); |
|
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_blocklist(__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_TREE_CTX ctx[1]; |
|
K_ITEM *b_item, *w_item; |
|
BLOCKS *blocks; |
|
char reply[1024] = ""; |
|
char tmp[1024]; |
|
char *buf; |
|
size_t len, off; |
|
int32_t height = -1; |
|
tv_t first_cd = {0,0}; |
|
int rows; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
rows = 0; |
|
K_RLOCK(blocks_free); |
|
b_item = last_in_ktree(blocks_root, ctx); |
|
while (b_item && rows < 42) { |
|
DATA_BLOCKS(blocks, b_item); |
|
if (height != blocks->height) { |
|
height = blocks->height; |
|
copy_tv(&first_cd, &(blocks->createdate)); |
|
} |
|
if (CURRENT(&(blocks->expirydate))) { |
|
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); |
|
|
|
str_to_buf(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); |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"firstcreatedate:%d=%ld%c", rows, |
|
first_cd.tv_sec, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"createdate:%d=%ld%c", rows, |
|
blocks->createdate.tv_sec, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"status:%d=%s%c", rows, |
|
blocks_confirmed(blocks->confirmed), 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); |
|
|
|
w_item = find_workinfo(blocks->workinfoid); |
|
if (w_item) { |
|
char wdiffbin[TXT_SML+1]; |
|
double wdiff; |
|
WORKINFO *workinfo; |
|
DATA_WORKINFO(workinfo, w_item); |
|
hex2bin(wdiffbin, workinfo->bits, 4); |
|
wdiff = diff_from_nbits(wdiffbin); |
|
snprintf(tmp, sizeof(tmp), |
|
"netdiff:%d=%.1f%c", |
|
rows, wdiff, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} else { |
|
snprintf(tmp, sizeof(tmp), |
|
"netdiff:%d=?%c", rows, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
|
|
rows++; |
|
} |
|
b_item = prev_in_ktree(ctx); |
|
} |
|
K_RUNLOCK(blocks_free); |
|
snprintf(tmp, sizeof(tmp), |
|
"rows=%d%cflds=%s%c", |
|
rows, FLDSEP, |
|
"height,blockhash,nonce,reward,workername,firstcreatedate," |
|
"createdate,status,diffacc,diffinv,shareacc,shareinv,elapsed," |
|
"netdiff", FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Blocks", FLDSEP, ""); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
LOGDEBUG("%s.ok.%d_blocks", id, rows); |
|
return buf; |
|
} |
|
|
|
static char *cmd_blockstatus(__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_height, *i_blockhash, *i_action; |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
K_ITEM *b_item; |
|
BLOCKS *blocks; |
|
int32_t height; |
|
char *action; |
|
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)); |
|
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); |
|
|
|
if (strcasecmp(action, "orphan") == 0) { |
|
switch (blocks->confirmed[0]) { |
|
case BLOCKS_NEW: |
|
case BLOCKS_CONFIRM: |
|
ok = blocks_add(conn, transfer_data(i_height), |
|
blocks->blockhash, |
|
BLOCKS_ORPHAN_STR, |
|
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, *look; |
|
IDCONTROL *row; |
|
char *params[2 + MODIFYDATECOUNT]; |
|
int par; |
|
bool ok = false; |
|
ExecStatusType rescode; |
|
bool conned = false; |
|
PGresult *res; |
|
char *ins; |
|
int n; |
|
|
|
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); |
|
|
|
K_WLOCK(idcontrol_free); |
|
look = k_unlink_head(idcontrol_free); |
|
K_WUNLOCK(idcontrol_free); |
|
|
|
DATA_IDCONTROL(row, look); |
|
|
|
STRNCPY(row->idname, transfer_data(i_idname)); |
|
TXT_TO_BIGINT("idvalue", transfer_data(i_idvalue), row->lastid); |
|
MODIFYDATEINIT(row, now, by, code, inet); |
|
|
|
par = 0; |
|
params[par++] = str_to_buf(row->idname, NULL, 0); |
|
params[par++] = bigint_to_buf(row->lastid, NULL, 0); |
|
MODIFYDATEPARAMS(params, par, row); |
|
PARCHK(par, params); |
|
|
|
ins = "insert into idcontrol " |
|
"(idname,lastid" MODIFYDATECONTROL ") values (" PQPARAM10 ")"; |
|
|
|
if (!conn) { |
|
conn = dbconnect(); |
|
conned = true; |
|
} |
|
|
|
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); |
|
rescode = PQresultStatus(res); |
|
if (!PGOK(rescode)) { |
|
PGLOGERR("Insert", rescode, conn); |
|
goto foil; |
|
} |
|
|
|
ok = true; |
|
foil: |
|
PQclear(res); |
|
if (conned) |
|
PQfinish(conn); |
|
for (n = 0; n < par; n++) |
|
free(params[n]); |
|
|
|
K_WLOCK(idcontrol_free); |
|
k_add_head(idcontrol_free, look); |
|
K_WUNLOCK(idcontrol_free); |
|
|
|
if (!ok) { |
|
LOGERR("%s() %s.failed.DBE", __func__, id); |
|
return strdup("failed.DBE"); |
|
} |
|
LOGDEBUG("%s.ok.added %s %"PRId64, id, transfer_data(i_idname), row->lastid); |
|
snprintf(reply, siz, "ok.added %s %"PRId64, |
|
transfer_data(i_idname), row->lastid); |
|
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, look, *u_item, *p_item; |
|
K_TREE_CTX ctx[1]; |
|
PAYMENTS lookpayments, *payments; |
|
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 = require_name(trf_root, "username", 3, (char *)userpatt, 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); |
|
|
|
lookpayments.userid = users->userid; |
|
lookpayments.paydate.tv_sec = 0; |
|
lookpayments.paydate.tv_usec = 0; |
|
INIT_PAYMENTS(&look); |
|
look.data = (void *)(&lookpayments); |
|
p_item = find_after_in_ktree(payments_root, &look, cmp_payments, ctx); |
|
DATA_PAYMENTS_NULL(payments, p_item); |
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
rows = 0; |
|
while (p_item && payments->userid == users->userid) { |
|
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); |
|
|
|
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); |
|
|
|
rows++; |
|
p_item = next_in_ktree(ctx); |
|
DATA_PAYMENTS_NULL(payments, p_item); |
|
} |
|
snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", |
|
rows, FLDSEP, |
|
"paydate,payaddress,amount", 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_workers(__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_stats, w_look, *u_item, *w_item, us_look, *us_item, *ws_item; |
|
K_TREE_CTX w_ctx[1], us_ctx[1]; |
|
WORKERS lookworkers, *workers; |
|
WORKERSTATUS *workerstatus; |
|
USERSTATS lookuserstats, *userstats; |
|
USERS *users; |
|
char reply[1024] = ""; |
|
char tmp[1024]; |
|
size_t siz = sizeof(reply); |
|
char *buf; |
|
size_t len, off; |
|
bool stats; |
|
int rows; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", 3, (char *)userpatt, 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); |
|
if (!i_stats) |
|
stats = false; |
|
else |
|
stats = (strcasecmp(transfer_data(i_stats), TRUE_STR) == 0); |
|
|
|
INIT_WORKERS(&w_look); |
|
INIT_USERSTATS(&us_look); |
|
|
|
lookworkers.userid = users->userid; |
|
lookworkers.workername[0] = '\0'; |
|
lookworkers.expirydate.tv_sec = 0; |
|
lookworkers.expirydate.tv_usec = 0; |
|
w_look.data = (void *)(&lookworkers); |
|
w_item = find_after_in_ktree(workers_root, &w_look, cmp_workers, w_ctx); |
|
DATA_WORKERS_NULL(workers, w_item); |
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
rows = 0; |
|
while (w_item && workers->userid == users->userid) { |
|
if (CURRENT(&(workers->expirydate))) { |
|
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) { |
|
K_TREE *userstats_workername_root = new_ktree(); |
|
K_TREE_CTX usw_ctx[1]; |
|
double w_hashrate5m, w_hashrate1hr; |
|
int64_t w_elapsed; |
|
tv_t w_lastshare; |
|
double w_lastdiff, w_diffacc, w_diffinv; |
|
double w_shareacc, w_shareinv; |
|
|
|
w_hashrate5m = w_hashrate1hr = 0.0; |
|
w_elapsed = -1; |
|
w_lastshare.tv_sec = 0; |
|
w_lastdiff = w_diffacc = w_diffinv = |
|
w_shareacc = w_shareinv = 0; |
|
|
|
ws_item = find_workerstatus(users->userid, workers->workername, |
|
__FILE__, __func__, __LINE__); |
|
if (ws_item) { |
|
DATA_WORKERSTATUS(workerstatus, ws_item); |
|
w_lastshare.tv_sec = workerstatus->last_share.tv_sec; |
|
w_lastdiff = workerstatus->last_diff; |
|
w_diffacc = workerstatus->diffacc; |
|
w_diffinv = workerstatus->diffinv; |
|
w_shareacc = workerstatus->shareacc; |
|
w_shareinv = workerstatus->shareinv; |
|
} |
|
|
|
// find last stored userid record |
|
lookuserstats.userid = users->userid; |
|
lookuserstats.statsdate.tv_sec = date_eot.tv_sec; |
|
lookuserstats.statsdate.tv_usec = date_eot.tv_usec; |
|
// find/cmp doesn't get to here |
|
lookuserstats.poolinstance[0] = '\0'; |
|
lookuserstats.workername[0] = '\0'; |
|
us_look.data = (void *)(&lookuserstats); |
|
K_RLOCK(userstats_free); |
|
us_item = find_before_in_ktree(userstats_root, &us_look, cmp_userstats, us_ctx); |
|
DATA_USERSTATS_NULL(userstats, us_item); |
|
while (us_item && userstats->userid == lookuserstats.userid) { |
|
if (strcmp(userstats->workername, workers->workername) == 0) { |
|
if (tvdiff(now, &(userstats->statsdate)) < USERSTATS_PER_S) { |
|
// TODO: add together the latest per pool instance (this is the latest per worker) |
|
if (!find_in_ktree(userstats_workername_root, us_item, cmp_userstats_workername, usw_ctx)) { |
|
w_hashrate5m += userstats->hashrate5m; |
|
w_hashrate1hr += userstats->hashrate1hr; |
|
if (w_elapsed == -1 || w_elapsed > userstats->elapsed) |
|
w_elapsed = userstats->elapsed; |
|
|
|
userstats_workername_root = add_to_ktree(userstats_workername_root, |
|
us_item, |
|
cmp_userstats_workername); |
|
} |
|
} else |
|
break; |
|
|
|
} |
|
us_item = prev_in_ktree(us_ctx); |
|
DATA_USERSTATS_NULL(userstats, us_item); |
|
} |
|
|
|
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); |
|
|
|
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); |
|
|
|
double_to_buf(w_lastdiff, 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_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); |
|
|
|
userstats_workername_root = free_ktree(userstats_workername_root, NULL); |
|
K_RUNLOCK(userstats_free); |
|
} |
|
|
|
rows++; |
|
} |
|
w_item = next_in_ktree(w_ctx); |
|
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_elapsed,w_lastshare," |
|
"w_lastdiff,w_diffacc,w_diffinv,w_shareacc,w_shareinv" : "", |
|
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_TREE *userstats_workername_root = new_ktree(); |
|
K_ITEM *us_item, *usw_item, *tmp_item, *u_item; |
|
K_TREE_CTX us_ctx[1], usw_ctx[1]; |
|
USERSTATS *userstats, *userstats_w; |
|
USERS *users; |
|
char reply[1024] = ""; |
|
char tmp[1024]; |
|
char *buf; |
|
size_t len, off; |
|
int rows; |
|
int64_t userid = -1; |
|
double u_hashrate5m = 0.0; |
|
double u_hashrate1hr = 0.0; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
// TODO: this really should just get the last value of each client_id (within the time limit) |
|
|
|
// Find last records for each user/worker in ALLUSERS_LIMIT_S |
|
// TODO: include pool_instance |
|
K_WLOCK(userstats_free); |
|
us_item = last_in_ktree(userstats_statsdate_root, us_ctx); |
|
DATA_USERSTATS_NULL(userstats, us_item); |
|
while (us_item && tvdiff(now, &(userstats->statsdate)) < ALLUSERS_LIMIT_S) { |
|
usw_item = find_in_ktree(userstats_workername_root, us_item, cmp_userstats_workername, usw_ctx); |
|
if (!usw_item) { |
|
usw_item = k_unlink_head(userstats_free); |
|
DATA_USERSTATS(userstats_w, usw_item); |
|
|
|
userstats_w->userid = userstats->userid; |
|
strcpy(userstats_w->workername, userstats->workername); |
|
userstats_w->hashrate5m = userstats->hashrate5m; |
|
userstats_w->hashrate1hr = userstats->hashrate1hr; |
|
|
|
userstats_workername_root = add_to_ktree(userstats_workername_root, usw_item, cmp_userstats_workername); |
|
} |
|
us_item = prev_in_ktree(us_ctx); |
|
DATA_USERSTATS_NULL(userstats, us_item); |
|
} |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
rows = 0; |
|
// Add up per user |
|
usw_item = first_in_ktree(userstats_workername_root, usw_ctx); |
|
while (usw_item) { |
|
DATA_USERSTATS(userstats_w, usw_item); |
|
if (userstats_w->userid != userid) { |
|
if (userid != -1) { |
|
K_RLOCK(users_free); |
|
u_item = find_userid(userid); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
LOGERR("%s() userid %"PRId64" ignored - userstats but not users", |
|
__func__, userid); |
|
} 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(userid, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "userid:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(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(u_hashrate1hr, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "u_hashrate1hr:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
rows++; |
|
} |
|
} |
|
userid = userstats_w->userid; |
|
u_hashrate5m = 0; |
|
u_hashrate1hr = 0; |
|
} |
|
u_hashrate5m += userstats_w->hashrate5m; |
|
u_hashrate1hr += userstats_w->hashrate1hr; |
|
|
|
tmp_item = usw_item; |
|
usw_item = next_in_ktree(usw_ctx); |
|
|
|
k_add_head(userstats_free, tmp_item); |
|
} |
|
if (userid != -1) { |
|
K_RLOCK(users_free); |
|
u_item = find_userid(userid); |
|
K_RUNLOCK(users_free); |
|
if (!u_item) { |
|
LOGERR("%s() userid %"PRId64" ignored - userstats but not users", |
|
__func__, userid); |
|
} 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(userid, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "userid:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(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(u_hashrate1hr, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "u_hashrate1hr:%d=%s%c", rows, reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
rows++; |
|
} |
|
} |
|
|
|
userstats_workername_root = free_ktree(userstats_workername_root, NULL); |
|
K_WUNLOCK(userstats_free); |
|
|
|
snprintf(tmp, sizeof(tmp), |
|
"rows=%d%cflds=%s%c", |
|
rows, FLDSEP, |
|
"username,userid,u_hashrate5m,u_hashrate1hr", 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; |
|
|
|
if (reloading && !confirm_sharesummary) { |
|
if (tv_equal(cd, &(dbstatus.newest_createdate_workinfo))) |
|
igndup = true; |
|
else if (tv_newer(cd, &(dbstatus.newest_createdate_workinfo))) |
|
return NULL; |
|
} |
|
|
|
i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz); |
|
if (!i_workinfoid) |
|
return strdup(reply); |
|
|
|
if (confirm_sharesummary) { |
|
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); |
|
|
|
if (workinfoid < confirm_first_workinfoid || |
|
workinfoid > confirm_last_workinfoid) |
|
goto wiconf; |
|
} |
|
|
|
i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz); |
|
if (!i_poolinstance) |
|
return strdup(reply); |
|
|
|
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"); |
|
} |
|
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; |
|
|
|
// This just excludes the shares we certainly don't need |
|
if (reloading && !confirm_sharesummary) { |
|
if (tv_newer(cd, &(dbstatus.sharesummary_firstshare))) |
|
return NULL; |
|
} |
|
|
|
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); |
|
|
|
if (confirm_sharesummary) { |
|
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); |
|
|
|
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); |
|
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"); |
|
} |
|
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; |
|
|
|
// This just excludes the shareerrors we certainly don't need |
|
if (reloading && !confirm_sharesummary) { |
|
if (tv_newer(cd, &(dbstatus.sharesummary_firstshare))) |
|
return NULL; |
|
} |
|
|
|
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); |
|
|
|
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); |
|
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; |
|
|
|
if (reloading && !confirm_sharesummary) { |
|
if (tv_newer(cd, &(dbstatus.sharesummary_firstshare))) |
|
return NULL; |
|
} |
|
|
|
i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz); |
|
if (!i_workinfoid) |
|
return strdup(reply); |
|
|
|
if (confirm_sharesummary) { |
|
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); |
|
|
|
if (workinfoid < confirm_first_workinfoid || |
|
workinfoid > confirm_last_workinfoid) |
|
goto awconf; |
|
} |
|
|
|
i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz); |
|
if (!i_poolinstance) |
|
return strdup(reply); |
|
|
|
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); |
|
|
|
ok = workinfo_age(conn, 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 |
|
* N.B. this means if you abort/shutdown the reload, |
|
* next restart will again go back to the oldest |
|
* unaged sharesummary due to a pool shutdown */ |
|
if (!reloading) { |
|
// Aging is a queued item thus the reply is ignored |
|
auto_age_older(conn, 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", cmd); |
|
return strdup("bad.cmd"); |
|
} |
|
|
|
// TODO: the confirm update: identify block changes from workinfo height? |
|
static char *cmd_blocks_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_ITEM *i_height, *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_height = require_name(trf_root, "height", 1, NULL, reply, siz); |
|
if (!i_height) |
|
return strdup(reply); |
|
|
|
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, transfer_data(i_height), |
|
transfer_data(i_blockhash), |
|
transfer_data(i_confirmed), |
|
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, transfer_data(i_height), |
|
transfer_data(i_blockhash), |
|
transfer_data(i_confirmed), |
|
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) |
|
{ |
|
bool igndup = false; |
|
|
|
// confirm_summaries() doesn't call this |
|
if (reloading) { |
|
if (tv_equal(cd, &(dbstatus.newest_createdate_blocks))) |
|
igndup = true; |
|
else if (tv_newer(cd, &(dbstatus.newest_createdate_blocks))) |
|
return NULL; |
|
} |
|
|
|
return cmd_blocks_do(conn, cmd, 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, bool igndup, |
|
K_TREE *trf_root) |
|
{ |
|
char reply[1024] = ""; |
|
size_t siz = sizeof(reply); |
|
K_ITEM *i_poolinstance, *i_username, *i_workername, *i_clientid; |
|
K_ITEM *i_enonce1, *i_useragent, *i_preauth; |
|
char *secuserid; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_poolinstance = optional_name(trf_root, "poolinstance", 1, NULL); |
|
if (!i_poolinstance) |
|
i_poolinstance = &auth_poolinstance; |
|
|
|
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 = optional_name(trf_root, "preauth", 1, NULL); |
|
if (!i_preauth) |
|
i_preauth = &auth_preauth; |
|
|
|
secuserid = 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, igndup, trf_root, false); |
|
|
|
if (!secuserid) { |
|
LOGDEBUG("%s() %s.failed.DBE", __func__, id); |
|
return strdup("failed.DBE"); |
|
} |
|
|
|
LOGDEBUG("%s.ok.auth added for %s", id, secuserid); |
|
snprintf(reply, siz, "ok.%s", secuserid); |
|
return strdup(reply); |
|
} |
|
|
|
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) |
|
{ |
|
bool igndup = false; |
|
|
|
// confirm_summaries() doesn't call this |
|
if (reloading) { |
|
if (tv_equal(cd, &(dbstatus.newest_createdate_auths))) |
|
igndup = true; |
|
else if (tv_newer(cd, &(dbstatus.newest_createdate_auths))) |
|
return NULL; |
|
} |
|
|
|
return cmd_auth_do(conn, cmd, id, by, code, inet, cd, igndup, trf_root); |
|
} |
|
|
|
static char *cmd_addrauth_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_ITEM *i_poolinstance, *i_username, *i_workername, *i_clientid; |
|
K_ITEM *i_enonce1, *i_useragent, *i_preauth; |
|
char *secuserid; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_poolinstance = optional_name(trf_root, "poolinstance", 1, NULL); |
|
if (!i_poolinstance) |
|
i_poolinstance = &auth_poolinstance; |
|
|
|
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); |
|
|
|
secuserid = 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, igndup, trf_root, true); |
|
|
|
if (!secuserid) { |
|
LOGDEBUG("%s() %s.failed.DBE", __func__, id); |
|
return strdup("failed.DBE"); |
|
} |
|
|
|
LOGDEBUG("%s.ok.auth added for %s", id, secuserid); |
|
snprintf(reply, siz, "ok.%s", secuserid); |
|
return strdup(reply); |
|
} |
|
|
|
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) |
|
{ |
|
bool igndup = false; |
|
|
|
// confirm_summaries() doesn't call this |
|
if (reloading) { |
|
if (tv_equal(cd, &(dbstatus.newest_createdate_auths))) |
|
igndup = true; |
|
else if (tv_newer(cd, &(dbstatus.newest_createdate_auths))) |
|
return NULL; |
|
} |
|
|
|
return cmd_addrauth_do(conn, cmd, id, by, code, inet, cd, igndup, trf_root); |
|
} |
|
|
|
static char *cmd_homepage(__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, *u_item, *b_item, *p_item, *us_item, look; |
|
double u_hashrate5m, u_hashrate1hr; |
|
char reply[1024], tmp[1024], *buf; |
|
USERSTATS lookuserstats, *userstats; |
|
POOLSTATS *poolstats; |
|
BLOCKS *blocks; |
|
USERS *users; |
|
int64_t u_elapsed; |
|
K_TREE_CTX ctx[1], w_ctx[1]; |
|
size_t len, off; |
|
bool has_uhr; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = optional_name(trf_root, "username", 1, NULL); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
if (last_bc.tv_sec) { |
|
tvs_to_buf(&last_bc, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "lastbc=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
K_RLOCK(workinfo_free); |
|
if (workinfo_current) { |
|
WORKINFO *wic; |
|
int32_t hi; |
|
DATA_WORKINFO(wic, workinfo_current); |
|
hi = coinbase1height(wic->coinbase1); |
|
snprintf(tmp, sizeof(tmp), "lastheight=%d%c", |
|
hi-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, sizeof(reply)); |
|
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: assumes only one poolinstance (for now) |
|
p_item = last_in_ktree(poolstats_root, ctx); |
|
if (p_item) { |
|
DATA_POOLSTATS(poolstats, p_item); |
|
int_to_buf(poolstats->users, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "users=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
int_to_buf(poolstats->workers, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "workers=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(poolstats->hashrate5m, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "p_hashrate5m=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(poolstats->hashrate1hr, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "p_hashrate1hr=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
bigint_to_buf(poolstats->elapsed, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "p_elapsed=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} else { |
|
snprintf(tmp, sizeof(tmp), "users=?%cworkers=?%cp_hashrate5m=?%c" |
|
"p_hashrate1hr=?%cp_elapsed=?%c", |
|
FLDSEP, FLDSEP, FLDSEP, FLDSEP, 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); |
|
} |
|
|
|
has_uhr = false; |
|
if (p_item && u_item) { |
|
DATA_USERS(users, u_item); |
|
K_TREE *userstats_workername_root = new_ktree(); |
|
u_hashrate5m = u_hashrate1hr = 0.0; |
|
u_elapsed = -1; |
|
// find last stored userid record |
|
lookuserstats.userid = users->userid; |
|
lookuserstats.statsdate.tv_sec = date_eot.tv_sec; |
|
lookuserstats.statsdate.tv_usec = date_eot.tv_usec; |
|
// find/cmp doesn't get to here |
|
STRNCPY(lookuserstats.poolinstance, EMPTY); |
|
STRNCPY(lookuserstats.workername, EMPTY); |
|
INIT_USERSTATS(&look); |
|
look.data = (void *)(&lookuserstats); |
|
K_RLOCK(userstats_free); |
|
us_item = find_before_in_ktree(userstats_root, &look, cmp_userstats, ctx); |
|
DATA_USERSTATS_NULL(userstats, us_item); |
|
while (us_item && userstats->userid == lookuserstats.userid && |
|
tvdiff(now, &(userstats->statsdate)) < USERSTATS_PER_S) { |
|
// TODO: add the latest per pool instance (this is the latest per worker) |
|
// Ignore summarised data from the DB, it should be old so irrelevant |
|
if (userstats->poolinstance[0] && |
|
!find_in_ktree(userstats_workername_root, us_item, cmp_userstats_workername, w_ctx)) { |
|
u_hashrate5m += userstats->hashrate5m; |
|
u_hashrate1hr += userstats->hashrate1hr; |
|
if (u_elapsed == -1 || u_elapsed > userstats->elapsed) |
|
u_elapsed = userstats->elapsed; |
|
has_uhr = true; |
|
userstats_workername_root = add_to_ktree(userstats_workername_root, |
|
us_item, |
|
cmp_userstats_workername); |
|
} |
|
us_item = prev_in_ktree(ctx); |
|
DATA_USERSTATS_NULL(userstats, us_item); |
|
} |
|
userstats_workername_root = free_ktree(userstats_workername_root, NULL); |
|
K_RUNLOCK(userstats_free); |
|
} |
|
|
|
if (has_uhr) { |
|
double_to_buf(u_hashrate5m, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "u_hashrate5m=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
double_to_buf(u_hashrate1hr, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "u_hashrate1hr=%s%c", reply, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
bigint_to_buf(u_elapsed, reply, sizeof(reply)); |
|
snprintf(tmp, sizeof(tmp), "u_elapsed=%s", reply); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} else { |
|
snprintf(tmp, sizeof(tmp), "u_hashrate5m=?%cu_hashrate1hr=?%cu_elapsed=?", |
|
FLDSEP, FLDSEP); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
} |
|
|
|
LOGDEBUG("%s.ok.home,user=%s", id, |
|
i_username ? transfer_data(i_username): "N"); |
|
return buf; |
|
} |
|
|
|
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); |
|
char tmp[1024]; |
|
USERATTS *useratts; |
|
USERS *users; |
|
char *reason = NULL; |
|
char *answer = NULL; |
|
char *ptr, *comma, *dot; |
|
size_t len, off; |
|
bool first; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz); |
|
if (!i_username) { |
|
reason = "Missing username"; |
|
goto nuts; |
|
} |
|
|
|
K_RLOCK(users_free); |
|
u_item = find_users(transfer_data(i_username)); |
|
K_RUNLOCK(users_free); |
|
|
|
if (!u_item) { |
|
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); |
|
ptr = strdup(transfer_data(i_attlist)); |
|
first = true; |
|
while (ptr && *ptr) { |
|
comma = strchr(ptr, ','); |
|
if (comma) |
|
*(comma++) = '\0'; |
|
dot = strchr(ptr, '.'); |
|
if (!dot) { |
|
free(answer); |
|
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 before now (not 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 before now (not expired) |
|
if (tv_newer(&(useratts->attdate2), now)) |
|
ans = TRUE_STR; |
|
else |
|
ans = FALSE_STR; |
|
} else { |
|
free(answer); |
|
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 (reason) { |
|
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 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)); |
|
} |
|
} |
|
|
|
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; |
|
USERS *users; |
|
char attname[sizeof(useratts->attname)*2]; |
|
char *reason = NULL; |
|
char *dot, *data; |
|
bool begun = false; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
i_username = require_name(trf_root, "username", 3, (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); |
|
/* format is: ua_attname.element=value |
|
* i.e. eash starts with the constant "ua_" |
|
* transfer will sort them so that any of the same attname |
|
* will be next to each other |
|
* thus can combine multiple elements for the same attname |
|
* into one single useratts record (as is mandatory) */ |
|
t_item = first_in_ktree(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; |
|
else { |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
PQclear(res); |
|
reason = "DBERR"; |
|
goto bats; |
|
} |
|
} |
|
if (!ua_item) { |
|
K_RLOCK(useratts_free); |
|
ua_item = k_unlink_head(useratts_free); |
|
K_RUNLOCK(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); |
|
} |
|
if (strcmp(dot, "str") == 0) { |
|
STRNCPY(useratts->attstr, data); |
|
} else if (strcmp(dot, "str2") == 0) { |
|
STRNCPY(useratts->attstr2, data); |
|
} else if (strcmp(dot, "num") == 0) { |
|
TXT_TO_BIGINT("num", data, useratts->attnum); |
|
} else if (strcmp(dot, "num2") == 0) { |
|
TXT_TO_BIGINT("num2", data, useratts->attnum2); |
|
} else if (strcmp(dot, "date") == 0) { |
|
att_to_date(&(useratts->attdate), data, now); |
|
} else if (strcmp(dot, "date2") == 0) { |
|
att_to_date(&(useratts->attdate2), data, now); |
|
} 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)) { |
|
res = PQexec(conn, "Rollback", CKPQ_WRITE); |
|
PQclear(res); |
|
reason = "DBERR"; |
|
goto bats; |
|
} |
|
res = PQexec(conn, "Commit", 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"); |
|
LOGDEBUG("%s.%s", id, reply); |
|
return strdup(reply); |
|
} |
|
|
|
// order by userid asc |
|
static cmp_t cmp_mu(K_ITEM *a, K_ITEM *b) |
|
{ |
|
MININGPAYOUTS *ma, *mb; |
|
DATA_MININGPAYOUTS(ma, a); |
|
DATA_MININGPAYOUTS(mb, b); |
|
return CMP_BIGINT(ma->userid, mb->userid); |
|
} |
|
|
|
static K_TREE *upd_add_mu(K_TREE *mu_root, K_STORE *mu_store, int64_t userid, int64_t diffacc) |
|
{ |
|
MININGPAYOUTS lookminingpayouts, *miningpayouts; |
|
K_ITEM look, *mu_item; |
|
K_TREE_CTX ctx[1]; |
|
|
|
lookminingpayouts.userid = userid; |
|
INIT_MININGPAYOUTS(&look); |
|
look.data = (void *)(&lookminingpayouts); |
|
mu_item = find_in_ktree(mu_root, &look, cmp_mu, ctx); |
|
if (mu_item) { |
|
DATA_MININGPAYOUTS(miningpayouts, mu_item); |
|
miningpayouts->amount += diffacc; |
|
} else { |
|
K_WLOCK(mu_store); |
|
mu_item = k_unlink_head(miningpayouts_free); |
|
DATA_MININGPAYOUTS(miningpayouts, mu_item); |
|
miningpayouts->userid = userid; |
|
miningpayouts->amount = diffacc; |
|
mu_root = add_to_ktree(mu_root, mu_item, cmp_mu); |
|
k_add_head(mu_store, mu_item); |
|
K_WUNLOCK(mu_store); |
|
} |
|
|
|
return mu_root; |
|
} |
|
|
|
/* Find the block_workinfoid of the block requested |
|
then add all it's diffacc shares |
|
then keep stepping back shares until diffacc_total matches or exceeds |
|
the number required (diff_want) - this is begin_workinfoid |
|
(also summarising diffacc per user) |
|
then keep stepping back until we complete the current begin_workinfoid |
|
(also summarising diffacc per user) |
|
This will give us the total number of diff1 shares (diffacc_total) |
|
to use for the payment calculations |
|
The value of diff_want defaults to the block's network difficulty |
|
(block_ndiff) but can be changed with diff_times and diff_add to: |
|
block_ndiff * diff_times + diff_add |
|
N.B. diff_times and diff_add can be zero, positive or negative |
|
The pplns_elapsed time of the shares is from the createdate of the |
|
begin_workinfoid that has shares accounted to the total, |
|
up to the createdate of the last share |
|
The user average hashrate would be: |
|
diffacc_user * 2^32 / pplns_elapsed |
|
PPLNS fraction of the block would be: |
|
diffacc_user / diffacc_total |
|
*/ |
|
|
|
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; |
|
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 *mu_item, *wb_item, *u_item; |
|
SHARESUMMARY looksharesummary, *sharesummary; |
|
MININGPAYOUTS *miningpayouts; |
|
WORKINFO *workinfo; |
|
TRANSFER *transfer; |
|
BLOCKS lookblocks, *blocks; |
|
K_TREE *mu_root; |
|
K_STORE *mu_store; |
|
USERS *users; |
|
int32_t height; |
|
int64_t workinfoid, end_workinfoid; |
|
int64_t begin_workinfoid; |
|
int64_t share_count; |
|
char tv_buf[DATE_BUFSIZ]; |
|
tv_t cd, begin_tv, block_tv, end_tv; |
|
K_TREE_CTX ctx[1]; |
|
double ndiff, total, elapsed; |
|
double diff_times = 1.0; |
|
double diff_add = 0.0; |
|
double diff_want; |
|
bool allow_aged = false; |
|
char ndiffbin[TXT_SML+1]; |
|
size_t len, off; |
|
int rows; |
|
|
|
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_difftimes = optional_name(trf_root, "diff_times", 1, NULL); |
|
if (i_difftimes) |
|
TXT_TO_DOUBLE("diff_times", transfer_data(i_difftimes), diff_times); |
|
|
|
i_diffadd = optional_name(trf_root, "diff_add", 1, NULL); |
|
if (i_diffadd) |
|
TXT_TO_DOUBLE("diff_add", transfer_data(i_diffadd), diff_add); |
|
|
|
i_allowaged = optional_name(trf_root, "allow_aged", 1, NULL); |
|
if (i_allowaged) { |
|
DATA_TRANSFER(transfer, i_allowaged); |
|
if (toupper(transfer->mvalue[0]) == TRUE_STR[0]) |
|
allow_aged = true; |
|
} |
|
|
|
cd.tv_sec = cd.tv_usec = 0L; |
|
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, cmp_blocks, 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_CONFIRM) |
|
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.unconfirmed block %d", height); |
|
return strdup(reply); |
|
} |
|
workinfoid = blocks->workinfoid; |
|
copy_tv(&block_tv, &(blocks->createdate)); |
|
copy_tv(&end_tv, &(blocks->createdate)); |
|
|
|
w_item = find_workinfo(workinfoid); |
|
if (!w_item) { |
|
snprintf(reply, siz, "ERR.missing workinfo %"PRId64, workinfoid); |
|
return strdup(reply); |
|
} |
|
DATA_WORKINFO(workinfo, w_item); |
|
|
|
hex2bin(ndiffbin, workinfo->bits, 4); |
|
ndiff = diff_from_nbits(ndiffbin); |
|
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); |
|
} |
|
|
|
begin_workinfoid = 0; |
|
share_count = 0; |
|
total = 0; |
|
|
|
looksharesummary.workinfoid = workinfoid; |
|
looksharesummary.userid = MAXID; |
|
looksharesummary.workername[0] = '\0'; |
|
INIT_SHARESUMMARY(&ss_look); |
|
ss_look.data = (void *)(&looksharesummary); |
|
K_RLOCK(sharesummary_free); |
|
ss_item = find_before_in_ktree(sharesummary_workinfoid_root, &ss_look, |
|
cmp_sharesummary_workinfoid, ctx); |
|
if (!ss_item) { |
|
K_RUNLOCK(sharesummary_free); |
|
snprintf(reply, siz, |
|
"ERR.no shares found with or before " |
|
"workinfo %"PRId64, |
|
workinfoid); |
|
return strdup(reply); |
|
} |
|
DATA_SHARESUMMARY(sharesummary, ss_item); |
|
|
|
mu_store = k_new_store(miningpayouts_free); |
|
mu_root = new_ktree(); |
|
end_workinfoid = sharesummary->workinfoid; |
|
/* add up all sharesummaries until >= diff_want |
|
* also record the latest lastshare - that will be the end pplns time |
|
* which will be >= block_tv */ |
|
while (ss_item && total < diff_want) { |
|
switch (sharesummary->complete[0]) { |
|
case SUMMARY_CONFIRM: |
|
break; |
|
case SUMMARY_COMPLETE: |
|
if (allow_aged) |
|
break; |
|
default: |
|
K_RUNLOCK(sharesummary_free); |
|
snprintf(reply, siz, |
|
"ERR.sharesummary1 not ready in " |
|
"workinfo %"PRId64, |
|
sharesummary->workinfoid); |
|
goto shazbot; |
|
} |
|
share_count++; |
|
total += (int64_t)(sharesummary->diffacc); |
|
begin_workinfoid = sharesummary->workinfoid; |
|
if (tv_newer(&end_tv, &(sharesummary->lastshare))) |
|
copy_tv(&end_tv, &(sharesummary->lastshare)); |
|
mu_root = 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: |
|
K_RUNLOCK(sharesummary_free); |
|
snprintf(reply, siz, |
|
"ERR.sharesummary2 not ready in " |
|
"workinfo %"PRId64, |
|
sharesummary->workinfoid); |
|
goto shazbot; |
|
} |
|
share_count++; |
|
total += (int64_t)(sharesummary->diffacc); |
|
mu_root = 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); |
|
} |
|
K_RUNLOCK(sharesummary_free); |
|
|
|
if (total == 0.0) { |
|
snprintf(reply, siz, |
|
"ERR.total share diff 0 before workinfo %"PRId64, |
|
workinfoid); |
|
goto shazbot; |
|
} |
|
|
|
wb_item = find_workinfo(begin_workinfoid); |
|
if (!wb_item) { |
|
snprintf(reply, siz, "ERR.missing begin workinfo record! %"PRId64, 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), "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", 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, 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) { |
|
K_ITEM *pa_item; |
|
PAYMENTADDRESSES *pa; |
|
char *payaddress; |
|
|
|
pa_item = find_paymentaddresses(miningpayouts->userid); |
|
if (pa_item) { |
|
DATA_PAYMENTADDRESSES(pa, pa_item); |
|
payaddress = pa->payaddress; |
|
} else |
|
payaddress = "none"; |
|
|
|
DATA_USERS(users, u_item); |
|
snprintf(tmp, sizeof(tmp), |
|
"user:%d=%s%cpayaddress:%d=%s%c", |
|
rows, users->username, FLDSEP, |
|
rows, payaddress, FLDSEP); |
|
|
|
} else { |
|
snprintf(tmp, sizeof(tmp), |
|
"user:%d=%"PRId64"%cpayaddress:%d=none%c", |
|
rows, miningpayouts->userid, FLDSEP, |
|
rows, 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); |
|
|
|
mu_item = next_in_ktree(ctx); |
|
rows++; |
|
} |
|
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), "share_count=%"PRId64, share_count); |
|
APPEND_REALLOC(buf, off, len, tmp); |
|
|
|
mu_root = free_ktree(mu_root, NULL); |
|
K_WLOCK(mu_store); |
|
k_list_transfer_to_head(mu_store, miningpayouts_free); |
|
K_WUNLOCK(mu_store); |
|
mu_store = k_free_store(mu_store); |
|
|
|
LOGDEBUG("%s.ok.pplns.%s", id, buf); |
|
return buf; |
|
|
|
shazbot: |
|
mu_root = free_ktree(mu_root, NULL); |
|
K_WLOCK(mu_store); |
|
k_list_transfer_to_head(mu_store, miningpayouts_free); |
|
K_WUNLOCK(mu_store); |
|
mu_store = k_free_store(mu_store); |
|
|
|
return strdup(reply); |
|
} |
|
|
|
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(sharesummary_free, sharesummary_root, transfer_data(i_file), NULL); |
|
|
|
dsp_ktree(userstats_free, userstats_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; |
|
size_t len, off; |
|
uint64_t ram, tot = 0; |
|
K_LIST *klist; |
|
int rows = 0; |
|
|
|
LOGDEBUG("%s(): cmd '%s'", __func__, cmd); |
|
|
|
APPEND_REALLOC_INIT(buf, off, len); |
|
APPEND_REALLOC(buf, off, len, "ok."); |
|
|
|
// Doesn't include blob memory |
|
// - average transactiontree length of ~119k I have is ~28k (>3.3GB) |
|
#define USEINFO(_obj, _stores, _trees) \ |
|
klist = _obj ## _free; \ |
|
ram = sizeof(K_LIST) + _stores * sizeof(K_STORE) + \ |
|
klist->allocate * klist->item_mem_count * klist->siz + \ |
|
sizeof(K_TREE) * (klist->total - klist->count) * _trees; \ |
|
snprintf(tmp, sizeof(tmp), \ |
|
"name:%d=" #_obj "%cinitial:%d=%d%callocated:%d=%d%c" \ |
|
"store:%d=%d%ctrees:%d=%d%cram:%d=%"PRIu64"%c" \ |
|
"cull:%d=%d%c", \ |
|
rows, FLDSEP, \ |
|
rows, klist->allocate, FLDSEP, \ |
|
rows, klist->total, FLDSEP, \ |
|
rows, klist->total - klist->count, FLDSEP, \ |
|
rows, _trees, FLDSEP, \ |
|
rows, ram, FLDSEP, \ |
|
rows, klist->cull_count, FLDSEP); \ |
|
APPEND_REALLOC(buf, off, len, tmp); \ |
|
tot += ram; \ |
|
rows++; |
|
|
|
USEINFO(users, 1, 2); |
|
USEINFO(workers, 1, 1); |
|
USEINFO(paymentaddresses, 1, 1); |
|
USEINFO(payments, 1, 1); |
|
USEINFO(idcontrol, 1, 0); |
|
USEINFO(workinfo, 1, 1); |
|
USEINFO(shares, 1, 1); |
|
USEINFO(shareerrors, 1, 1); |
|
USEINFO(sharesummary, 1, 2); |
|
USEINFO(blocks, 1, 1); |
|
USEINFO(auths, 1, 1); |
|
USEINFO(poolstats, 1, 1); |
|
USEINFO(userstats, 4, 2); |
|
USEINFO(workerstatus, 1, 1); |
|
USEINFO(workqueue, 1, 0); |
|
USEINFO(transfer, 0, 0); |
|
USEINFO(logqueue, 1, 0); |
|
|
|
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,store,trees,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: limit access by having seperate sockets for each |
|
#define ACCESS_POOL "p" |
|
#define ACCESS_SYSTEM "s" |
|
#define ACCESS_WEB "w" |
|
#define ACCESS_PROXY "x" |
|
#define ACCESS_CKDB "c" |
|
|
|
/* 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 |
|
* |
|
* shutdown |
|
* .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 |
|
*/ |
|
static struct CMDS { |
|
enum cmd_values cmd_val; |
|
char *cmd_str; |
|
bool noid; // doesn't require an id |
|
bool createdate; // requires a createdate |
|
char *(*func)(PGconn *, char *, char *, tv_t *, char *, char *, |
|
char *, tv_t *, K_TREE *); |
|
char *access; |
|
} cmds[] = { |
|
{ CMD_SHUTDOWN, "shutdown", true, false, NULL, ACCESS_SYSTEM }, |
|
{ CMD_PING, "ping", true, false, NULL, ACCESS_SYSTEM ACCESS_POOL ACCESS_WEB }, |
|
{ CMD_VERSION, "version", true, false, NULL, ACCESS_SYSTEM ACCESS_POOL ACCESS_WEB }, |
|
{ CMD_LOGLEVEL, "loglevel", true, false, NULL, ACCESS_SYSTEM }, |
|
{ CMD_SHARELOG, STR_WORKINFO, false, true, cmd_sharelog, ACCESS_POOL }, |
|
{ CMD_SHARELOG, STR_SHARES, false, true, cmd_sharelog, ACCESS_POOL }, |
|
{ CMD_SHARELOG, STR_SHAREERRORS, false, true, cmd_sharelog, ACCESS_POOL }, |
|
{ CMD_SHARELOG, STR_AGEWORKINFO, false, true, cmd_sharelog, ACCESS_POOL }, |
|
{ CMD_AUTH, "authorise", false, true, cmd_auth, ACCESS_POOL }, |
|
{ CMD_ADDRAUTH, "addrauth", false, true, cmd_addrauth, ACCESS_POOL }, |
|
{ CMD_ADDUSER, "adduser", false, false, cmd_adduser, ACCESS_WEB }, |
|
{ CMD_NEWPASS, "newpass", false, false, cmd_newpass, ACCESS_WEB }, |
|
{ CMD_CHKPASS, "chkpass", false, false, cmd_chkpass, ACCESS_WEB }, |
|
{ CMD_USERSET, "usersettings", false, false, cmd_userset, ACCESS_WEB }, |
|
{ CMD_POOLSTAT, "poolstats", false, true, cmd_poolstats, ACCESS_POOL }, |
|
{ CMD_USERSTAT, "userstats", false, true, cmd_userstats, ACCESS_POOL }, |
|
{ CMD_BLOCK, "block", false, true, cmd_blocks, ACCESS_POOL }, |
|
{ CMD_BLOCKLIST,"blocklist", false, false, cmd_blocklist, ACCESS_WEB }, |
|
{ CMD_BLOCKSTATUS,"blockstatus",false, false, cmd_blockstatus,ACCESS_WEB }, |
|
{ CMD_NEWID, "newid", false, false, cmd_newid, ACCESS_SYSTEM }, |
|
{ CMD_PAYMENTS, "payments", false, false, cmd_payments, ACCESS_WEB }, |
|
{ CMD_WORKERS, "workers", false, false, cmd_workers, ACCESS_WEB }, |
|
{ CMD_ALLUSERS, "allusers", false, false, cmd_allusers, ACCESS_WEB }, |
|
{ CMD_HOMEPAGE, "homepage", false, false, cmd_homepage, ACCESS_WEB }, |
|
{ CMD_GETATTS, "getatts", false, false, cmd_getatts, ACCESS_WEB }, |
|
{ CMD_SETATTS, "setatts", false, false, cmd_setatts, ACCESS_WEB }, |
|
{ CMD_DSP, "dsp", false, false, cmd_dsp, ACCESS_SYSTEM }, |
|
{ CMD_STATS, "stats", true, false, cmd_stats, ACCESS_SYSTEM }, |
|
{ CMD_PPLNS, "pplns", false, false, cmd_pplns, ACCESS_SYSTEM }, |
|
{ CMD_END, NULL, false, false, NULL, NULL } |
|
}; |
|
|
|
static enum cmd_values breakdown(K_TREE **trf_root, K_STORE **trf_store, |
|
char *buf, int *which_cmds, char *cmd, |
|
char *id, tv_t *cd) |
|
{ |
|
char reply[1024] = ""; |
|
TRANSFER *transfer; |
|
K_TREE_CTX ctx[1]; |
|
K_ITEM *item; |
|
char *cmdptr, *idptr, *next, *eq; |
|
char *data = NULL; |
|
bool noid = false; |
|
|
|
*trf_root = NULL; |
|
*trf_store = NULL; |
|
*which_cmds = CMD_UNSET; |
|
*cmd = *id = '\0'; |
|
cd->tv_sec = 0; |
|
cd->tv_usec = 0; |
|
|
|
cmdptr = strdup(buf); |
|
idptr = strchr(cmdptr, '.'); |
|
if (!idptr || !*idptr) |
|
noid = true; |
|
else { |
|
*(idptr++) = '\0'; |
|
data = strchr(idptr, '.'); |
|
if (data) |
|
*(data++) = '\0'; |
|
STRNCPYSIZ(id, idptr, ID_SIZ); |
|
} |
|
|
|
STRNCPYSIZ(cmd, cmdptr, CMD_SIZ); |
|
for (*which_cmds = 0; cmds[*which_cmds].cmd_val != CMD_END; (*which_cmds)++) { |
|
if (strcasecmp(cmd, cmds[*which_cmds].cmd_str) == 0) |
|
break; |
|
} |
|
|
|
if (cmds[*which_cmds].cmd_val == CMD_END) { |
|
LOGERR("Listener received unknown command: '%s'", buf); |
|
free(cmdptr); |
|
return CMD_REPLY; |
|
} |
|
|
|
if (noid) { |
|
if (cmds[*which_cmds].noid) { |
|
*id = '\0'; |
|
free(cmdptr); |
|
return cmds[*which_cmds].cmd_val; |
|
} |
|
|
|
STRNCPYSIZ(id, cmdptr, ID_SIZ); |
|
LOGERR("Listener received invalid (noid) message: '%s'", buf); |
|
free(cmdptr); |
|
return CMD_REPLY; |
|
} |
|
|
|
*trf_root = new_ktree(); |
|
*trf_store = k_new_store(transfer_free); |
|
next = data; |
|
if (next && strncmp(next, JSON_TRANSFER, JSON_TRANSFER_LEN) == 0) { |
|
json_t *json_data; |
|
json_error_t err_val; |
|
void *json_iter; |
|
const char *json_key, *json_str; |
|
json_t *json_value; |
|
int json_typ; |
|
size_t siz; |
|
bool ok; |
|
|
|
next += JSON_TRANSFER_LEN; |
|
json_data = json_loads(next, JSON_DISABLE_EOF_CHECK, &err_val); |
|
if (!json_data) { |
|
/* This REALLY shouldn't ever get an error since the input |
|
* is a json generated string |
|
* If that happens then dump lots of information */ |
|
char *text = safe_text(next); |
|
LOGERR("Json decode error from command: '%s' " |
|
"json_err=(%d:%d:%d)%s:%s input='%s'", |
|
cmd, err_val.line, err_val.column, |
|
err_val.position, err_val.source, |
|
err_val.text, text); |
|
free(text); |
|
free(cmdptr); |
|
return CMD_REPLY; |
|
} |
|
json_iter = json_object_iter(json_data); |
|
K_WLOCK(transfer_free); |
|
while (json_iter) { |
|
json_key = json_object_iter_key(json_iter); |
|
json_value = json_object_iter_value(json_iter); |
|
item = k_unlink_head(transfer_free); |
|
DATA_TRANSFER(transfer, item); |
|
ok = true; |
|
json_typ = json_typeof(json_value); |
|
switch (json_typ) { |
|
case JSON_STRING: |
|
json_str = json_string_value(json_value); |
|
siz = strlen(json_str); |
|
if (siz >= sizeof(transfer->svalue)) |
|
transfer->mvalue = strdup(json_str); |
|
else { |
|
STRNCPY(transfer->svalue, json_str); |
|
transfer->mvalue = transfer->svalue; |
|
} |
|
break; |
|
case JSON_REAL: |
|
snprintf(transfer->svalue, |
|
sizeof(transfer->svalue), |
|
"%f", json_real_value(json_value)); |
|
transfer->mvalue = transfer->svalue; |
|
break; |
|
case JSON_INTEGER: |
|
snprintf(transfer->svalue, |
|
sizeof(transfer->svalue), |
|
"%"PRId64, |
|
(int64_t)json_integer_value(json_value)); |
|
transfer->mvalue = transfer->svalue; |
|
break; |
|
case JSON_TRUE: |
|
case JSON_FALSE: |
|
snprintf(transfer->svalue, |
|
sizeof(transfer->svalue), |
|
"%s", (json_typ == JSON_TRUE) ? |
|
TRUE_STR : FALSE_STR); |
|
transfer->mvalue = transfer->svalue; |
|
break; |
|
case JSON_ARRAY: |
|
{ |
|
/* only one level array of strings for now (merkletree) |
|
* ignore other data */ |
|
size_t i, len, off, count = json_array_size(json_value); |
|
json_t *json_element; |
|
bool first = true; |
|
|
|
APPEND_REALLOC_INIT(transfer->mvalue, off, len); |
|
for (i = 0; i < count; i++) { |
|
json_element = json_array_get(json_value, i); |
|
if (json_is_string(json_element)) { |
|
json_str = json_string_value(json_element); |
|
siz = strlen(json_str); |
|
if (first) |
|
first = false; |
|
else { |
|
APPEND_REALLOC(transfer->mvalue, |
|
off, len, " "); |
|
} |
|
APPEND_REALLOC(transfer->mvalue, |
|
off, len, json_str); |
|
} else |
|
LOGERR("%s() unhandled json type %d in array %s" |
|
" in cmd %s", __func__, |
|
json_typ, json_key, cmd); |
|
} |
|
} |
|
break; |
|
default: |
|
LOGERR("%s() unhandled json type %d in cmd %s", |
|
__func__, json_typ, cmd); |
|
ok = false; |
|
break; |
|
} |
|
|
|
if (ok) |
|
STRNCPY(transfer->name, json_key); |
|
if (!ok || find_in_ktree(*trf_root, item, cmp_transfer, ctx)) { |
|
if (transfer->mvalue != transfer->svalue) |
|
free(transfer->mvalue); |
|
k_add_head(transfer_free, item); |
|
} else { |
|
*trf_root = add_to_ktree(*trf_root, item, cmp_transfer); |
|
k_add_head(*trf_store, item); |
|
} |
|
json_iter = json_object_iter_next(json_data, json_iter); |
|
} |
|
K_WUNLOCK(transfer_free); |
|
json_decref(json_data); |
|
} else { |
|
K_WLOCK(transfer_free); |
|
while (next && *next) { |
|
data = next; |
|
next = strchr(data, FLDSEP); |
|
if (next) |
|
*(next++) = '\0'; |
|
|
|
eq = strchr(data, '='); |
|
if (!eq) |
|
eq = EMPTY; |
|
else |
|
*(eq++) = '\0'; |
|
|
|
item = k_unlink_head(transfer_free); |
|
DATA_TRANSFER(transfer, item); |
|
STRNCPY(transfer->name, data); |
|
STRNCPY(transfer->svalue, eq); |
|
transfer->mvalue = transfer->svalue; |
|
|
|
if (find_in_ktree(*trf_root, item, cmp_transfer, ctx)) { |
|
if (transfer->mvalue != transfer->svalue) |
|
free(transfer->mvalue); |
|
k_add_head(transfer_free, item); |
|
} else { |
|
*trf_root = add_to_ktree(*trf_root, item, cmp_transfer); |
|
k_add_head(*trf_store, item); |
|
} |
|
} |
|
K_WUNLOCK(transfer_free); |
|
} |
|
if (cmds[*which_cmds].createdate) { |
|
item = require_name(*trf_root, "createdate", 10, NULL, reply, sizeof(reply)); |
|
if (!item) |
|
return CMD_REPLY; |
|
|
|
DATA_TRANSFER(transfer, item); |
|
txt_to_ctv("createdate", transfer->mvalue, cd, sizeof(*cd)); |
|
if (cd->tv_sec == 0) { |
|
LOGERR("%s(): failed, %s has invalid createdate '%s'", |
|
__func__, cmdptr, transfer->mvalue); |
|
free(cmdptr); |
|
return CMD_REPLY; |
|
} |
|
if (confirm_check_createdate) |
|
check_createdate_ccl(cmd, cd); |
|
} |
|
free(cmdptr); |
|
return cmds[*which_cmds].cmd_val; |
|
} |
|
|
|
static void summarise_blocks() |
|
{ |
|
K_ITEM *b_item, *b_prev, *wi_item, ss_look, *ss_item; |
|
K_TREE_CTX ctx[1], ss_ctx[1]; |
|
double diffacc, diffinv, shareacc, shareinv; |
|
tv_t now, elapsed_start, elapsed_finish; |
|
int64_t elapsed, wi_start, wi_finish; |
|
BLOCKS *blocks, *prev_blocks; |
|
WORKINFO *prev_workinfo; |
|
SHARESUMMARY looksharesummary, *sharesummary; |
|
int32_t hi, prev_hi; |
|
bool ok; |
|
|
|
setnow(&now); |
|
|
|
K_RLOCK(blocks_free); |
|
// Find the oldest, stats pending, not new, block |
|
b_item = first_in_ktree(blocks_root, ctx); |
|
while (b_item) { |
|
DATA_BLOCKS(blocks, b_item); |
|
if (CURRENT(&(blocks->expirydate)) && |
|
blocks->statsconfirmed[0] == BLOCKS_STATSPENDING && |
|
blocks->confirmed[0] != BLOCKS_NEW) |
|
break; |
|
b_item = next_in_ktree(ctx); |
|
} |
|
K_RUNLOCK(blocks_free); |
|
|
|
// None |
|
if (!b_item) |
|
return; |
|
|
|
wi_finish = blocks->workinfoid; |
|
hi = 0; |
|
K_RLOCK(workinfo_free); |
|
if (workinfo_current) { |
|
WORKINFO *wic; |
|
DATA_WORKINFO(wic, workinfo_current); |
|
hi = coinbase1height(wic->coinbase1); |
|
} |
|
K_RUNLOCK(workinfo_free); |
|
|
|
// Wait at least for the (badly named) '2nd' confirm |
|
if (hi == 0 || blocks->height >= (hi - 1)) |
|
return; |
|
|
|
diffacc = diffinv = shareacc = shareinv = 0; |
|
elapsed = 0; |
|
K_RLOCK(blocks_free); |
|
b_prev = find_prev_blocks(blocks->height); |
|
K_RUNLOCK(blocks_free); |
|
if (!b_prev) { |
|
wi_start = 0; |
|
elapsed_start.tv_sec = elapsed_start.tv_usec = 0L; |
|
prev_hi = 0; |
|
} else { |
|
DATA_BLOCKS(prev_blocks, b_prev); |
|
wi_start = prev_blocks->workinfoid; |
|
wi_item = find_workinfo(wi_start); |
|
if (!wi_item) { |
|
// This will repeat until fixed ... |
|
LOGERR("%s() block %d, but prev %d wid " |
|
"%"PRId64" is missing", |
|
__func__, blocks->height, |
|
prev_blocks->height, |
|
prev_blocks->workinfoid); |
|
return; |
|
} |
|
DATA_WORKINFO(prev_workinfo, wi_item); |
|
copy_tv(&elapsed_start, &(prev_workinfo->createdate)); |
|
prev_hi = prev_blocks->height; |
|
} |
|
elapsed_finish.tv_sec = elapsed_finish.tv_usec = 0L; |
|
|
|
// Add up the sharesummaries, abort if any SUMMARY_NEW |
|
looksharesummary.workinfoid = wi_finish; |
|
looksharesummary.userid = MAXID; |
|
looksharesummary.workername[0] = '\0'; |
|
INIT_SHARESUMMARY(&ss_look); |
|
ss_look.data = (void *)(&looksharesummary); |
|
K_RLOCK(sharesummary_free); |
|
ss_item = find_before_in_ktree(sharesummary_workinfoid_root, &ss_look, |
|
cmp_sharesummary_workinfoid, ss_ctx); |
|
|
|
if (!ss_item) { |
|
K_RUNLOCK(sharesummary_free); |
|
// This will repeat each call here until fixed ... |
|
LOGERR("%s() block %d, prev %d no sharesummaries " |
|
"on or before %"PRId64, |
|
__func__, blocks->height, |
|
prev_hi, wi_finish); |
|
return; |
|
} |
|
DATA_SHARESUMMARY(sharesummary, ss_item); |
|
while (ss_item && sharesummary->workinfoid > wi_start) { |
|
if (sharesummary->complete[0] == SUMMARY_NEW) { |
|
// Not aged yet |
|
K_RUNLOCK(sharesummary_free); |
|
return; |
|
} |
|
if (elapsed_start.tv_sec == 0 || |
|
!tv_newer(&elapsed_start, &(sharesummary->firstshare))) { |
|
copy_tv(&elapsed_start, &(sharesummary->firstshare)); |
|
} |
|
if (tv_newer(&elapsed_finish, &(sharesummary->lastshare))) |
|
copy_tv(&elapsed_finish, &(sharesummary->lastshare)); |
|
|
|
diffacc += sharesummary->diffacc; |
|
diffinv += sharesummary->diffsta + sharesummary->diffdup + |
|
sharesummary->diffhi + sharesummary-> diffrej; |
|
shareacc += sharesummary->shareacc; |
|
shareinv += sharesummary->sharesta + sharesummary->sharedup + |
|
sharesummary->sharehi + sharesummary-> sharerej; |
|
|
|
ss_item = prev_in_ktree(ss_ctx); |
|
DATA_SHARESUMMARY_NULL(sharesummary, ss_item); |
|
} |
|
K_RUNLOCK(sharesummary_free); |
|
|
|
elapsed = (int64_t)(tvdiff(&elapsed_finish, &elapsed_start) + 0.5); |
|
ok = blocks_stats(NULL, blocks->height, blocks->blockhash, |
|
diffacc, diffinv, shareacc, shareinv, elapsed, |
|
by_default, (char *)__func__, inet_default, &now); |
|
|
|
if (ok) { |
|
LOGWARNING("%s() block %d, stats confirmed " |
|
"%0.f/%.0f/%.0f/%.0f/%"PRId64, |
|
__func__, blocks->height, |
|
diffacc, diffinv, shareacc, shareinv, elapsed); |
|
} else { |
|
LOGERR("%s() block %d, failed to confirm stats", |
|
__func__, blocks->height); |
|
} |
|
} |
|
|
|
static void summarise_poolstats() |
|
{ |
|
// TODO |
|
} |
|
|
|
// TODO: daily |
|
// TODO: consider limiting how much/how long this processes each time |
|
static void summarise_userstats() |
|
{ |
|
K_TREE_CTX ctx[1], ctx2[1]; |
|
K_ITEM *first, *last, *new, *next, *tmp; |
|
USERSTATS *userstats, *us_first, *us_last, *us_next; |
|
double statrange, factor; |
|
bool locked, upgrade; |
|
tv_t now, process, when; |
|
PGconn *conn = NULL; |
|
int count; |
|
char error[1024]; |
|
char tvbuf1[DATE_BUFSIZ], tvbuf2[DATE_BUFSIZ]; |
|
|
|
upgrade = false; |
|
locked = false; |
|
while (1764) { |
|
error[0] = '\0'; |
|
setnow(&now); |
|
upgrade = false; |
|
locked = true; |
|
K_ILOCK(userstats_free); |
|
|
|
// confirm_summaries() doesn't call this |
|
if (!reloading) |
|
copy_tv(&process, &now); |
|
else { |
|
// During reload, base the check date on the newest statsdate |
|
last = last_in_ktree(userstats_statsdate_root, ctx); |
|
if (!last) |
|
break; |
|
|
|
DATA_USERSTATS(us_last, last); |
|
copy_tv(&process, &us_last->statsdate); |
|
} |
|
|
|
first = first_in_ktree(userstats_statsdate_root, ctx); |
|
DATA_USERSTATS_NULL(us_first, first); |
|
// Oldest non DB stat |
|
// TODO: make the index start with summarylevel? so can find faster |
|
while (first && us_first->summarylevel[0] != SUMMARY_NONE) { |
|
first = next_in_ktree(ctx); |
|
DATA_USERSTATS_NULL(us_first, first); |
|
} |
|
|
|
if (!first) |
|
break; |
|
|
|
statrange = tvdiff(&process, &(us_first->statsdate)); |
|
// Is there data ready for summarising? |
|
if (statrange <= USERSTATS_AGE) |
|
break; |
|
|
|
copy_tv(&when, &(us_first->statsdate)); |
|
/* Convert when to the start of the timeframe after the one it is in |
|
* assume timeval ignores leapseconds ... */ |
|
when.tv_sec = when.tv_sec - (when.tv_sec % USERSTATS_DB_S) + USERSTATS_DB_S; |
|
when.tv_usec = 0; |
|
|
|
// Is the whole timerange up to before 'when' ready for summarising? |
|
statrange = tvdiff(&process, &when); |
|
if (statrange < USERSTATS_AGE) |
|
break; |
|
|
|
next = next_in_ktree(ctx); |
|
|
|
upgrade = true; |
|
K_ULOCK(userstats_free); |
|
new = k_unlink_head(userstats_free); |
|
DATA_USERSTATS(userstats, new); |
|
memcpy(userstats, us_first, sizeof(USERSTATS)); |
|
|
|
userstats_root = remove_from_ktree(userstats_root, first, cmp_userstats, ctx2); |
|
userstats_statsdate_root = remove_from_ktree(userstats_statsdate_root, first, |
|
cmp_userstats_statsdate, ctx2); |
|
k_unlink_item(userstats_store, first); |
|
k_add_head(userstats_summ, first); |
|
|
|
count = 1; |
|
while (next) { |
|
DATA_USERSTATS(us_next, next); |
|
statrange = tvdiff(&when, &(us_next->statsdate)); |
|
if (statrange <= 0) |
|
break; |
|
|
|
tmp = next_in_ktree(ctx); |
|
|
|
if (us_next->summarylevel[0] == SUMMARY_NONE && |
|
us_next->userid == userstats->userid && |
|
strcmp(us_next->workername, userstats->workername) == 0) { |
|
count++; |
|
userstats->hashrate += us_next->hashrate; |
|
userstats->hashrate5m += us_next->hashrate5m; |
|
userstats->hashrate1hr += us_next->hashrate1hr; |
|
userstats->hashrate24hr += us_next->hashrate24hr; |
|
if (userstats->elapsed > us_next->elapsed) |
|
userstats->elapsed = us_next->elapsed; |
|
userstats->summarycount += us_next->summarycount; |
|
|
|
userstats_root = remove_from_ktree(userstats_root, next, cmp_userstats, ctx2); |
|
userstats_statsdate_root = remove_from_ktree(userstats_statsdate_root, next, |
|
cmp_userstats_statsdate, ctx2); |
|
k_unlink_item(userstats_store, next); |
|
k_add_head(userstats_summ, next); |
|
} |
|
next = tmp; |
|
} |
|
|
|
// Can temporarily release the lock since all our data is now not part of the lock |
|
if (upgrade) |
|
K_WUNLOCK(userstats_free); |
|
else |
|
K_IUNLOCK(userstats_free); |
|
upgrade = false; |
|
locked = false; |
|
|
|
if (userstats->hashrate5m > 0.0 || userstats->hashrate1hr > 0.0) |
|
userstats->idle = false; |
|
else |
|
userstats->idle = true; |
|
|
|
userstats->summarylevel[0] = SUMMARY_DB; |
|
userstats->summarylevel[1] = '\0'; |
|
|
|
// Expect 6 per poolinstance |
|
factor = (double)count / 6.0; |
|
userstats->hashrate *= factor; |
|
userstats->hashrate5m *= factor; |
|
userstats->hashrate1hr *= factor; |
|
userstats->hashrate24hr *= factor; |
|
|
|
copy_tv(&(userstats->statsdate), &when); |
|
// Stats to the end of this timeframe |
|
userstats->statsdate.tv_sec -= 1; |
|
userstats->statsdate.tv_usec = 999999; |
|
|
|
// This is simply when it was written, so 'now' is fine |
|
SIMPLEDATEDEFAULT(userstats, &now); |
|
|
|
if (!conn) |
|
conn = dbconnect(); |
|
|
|
if (!userstats_add_db(conn, userstats)) { |
|
/* This should only happen if a restart finds data |
|
that wasn't found during the reload but is in |
|
the same timeframe as DB data |
|
i.e. it shouldn't happen, but keep the summary anyway */ |
|
when.tv_sec -= USERSTATS_DB_S; |
|
tv_to_buf(&when, tvbuf1, sizeof(tvbuf1)); |
|
tv_to_buf(&(userstats->statsdate), tvbuf2, sizeof(tvbuf2)); |
|
snprintf(error, sizeof(error), |
|
"Userid %"PRId64" Worker %s, %d userstats record%s " |
|
"discarded from %s to %s", |
|
userstats->userid, |
|
userstats->workername, |
|
count, (count == 1 ? "" : "s"), |
|
tvbuf1, tvbuf2); |
|
} |
|
|
|
// The flags are not needed |
|
//upgrade = true; |
|
//locked = true; |
|
K_WLOCK(userstats_free); |
|
k_list_transfer_to_tail(userstats_summ, userstats_free); |
|
k_add_head(userstats_store, new); |
|
userstats_root = add_to_ktree(userstats_root, new, cmp_userstats); |
|
userstats_statsdate_root = add_to_ktree(userstats_statsdate_root, new, |
|
cmp_userstats_statsdate); |
|
|
|
K_WUNLOCK(userstats_free); |
|
//locked = false; |
|
//upgrade = false; |
|
|
|
if (error[0]) |
|
LOGERR(error); |
|
} |
|
|
|
if (locked) { |
|
if (upgrade) |
|
K_WUNLOCK(userstats_free); |
|
else |
|
K_IUNLOCK(userstats_free); |
|
} |
|
|
|
if (conn) |
|
PQfinish(conn); |
|
} |
|
|
|
static void *summariser(__maybe_unused void *arg) |
|
{ |
|
pthread_detach(pthread_self()); |
|
|
|
rename_proc("db_summariser"); |
|
|
|
while (!everyone_die && !db_load_complete) |
|
cksleep_ms(42); |
|
|
|
while (!everyone_die) { |
|
sleep(5); |
|
if (!everyone_die) |
|
summarise_blocks(); |
|
|
|
sleep(4); |
|
if (!everyone_die) |
|
summarise_poolstats(); |
|
|
|
sleep(4); |
|
if (!everyone_die) |
|
summarise_userstats(); |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
static void *logger(__maybe_unused void *arg) |
|
{ |
|
K_ITEM *lq_item; |
|
LOGQUEUE *lq; |
|
char buf[128]; |
|
tv_t now; |
|
|
|
pthread_detach(pthread_self()); |
|
|
|
snprintf(buf, sizeof(buf), "db%s_logger", dbcode); |
|
rename_proc(buf); |
|
|
|
setnow(&now); |
|
snprintf(buf, sizeof(buf), "logstart.%ld,%ld", |
|
now.tv_sec, now.tv_usec); |
|
LOGFILE(buf); |
|
|
|
while (!everyone_die) { |
|
K_WLOCK(logqueue_free); |
|
lq_item = k_unlink_head(logqueue_store); |
|
K_WUNLOCK(logqueue_free); |
|
while (lq_item) { |
|
DATA_LOGQUEUE(lq, lq_item); |
|
LOGFILE(lq->msg); |
|
free(lq->msg); |
|
|
|
K_WLOCK(logqueue_free); |
|
k_add_head(logqueue_free, lq_item); |
|
if (!everyone_die) |
|
lq_item = k_unlink_head(logqueue_store); |
|
else |
|
lq_item = NULL; |
|
K_WUNLOCK(logqueue_free); |
|
} |
|
cksleep_ms(42); |
|
} |
|
|
|
K_WLOCK(logqueue_free); |
|
setnow(&now); |
|
snprintf(buf, sizeof(buf), "logstopping.%d.%ld,%ld", |
|
logqueue_store->count, |
|
now.tv_sec, now.tv_usec); |
|
LOGFILE(buf); |
|
while((lq_item = k_unlink_head(logqueue_store))) { |
|
DATA_LOGQUEUE(lq, lq_item); |
|
LOGFILE(lq->msg); |
|
} |
|
K_WUNLOCK(logqueue_free); |
|
|
|
setnow(&now); |
|
snprintf(buf, sizeof(buf), "logstop.%ld,%ld", |
|
now.tv_sec, now.tv_usec); |
|
LOGFILE(buf); |
|
|
|
return NULL; |
|
} |
|
|
|
#define STORELASTREPLY(_cmd) do { \ |
|
if (last_ ## _cmd) \ |
|
free(last_ ## _cmd); \ |
|
last_ ## _cmd = buf; \ |
|
buf = NULL; \ |
|
if (reply_ ## _cmd) \ |
|
free(reply_ ## _cmd); \ |
|
reply_ ## _cmd = rep; \ |
|
} while (0) |
|
|
|
static void *socketer(__maybe_unused void *arg) |
|
{ |
|
proc_instance_t *pi = (proc_instance_t *)arg; |
|
unixsock_t *us = &pi->us; |
|
char *end, *ans = NULL, *rep = NULL, *buf = NULL, *dot; |
|
char cmd[CMD_SIZ+1], id[ID_SIZ+1], reply[1024+1]; |
|
char *last_auth = NULL, *reply_auth = NULL; |
|
char *last_addrauth = NULL, *reply_addrauth = NULL; |
|
char *last_chkpass = NULL, *reply_chkpass = NULL; |
|
char *last_adduser = NULL, *reply_adduser = NULL; |
|
char *last_newpass = NULL, *reply_newpass = NULL; |
|
char *last_userset = NULL, *reply_userset = NULL; |
|
char *last_newid = NULL, *reply_newid = NULL; |
|
char *last_setatts = NULL, *reply_setatts = NULL; |
|
char *last_web = NULL, *reply_web = NULL; |
|
char *reply_last, duptype[CMD_SIZ+1]; |
|
enum cmd_values cmdnum; |
|
int sockd, which_cmds; |
|
WORKQUEUE *workqueue; |
|
TRANSFER *transfer; |
|
K_STORE *trf_store; |
|
K_TREE *trf_root; |
|
K_ITEM *item; |
|
size_t siz; |
|
tv_t now, cd; |
|
bool dup, want_first; |
|
int loglevel, oldloglevel; |
|
|
|
pthread_detach(pthread_self()); |
|
|
|
rename_proc("db_socketer"); |
|
|
|
while (!everyone_die && !db_auths_complete) |
|
cksem_mswait(&socketer_sem, 420); |
|
|
|
want_first = true; |
|
while (!everyone_die) { |
|
if (buf) |
|
dealloc(buf); |
|
sockd = accept(us->sockd, NULL, NULL); |
|
if (sockd < 0) { |
|
LOGERR("Failed to accept on socket in listener"); |
|
break; |
|
} |
|
|
|
cmdnum = CMD_UNSET; |
|
trf_root = NULL; |
|
trf_store = NULL; |
|
|
|
buf = recv_unix_msg(sockd); |
|
// Once we've read the message |
|
setnow(&now); |
|
if (buf) { |
|
end = buf + strlen(buf) - 1; |
|
// strip trailing \n and \r |
|
while (end >= buf && (*end == '\n' || *end == '\r')) |
|
*(end--) = '\0'; |
|
} |
|
if (!buf || !*buf) { |
|
// An empty message wont get a reply |
|
if (!buf) |
|
LOGWARNING("Failed to get message in listener"); |
|
else |
|
LOGWARNING("Empty message in listener"); |
|
} else { |
|
/* For duplicates: |
|
* Queued pool messages are handled by the queue code |
|
* but since they reply ok.queued that message can |
|
* be returned every time here |
|
* System: repeat process them |
|
* Web: current php web sends a timestamp of seconds |
|
* so duplicate code will only trigger if the same |
|
* message is sent within the same second and thus |
|
* will effectively reduce the processing load for |
|
* sequential duplicates |
|
* As per the 'if' list below, |
|
* remember individual last messages and replies and |
|
* repeat the reply without reprocessing the message |
|
* The rest are remembered in the same buffer 'web' |
|
* so a duplicate will not be seen if another 'web' |
|
* command arrived between two duplicate commands |
|
*/ |
|
dup = false; |
|
// These are ordered approximately most likely first |
|
if (last_auth && strcmp(last_auth, buf) == 0) { |
|
reply_last = reply_auth; |
|
dup = true; |
|
} else if (last_chkpass && strcmp(last_chkpass, buf) == 0) { |
|
reply_last = reply_chkpass; |
|
dup = true; |
|
} else if (last_adduser && strcmp(last_adduser, buf) == 0) { |
|
reply_last = reply_adduser; |
|
dup = true; |
|
} else if (last_newpass && strcmp(last_newpass, buf) == 0) { |
|
reply_last = reply_newpass; |
|
dup = true; |
|
} else if (last_newid && strcmp(last_newid, buf) == 0) { |
|
reply_last = reply_newid; |
|
dup = true; |
|
} else if (last_addrauth && strcmp(last_addrauth, buf) == 0) { |
|
reply_last = reply_addrauth; |
|
dup = true; |
|
} else if (last_userset && strcmp(last_userset, buf) == 0) { |
|
reply_last = reply_userset; |
|
dup = true; |
|
} else if (last_setatts && strcmp(last_setatts, buf) == 0) { |
|
reply_last = reply_setatts; |
|
dup = true; |
|
} else if (last_web && strcmp(last_web, buf) == 0) { |
|
reply_last = reply_web; |
|
dup = true; |
|
} |
|
if (dup) { |
|
send_unix_msg(sockd, reply_last); |
|
STRNCPY(duptype, buf); |
|
dot = strchr(duptype, '.'); |
|
if (dot) |
|
*dot = '\0'; |
|
snprintf(reply, sizeof(reply), "%s%ld,%ld.%s", |
|
LOGDUP, now.tv_sec, now.tv_usec, duptype); |
|
LOGQUE(reply); |
|
LOGWARNING("Duplicate '%s' message received", duptype); |
|
} else { |
|
LOGQUE(buf); |
|
cmdnum = breakdown(&trf_root, &trf_store, buf, &which_cmds, cmd, id, &cd); |
|
switch (cmdnum) { |
|
case CMD_REPLY: |
|
snprintf(reply, sizeof(reply), "%s.%ld.?.", id, now.tv_sec); |
|
send_unix_msg(sockd, reply); |
|
break; |
|
case CMD_SHUTDOWN: |
|
LOGWARNING("Listener received shutdown message, terminating ckdb"); |
|
snprintf(reply, sizeof(reply), "%s.%ld.ok.exiting", id, now.tv_sec); |
|
send_unix_msg(sockd, reply); |
|
everyone_die = true; |
|
break; |
|
case CMD_PING: |
|
LOGDEBUG("Listener received ping request"); |
|
snprintf(reply, sizeof(reply), "%s.%ld.ok.pong", id, now.tv_sec); |
|
send_unix_msg(sockd, reply); |
|
break; |
|
case CMD_VERSION: |
|
LOGDEBUG("Listener received version request"); |
|
snprintf(reply, sizeof(reply), |
|
"%s.%ld.ok.CKDB V%s", |
|
id, now.tv_sec, CKDB_VERSION); |
|
send_unix_msg(sockd, reply); |
|
break; |
|
case CMD_LOGLEVEL: |
|
if (!*id) { |
|
LOGDEBUG("Listener received loglevel, currently %d", |
|
pi->ckp->loglevel); |
|
snprintf(reply, sizeof(reply), |
|
"%s.%ld.ok.loglevel currently %d", |
|
id, now.tv_sec, |
|
pi->ckp->loglevel); |
|
} else { |
|
oldloglevel = pi->ckp->loglevel; |
|
loglevel = atoi(id); |
|
LOGDEBUG("Listener received loglevel %d currently %d A", |
|
loglevel, oldloglevel); |
|
if (loglevel < LOG_EMERG || loglevel > LOG_DEBUG) { |
|
snprintf(reply, sizeof(reply), |
|
"%s.%ld.ERR.invalid loglevel %d" |
|
" - currently %d", |
|
id, now.tv_sec, |
|
loglevel, oldloglevel); |
|
} else { |
|
pi->ckp->loglevel = loglevel; |
|
snprintf(reply, sizeof(reply), |
|
"%s.%ld.ok.loglevel now %d - was %d", |
|
id, now.tv_sec, |
|
pi->ckp->loglevel, oldloglevel); |
|
} |
|
// Do this twice since the loglevel may have changed |
|
LOGDEBUG("Listener received loglevel %d currently %d B", |
|
loglevel, oldloglevel); |
|
} |
|
send_unix_msg(sockd, reply); |
|
break; |
|
// Always process immediately: |
|
case CMD_AUTH: |
|
case CMD_ADDRAUTH: |
|
// First message from the pool |
|
if (want_first) { |
|
ck_wlock(&fpm_lock); |
|
first_pool_message = strdup(buf); |
|
ck_wunlock(&fpm_lock); |
|
want_first = false; |
|
} |
|
case CMD_CHKPASS: |
|
case CMD_ADDUSER: |
|
case CMD_NEWPASS: |
|
case CMD_USERSET: |
|
case CMD_GETATTS: |
|
case CMD_SETATTS: |
|
case CMD_BLOCKLIST: |
|
case CMD_NEWID: |
|
case CMD_STATS: |
|
ans = cmds[which_cmds].func(NULL, cmd, id, &now, |
|
by_default, |
|
(char *)__func__, |
|
inet_default, |
|
&cd, trf_root); |
|
siz = strlen(ans) + strlen(id) + 32; |
|
rep = malloc(siz); |
|
snprintf(rep, siz, "%s.%ld.%s", id, now.tv_sec, ans); |
|
send_unix_msg(sockd, rep); |
|
free(ans); |
|
ans = NULL; |
|
switch (cmdnum) { |
|
case CMD_AUTH: |
|
STORELASTREPLY(auth); |
|
break; |
|
case CMD_ADDRAUTH: |
|
STORELASTREPLY(addrauth); |
|
break; |
|
case CMD_CHKPASS: |
|
STORELASTREPLY(chkpass); |
|
break; |
|
case CMD_ADDUSER: |
|
STORELASTREPLY(adduser); |
|
break; |
|
case CMD_NEWPASS: |
|
STORELASTREPLY(newpass); |
|
break; |
|
case CMD_USERSET: |
|
STORELASTREPLY(userset); |
|
break; |
|
case CMD_NEWID: |
|
STORELASTREPLY(newid); |
|
break; |
|
case CMD_SETATTS: |
|
STORELASTREPLY(setatts); |
|
break; |
|
// The rest |
|
default: |
|
free(rep); |
|
} |
|
rep = NULL; |
|
break; |
|
// Process, but reject (loading) until startup_complete |
|
case CMD_HOMEPAGE: |
|
case CMD_ALLUSERS: |
|
case CMD_WORKERS: |
|
case CMD_PAYMENTS: |
|
case CMD_PPLNS: |
|
case CMD_DSP: |
|
case CMD_BLOCKSTATUS: |
|
if (!startup_complete) { |
|
snprintf(reply, sizeof(reply), |
|
"%s.%ld.loading.%s", |
|
id, now.tv_sec, cmd); |
|
send_unix_msg(sockd, reply); |
|
} else { |
|
ans = cmds[which_cmds].func(NULL, cmd, id, &now, |
|
by_default, |
|
(char *)__func__, |
|
inet_default, |
|
&cd, trf_root); |
|
siz = strlen(ans) + strlen(id) + 32; |
|
rep = malloc(siz); |
|
snprintf(rep, siz, "%s.%ld.%s", id, now.tv_sec, ans); |
|
send_unix_msg(sockd, rep); |
|
free(ans); |
|
ans = NULL; |
|
if (cmdnum == CMD_DSP) |
|
free(rep); |
|
else { |
|
if (last_web) |
|
free(last_web); |
|
last_web = buf; |
|
buf = NULL; |
|
if (reply_web) |
|
free(reply_web); |
|
reply_web = rep; |
|
} |
|
rep = NULL; |
|
} |
|
break; |
|
// Always queue (ok.queued) |
|
case CMD_SHARELOG: |
|
case CMD_POOLSTAT: |
|
case CMD_USERSTAT: |
|
case CMD_BLOCK: |
|
// First message from the pool |
|
if (want_first) { |
|
ck_wlock(&fpm_lock); |
|
first_pool_message = strdup(buf); |
|
ck_wunlock(&fpm_lock); |
|
want_first = false; |
|
} |
|
|
|
snprintf(reply, sizeof(reply), |
|
"%s.%ld.ok.queued", |
|
id, now.tv_sec); |
|
send_unix_msg(sockd, reply); |
|
|
|
K_WLOCK(workqueue_free); |
|
item = k_unlink_head(workqueue_free); |
|
K_WUNLOCK(workqueue_free); |
|
|
|
DATA_WORKQUEUE(workqueue, item); |
|
workqueue->buf = buf; |
|
buf = NULL; |
|
workqueue->which_cmds = which_cmds; |
|
workqueue->cmdnum = cmdnum; |
|
STRNCPY(workqueue->cmd, cmd); |
|
STRNCPY(workqueue->id, id); |
|
copy_tv(&(workqueue->now), &now); |
|
STRNCPY(workqueue->by, by_default); |
|
STRNCPY(workqueue->code, __func__); |
|
STRNCPY(workqueue->inet, inet_default); |
|
copy_tv(&(workqueue->cd), &cd); |
|
workqueue->trf_root = trf_root; |
|
trf_root = NULL; |
|
workqueue->trf_store = trf_store; |
|
trf_store = NULL; |
|
|
|
K_WLOCK(workqueue_free); |
|
k_add_tail(workqueue_store, item); |
|
K_WUNLOCK(workqueue_free); |
|
mutex_lock(&wq_waitlock); |
|
pthread_cond_signal(&wq_waitcond); |
|
mutex_unlock(&wq_waitlock); |
|
break; |
|
// Code error |
|
default: |
|
LOGEMERG("%s() CODE ERROR unhandled message %d %.32s...", |
|
__func__, cmdnum, buf); |
|
snprintf(reply, sizeof(reply), |
|
"%s.%ld.failed.code", |
|
id, now.tv_sec); |
|
send_unix_msg(sockd, reply); |
|
break; |
|
} |
|
} |
|
} |
|
close(sockd); |
|
|
|
tick(); |
|
|
|
if (trf_root) |
|
trf_root = free_ktree(trf_root, NULL); |
|
if (trf_store) { |
|
item = trf_store->head; |
|
while (item) { |
|
DATA_TRANSFER(transfer, item); |
|
if (transfer->mvalue != transfer->svalue) |
|
free(transfer->mvalue); |
|
item = item->next; |
|
} |
|
K_WLOCK(transfer_free); |
|
k_list_transfer_to_head(trf_store, transfer_free); |
|
trf_store = k_free_store(trf_store); |
|
if (transfer_free->count == transfer_free->total && |
|
transfer_free->total > ALLOC_TRANSFER * CULL_TRANSFER) |
|
k_cull_list(transfer_free); |
|
K_WUNLOCK(transfer_free); |
|
} |
|
} |
|
|
|
if (buf) |
|
dealloc(buf); |
|
// TODO: if anyone cares, free all the dup buffers :P |
|
close_unix_socket(us->sockd, us->path); |
|
|
|
return NULL; |
|
} |
|
|
|
static bool reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) |
|
{ |
|
char cmd[CMD_SIZ+1], id[ID_SIZ+1]; |
|
enum cmd_values cmdnum; |
|
char *end, *ans; |
|
int which_cmds; |
|
K_STORE *trf_store = NULL; |
|
K_TREE *trf_root = NULL; |
|
TRANSFER *transfer; |
|
K_ITEM *item; |
|
tv_t now, cd; |
|
bool finished; |
|
|
|
// Once we've read the message |
|
setnow(&now); |
|
if (buf) { |
|
end = buf + strlen(buf) - 1; |
|
// strip trailing \n and \r |
|
while (end >= buf && (*end == '\n' || *end == '\r')) |
|
*(end--) = '\0'; |
|
} |
|
if (!buf || !*buf) { |
|
if (!buf) |
|
LOGERR("%s() NULL message line %"PRIu64, __func__, count); |
|
else |
|
LOGERR("%s() Empty message line %"PRIu64, __func__, count); |
|
} else { |
|
finished = false; |
|
ck_wlock(&fpm_lock); |
|
if (first_pool_message && strcmp(first_pool_message, buf) == 0) |
|
finished = true; |
|
ck_wunlock(&fpm_lock); |
|
if (finished) { |
|
LOGERR("%s() reload completed, ckpool queue match at line %"PRIu64, __func__, count); |
|
return true; |
|
} |
|
|
|
LOGQUE(buf); |
|
cmdnum = breakdown(&trf_root, &trf_store, buf, &which_cmds, cmd, id, &cd); |
|
switch (cmdnum) { |
|
// Ignore |
|
case CMD_REPLY: |
|
break; |
|
// Shouldn't be there |
|
case CMD_SHUTDOWN: |
|
case CMD_PING: |
|
case CMD_VERSION: |
|
case CMD_LOGLEVEL: |
|
// Non pool commands, shouldn't be there |
|
case CMD_ADDUSER: |
|
case CMD_NEWPASS: |
|
case CMD_CHKPASS: |
|
case CMD_USERSET: |
|
case CMD_BLOCKLIST: |
|
case CMD_BLOCKSTATUS: |
|
case CMD_NEWID: |
|
case CMD_PAYMENTS: |
|
case CMD_WORKERS: |
|
case CMD_ALLUSERS: |
|
case CMD_HOMEPAGE: |
|
case CMD_GETATTS: |
|
case CMD_SETATTS: |
|
case CMD_DSP: |
|
case CMD_STATS: |
|
case CMD_PPLNS: |
|
LOGERR("%s() Message line %"PRIu64" '%s' - invalid - ignored", |
|
__func__, count, cmd); |
|
break; |
|
case CMD_AUTH: |
|
case CMD_ADDRAUTH: |
|
case CMD_POOLSTAT: |
|
case CMD_USERSTAT: |
|
case CMD_BLOCK: |
|
if (confirm_sharesummary) |
|
break; |
|
case CMD_SHARELOG: |
|
ans = cmds[which_cmds].func(conn, cmd, id, &now, |
|
by_default, |
|
(char *)__func__, |
|
inet_default, |
|
&cd, trf_root); |
|
if (ans) |
|
free(ans); |
|
break; |
|
default: |
|
// Force this switch to be updated if new cmds are added |
|
quithere(1, "%s line %"PRIu64" '%s' - not handled by reload", |
|
filename, count, cmd); |
|
break; |
|
} |
|
|
|
if (trf_root) |
|
trf_root = free_ktree(trf_root, NULL); |
|
if (trf_store) { |
|
item = trf_store->head; |
|
while (item) { |
|
DATA_TRANSFER(transfer, item); |
|
if (transfer->mvalue != transfer->svalue) |
|
free(transfer->mvalue); |
|
item = item->next; |
|
} |
|
K_WLOCK(transfer_free); |
|
k_list_transfer_to_head(trf_store, transfer_free); |
|
K_WUNLOCK(transfer_free); |
|
trf_store = k_free_store(trf_store); |
|
} |
|
} |
|
|
|
tick(); |
|
|
|
return false; |
|
} |
|
|
|
// 10Mb for now - transactiontree can be large |
|
#define MAX_READ (10 * 1024 * 1024) |
|
static char *reload_buf; |
|
|
|
/* If the reload start file is missing and -r was specified correctly: |
|
* touch the filename reported in "Failed to open 'filename'", |
|
* if ckdb aborts at the beginning of the reload, then start again */ |
|
static bool reload_from(tv_t *start) |
|
{ |
|
PGconn *conn = NULL; |
|
char buf[DATE_BUFSIZ+1], run[DATE_BUFSIZ+1]; |
|
size_t rflen = strlen(restorefrom); |
|
char *missingfirst = NULL, *missinglast = NULL; |
|
int missing_count; |
|
int processing; |
|
bool finished = false, matched = false, ret = true; |
|
char *filename = NULL; |
|
uint64_t count, total; |
|
tv_t now; |
|
FILE *fp = NULL; |
|
|
|
reload_buf = malloc(MAX_READ); |
|
if (!reload_buf) |
|
quithere(1, "OOM"); |
|
|
|
reloading = true; |
|
|
|
copy_tv(&reload_timestamp, start); |
|
reload_timestamp.tv_sec -= reload_timestamp.tv_sec % ROLL_S; |
|
|
|
tv_to_buf(start, buf, sizeof(buf)); |
|
tv_to_buf(&reload_timestamp, run, sizeof(run)); |
|
LOGWARNING("%s(): from %s (stamp %s)", __func__, buf, run); |
|
|
|
filename = rotating_filename(restorefrom, reload_timestamp.tv_sec); |
|
fp = fopen(filename, "re"); |
|
if (!fp) |
|
quithere(1, "Failed to open '%s'", filename); |
|
|
|
setnow(&now); |
|
tvs_to_buf(&now, run, sizeof(run)); |
|
snprintf(reload_buf, MAX_READ, "reload.%s.s0", run); |
|
LOGQUE(reload_buf); |
|
|
|
conn = dbconnect(); |
|
|
|
total = 0; |
|
processing = 0; |
|
while (!everyone_die && !finished) { |
|
LOGWARNING("%s(): processing %s", __func__, filename); |
|
processing++; |
|
count = 0; |
|
|
|
while (!everyone_die && !matched && fgets_unlocked(reload_buf, MAX_READ, fp)) |
|
matched = reload_line(conn, filename, ++count, reload_buf); |
|
|
|
if (ferror(fp)) { |
|
int err = errno; |
|
quithere(1, "Read failed on %s (%d) '%s'", |
|
filename, err, strerror(err)); |
|
} |
|
|
|
LOGWARNING("%s(): %sread %"PRIu64" line%s from %s", |
|
__func__, |
|
everyone_die ? "Shutdown, aborting - " : "", |
|
count, count == 1 ? "" : "s", |
|
filename); |
|
total += count; |
|
fclose(fp); |
|
free(filename); |
|
if (everyone_die || matched) |
|
break; |
|
reload_timestamp.tv_sec += ROLL_S; |
|
if (confirm_sharesummary && tv_newer(&confirm_finish, &reload_timestamp)) { |
|
LOGWARNING("%s(): confirm range complete", __func__); |
|
break; |
|
} |
|
filename = rotating_filename(restorefrom, reload_timestamp.tv_sec); |
|
fp = fopen(filename, "re"); |
|
if (!fp) { |
|
missingfirst = strdup(filename); |
|
free(filename); |
|
filename = NULL; |
|
errno = 0; |
|
missing_count = 1; |
|
setnow(&now); |
|
now.tv_sec += ROLL_S; |
|
while (42) { |
|
reload_timestamp.tv_sec += ROLL_S; |
|
/* WARNING: if the system clock is wrong, any CCLs |
|
* missing or not created due to a ckpool outage of |
|
* an hour or more can stop the reload early and |
|
* cause DB problems! Though, the clock being wrong |
|
* can screw up ckpool and ckdb anyway ... */ |
|
if (!tv_newer(&reload_timestamp, &now)) { |
|
finished = true; |
|
break; |
|
} |
|
filename = rotating_filename(restorefrom, reload_timestamp.tv_sec); |
|
fp = fopen(filename, "re"); |
|
if (fp) |
|
break; |
|
errno = 0; |
|
if (missing_count++ > 1) |
|
free(missinglast); |
|
missinglast = strdup(filename); |
|
free(filename); |
|
filename = NULL; |
|
} |
|
if (missing_count == 1) |
|
LOGWARNING("%s(): skipped %s", __func__, missingfirst+rflen); |
|
else { |
|
LOGWARNING("%s(): skipped %d files from %s to %s", |
|
__func__, missing_count, missingfirst+rflen, missinglast+rflen); |
|
free(missinglast); |
|
missinglast = NULL; |
|
} |
|
free(missingfirst); |
|
missingfirst = NULL; |
|
} |
|
} |
|
|
|
PQfinish(conn); |
|
|
|
snprintf(reload_buf, MAX_READ, "reload.%s.%"PRIu64, run, total); |
|
LOGQUE(reload_buf); |
|
LOGWARNING("%s(): read %d file%s, total %"PRIu64" line%s", |
|
__func__, |
|
processing, processing == 1 ? "" : "s", |
|
total, total == 1 ? "" : "s"); |
|
|
|
if (everyone_die) |
|
return true; |
|
|
|
if (!matched) { |
|
ck_wlock(&fpm_lock); |
|
if (first_pool_message) { |
|
LOGERR("%s() reload completed without finding ckpool queue match '%.32s'...", |
|
__func__, first_pool_message); |
|
LOGERR("%s() restart ckdb to resolve this", __func__); |
|
ret = false; |
|
} |
|
ck_wunlock(&fpm_lock); |
|
} |
|
|
|
reloading = false; |
|
|
|
free(reload_buf); |
|
reload_buf = NULL; |
|
|
|
return ret; |
|
} |
|
|
|
static void process_queued(PGconn *conn, K_ITEM *wq_item) |
|
{ |
|
static char *last_buf = NULL; |
|
WORKQUEUE *workqueue; |
|
TRANSFER *transfer; |
|
K_ITEM *item; |
|
char *ans; |
|
|
|
DATA_WORKQUEUE(workqueue, wq_item); |
|
|
|
// Simply ignore the (very rare) duplicates |
|
if (!last_buf || strcmp(workqueue->buf, last_buf)) { |
|
ans = cmds[workqueue->which_cmds].func(conn, workqueue->cmd, workqueue->id, |
|
&(workqueue->now), workqueue->by, |
|
workqueue->code, workqueue->inet, |
|
&(workqueue->cd), workqueue->trf_root); |
|
free(ans); |
|
} |
|
|
|
if (last_buf) |
|
free(last_buf); |
|
last_buf = workqueue->buf; |
|
|
|
workqueue->trf_root = free_ktree(workqueue->trf_root, NULL); |
|
item = workqueue->trf_store->head; |
|
while (item) { |
|
DATA_TRANSFER(transfer, item); |
|
if (transfer->mvalue != transfer->svalue) |
|
free(transfer->mvalue); |
|
item = item->next; |
|
} |
|
K_WLOCK(transfer_free); |
|
k_list_transfer_to_head(workqueue->trf_store, transfer_free); |
|
K_WUNLOCK(transfer_free); |
|
workqueue->trf_store = k_free_store(workqueue->trf_store); |
|
|
|
K_WLOCK(workqueue_free); |
|
k_add_head(workqueue_free, wq_item); |
|
if (workqueue_free->count == workqueue_free->total && |
|
workqueue_free->total > ALLOC_WORKQUEUE * CULL_WORKQUEUE) |
|
k_cull_list(workqueue_free); |
|
K_WUNLOCK(workqueue_free); |
|
} |
|
|
|
// TODO: equivalent of api_allow |
|
static void *listener(void *arg) |
|
{ |
|
PGconn *conn = NULL; |
|
pthread_t log_pt; |
|
pthread_t sock_pt; |
|
pthread_t summ_pt; |
|
K_ITEM *wq_item; |
|
int qc; |
|
|
|
logqueue_free = k_new_list("LogQueue", sizeof(LOGQUEUE), |
|
ALLOC_LOGQUEUE, LIMIT_LOGQUEUE, true); |
|
logqueue_store = k_new_store(logqueue_free); |
|
|
|
create_pthread(&log_pt, logger, NULL); |
|
|
|
create_pthread(&sock_pt, socketer, arg); |
|
|
|
create_pthread(&summ_pt, summariser, NULL); |
|
|
|
rename_proc("db_listener"); |
|
|
|
if (!setup_data()) { |
|
if (!everyone_die) { |
|
LOGEMERG("ABORTING"); |
|
everyone_die = true; |
|
} |
|
return NULL; |
|
} |
|
|
|
if (!everyone_die) { |
|
K_RLOCK(workqueue_store); |
|
qc = workqueue_store->count; |
|
K_RUNLOCK(workqueue_store); |
|
|
|
LOGWARNING("%s(): ckdb ready, queue %d", __func__, qc); |
|
|
|
startup_complete = true; |
|
} |
|
|
|
if (!everyone_die) |
|
conn = dbconnect(); |
|
|
|
// Process queued work |
|
while (!everyone_die) { |
|
K_WLOCK(workqueue_store); |
|
wq_item = k_unlink_head(workqueue_store); |
|
K_WUNLOCK(workqueue_store); |
|
if (wq_item) { |
|
process_queued(conn, wq_item); |
|
tick(); |
|
} else { |
|
const ts_t tsdiff = {0, 420000000}; |
|
tv_t now; |
|
ts_t abs; |
|
|
|
tv_time(&now); |
|
tv_to_ts(&abs, &now); |
|
timeraddspec(&abs, &tsdiff); |
|
|
|
mutex_lock(&wq_waitlock); |
|
pthread_cond_timedwait(&wq_waitcond, &wq_waitlock, &abs); |
|
mutex_unlock(&wq_waitlock); |
|
} |
|
} |
|
|
|
if (conn) |
|
PQfinish(conn); |
|
|
|
return NULL; |
|
} |
|
|
|
/* TODO: This will be way faster traversing both trees simultaneously |
|
* rather than traversing one and searching the other, then repeating |
|
* in reverse. Will change it later */ |
|
static void compare_summaries(K_TREE *leftsum, char *leftname, |
|
K_TREE *rightsum, char *rightname, |
|
bool show_missing, bool show_diff) |
|
{ |
|
K_TREE_CTX ctxl[1], ctxr[1]; |
|
K_ITEM look, *lss, *rss; |
|
char cd_buf[DATE_BUFSIZ]; |
|
SHARESUMMARY looksharesummary, *l_ss, *r_ss; |
|
uint64_t total, ok, missing, diff; |
|
uint64_t first_used = 0, last_used = 0; |
|
int64_t miss_first = 0, miss_last = 0; |
|
tv_t miss_first_cd = {0,0}, miss_last_cd = {0,0}; |
|
int64_t diff_first = 0, diff_last = 0; |
|
tv_t diff_first_cd = {0,0}, diff_last_cd = {0,0}; |
|
char cd_buf1[DATE_BUFSIZ], cd_buf2[DATE_BUFSIZ]; |
|
|
|
looksharesummary.workinfoid = confirm_first_workinfoid; |
|
looksharesummary.userid = -1; |
|
looksharesummary.workername[0] = '\0'; |
|
INIT_SHARESUMMARY(&look); |
|
look.data = (void *)(&looksharesummary); |
|
|
|
total = ok = missing = diff = 0; |
|
lss = find_after_in_ktree(leftsum, &look, cmp_sharesummary_workinfoid, ctxl); |
|
while (lss) { |
|
DATA_SHARESUMMARY(l_ss, lss); |
|
if (l_ss->workinfoid > confirm_last_workinfoid) |
|
break; |
|
|
|
total++; |
|
|
|
if (first_used == 0) |
|
first_used = l_ss->workinfoid; |
|
last_used = l_ss->workinfoid; |
|
|
|
rss = find_in_ktree(rightsum, lss, cmp_sharesummary_workinfoid, ctxr); |
|
DATA_SHARESUMMARY_NULL(r_ss, rss); |
|
if (!rss) { |
|
missing++; |
|
if (miss_first == 0) { |
|
miss_first = l_ss->workinfoid; |
|
copy_tv(&miss_first_cd, &(l_ss->createdate)); |
|
} |
|
miss_last = l_ss->workinfoid; |
|
copy_tv(&miss_last_cd, &(l_ss->createdate)); |
|
if (show_missing) { |
|
LOGERR("ERROR: %s %"PRId64"/%s/%ld,%ld %.19s missing from %s", |
|
leftname, |
|
l_ss->workinfoid, |
|
l_ss->workername, |
|
l_ss->createdate.tv_sec, |
|
l_ss->createdate.tv_usec, |
|
tv_to_buf(&(l_ss->createdate), cd_buf, sizeof(cd_buf)), |
|
rightname); |
|
} |
|
} else if (r_ss->diffacc != l_ss->diffacc) { |
|
diff++; |
|
if (show_diff) { |
|
if (diff_first == 0) { |
|
diff_first = l_ss->workinfoid; |
|
copy_tv(&diff_first_cd, &(l_ss->createdate)); |
|
} |
|
diff_last = l_ss->workinfoid; |
|
copy_tv(&diff_last_cd, &(l_ss->createdate)); |
|
LOGERR("ERROR: %"PRId64"/%s/%ld,%ld %.19s - diffacc: %s: %.0f %s: %.0f", |
|
l_ss->workinfoid, |
|
l_ss->workername, |
|
l_ss->createdate.tv_sec, |
|
l_ss->createdate.tv_usec, |
|
tv_to_buf(&(l_ss->createdate), cd_buf, sizeof(cd_buf)), |
|
leftname, |
|
l_ss->diffacc, |
|
rightname, |
|
r_ss->diffacc); |
|
} |
|
} else |
|
ok++; |
|
|
|
lss = next_in_ktree(ctxl); |
|
} |
|
|
|
LOGERR("RESULT: %s->%s Total %"PRIu64" workinfoid %"PRId64"-%"PRId64 |
|
" %s missing: %"PRIu64" different: %"PRIu64, |
|
leftname, rightname, total, first_used, last_used, |
|
rightname, missing, diff); |
|
if (miss_first) { |
|
tv_to_buf(&miss_first_cd, cd_buf1, sizeof(cd_buf1)); |
|
tv_to_buf(&miss_last_cd, cd_buf2, sizeof(cd_buf2)); |
|
LOGERR(" workinfoid range for missing: %"PRId64"-%"PRId64 |
|
" (%s .. %s)", |
|
miss_first, miss_last, cd_buf1, cd_buf2); |
|
} |
|
if (show_diff && diff_first) { |
|
tv_to_buf(&diff_first_cd, cd_buf1, sizeof(cd_buf1)); |
|
tv_to_buf(&diff_last_cd, cd_buf2, sizeof(cd_buf2)); |
|
LOGERR(" workinfoid range for differences: %"PRId64"-%"PRId64 |
|
" (%s .. %s)", |
|
diff_first, diff_last, cd_buf1, cd_buf2); |
|
} |
|
} |
|
|
|
static void confirm_reload() |
|
{ |
|
K_TREE *sharesummary_workinfoid_save; |
|
__maybe_unused K_TREE *sharesummary_save; |
|
__maybe_unused K_TREE *workinfo_save; |
|
K_ITEM b_look, wi_look, *wi_item, *wif_item, *wil_item; |
|
K_ITEM *b_begin_item, *b_end_item; |
|
K_ITEM *ss_begin_item, *ss_end_item; |
|
WORKINFO lookworkinfo, *workinfo; |
|
BLOCKS lookblocks, *b_blocks, *e_blocks; |
|
SHARESUMMARY *b_ss, *e_ss; |
|
K_TREE_CTX ctx[1]; |
|
char buf[DATE_BUFSIZ+1]; |
|
char *first_reason; |
|
char *last_reason; |
|
char cd_buf[DATE_BUFSIZ]; |
|
char first_buf[64], last_buf[64]; |
|
char *filename; |
|
tv_t start; |
|
FILE *fp; |
|
|
|
// TODO: // abort reload when we get an age after the end of a workinfo after the Xs after the last workinfo before the end |
|
|
|
wif_item = first_in_ktree(workinfo_root, ctx); |
|
wil_item = last_in_ktree(workinfo_root, ctx); |
|
|
|
if (!wif_item || !wil_item) { |
|
LOGWARNING("%s(): DB contains no workinfo records", __func__); |
|
return; |
|
} |
|
|
|
DATA_WORKINFO(workinfo, wif_item); |
|
tv_to_buf(&(workinfo->createdate), cd_buf, sizeof(cd_buf)); |
|
LOGWARNING("%s(): DB first workinfoid %"PRId64" %s", |
|
__func__, workinfo->workinfoid, cd_buf); |
|
|
|
DATA_WORKINFO(workinfo, wil_item); |
|
tv_to_buf(&(workinfo->createdate), cd_buf, sizeof(cd_buf)); |
|
LOGWARNING("%s(): DB last workinfoid %"PRId64" %s", |
|
__func__, workinfo->workinfoid, cd_buf); |
|
|
|
b_begin_item = first_in_ktree(blocks_root, ctx); |
|
b_end_item = last_in_ktree(blocks_root, ctx); |
|
|
|
if (!b_begin_item || !b_end_item) |
|
LOGWARNING("%s(): DB contains no blocks :(", __func__); |
|
else { |
|
DATA_BLOCKS(b_blocks, b_begin_item); |
|
DATA_BLOCKS(e_blocks, b_end_item); |
|
tv_to_buf(&(b_blocks->createdate), cd_buf, sizeof(cd_buf)); |
|
LOGWARNING("%s(): DB first block %d/%"PRId64" %s", |
|
__func__, |
|
b_blocks->height, |
|
b_blocks->workinfoid, |
|
cd_buf); |
|
tv_to_buf(&(e_blocks->createdate), |
|
cd_buf, sizeof(cd_buf)); |
|
LOGWARNING("%s(): DB last block %d/%"PRId64" %s", |
|
__func__, |
|
e_blocks->height, |
|
e_blocks->workinfoid, |
|
cd_buf); |
|
} |
|
|
|
ss_begin_item = first_in_ktree(sharesummary_workinfoid_root, ctx); |
|
ss_end_item = last_in_ktree(sharesummary_workinfoid_root, ctx); |
|
|
|
if (!ss_begin_item || !ss_end_item) |
|
LOGWARNING("%s(): DB contains no sharesummary records", __func__); |
|
else { |
|
DATA_SHARESUMMARY(b_ss, ss_begin_item); |
|
DATA_SHARESUMMARY(e_ss, ss_end_item); |
|
tv_to_buf(&(b_ss->createdate), cd_buf, sizeof(cd_buf)); |
|
LOGWARNING("%s(): DB first sharesummary %"PRId64"/%s %s", |
|
__func__, |
|
b_ss->workinfoid, |
|
b_ss->workername, |
|
cd_buf); |
|
tv_to_buf(&(e_ss->createdate), cd_buf, sizeof(cd_buf)); |
|
LOGWARNING("%s(): DB last sharesummary %"PRId64"/%s %s", |
|
__func__, |
|
e_ss->workinfoid, |
|
e_ss->workername, |
|
cd_buf); |
|
} |
|
|
|
/* The first workinfo we should process |
|
* With no y records we should start from the beginning (0) |
|
* With any y records, we should start from the oldest of: y+1 and a |
|
* which can produce y records as reload a's, if a is used */ |
|
if (dbstatus.newest_workinfoid_y > 0) { |
|
confirm_first_workinfoid = dbstatus.newest_workinfoid_y + 1; |
|
if (confirm_first_workinfoid > dbstatus.oldest_workinfoid_a) { |
|
confirm_first_workinfoid = dbstatus.oldest_workinfoid_a; |
|
first_reason = "oldest aged"; |
|
} else |
|
first_reason = "newest confirmed+1"; |
|
} else |
|
first_reason = "0 - none confirmed"; |
|
|
|
/* The last workinfo we should process |
|
* The reason for going past the last 'a' up to before |
|
* the first 'n' is in case there were shares missed between them - |
|
* but that should only be the case with a code bug - |
|
* so it checks that */ |
|
if (dbstatus.newest_workinfoid_a > 0) { |
|
confirm_last_workinfoid = dbstatus.newest_workinfoid_a; |
|
last_reason = "newest aged"; |
|
} |
|
if (confirm_last_workinfoid < dbstatus.oldest_workinfoid_n) { |
|
confirm_last_workinfoid = dbstatus.oldest_workinfoid_n - 1; |
|
last_reason = "oldest new-1"; |
|
} |
|
if (confirm_last_workinfoid == 0) { |
|
LOGWARNING("%s(): there are no unconfirmed sharesummary records in the DB", |
|
__func__, buf); |
|
return; |
|
} |
|
|
|
INIT_BLOCKS(&b_look); |
|
INIT_WORKINFO(&wi_look); |
|
|
|
// Do this after above code for checking and so we can use the results |
|
if (confirm_range && *confirm_range) { |
|
switch(tolower(confirm_range[0])) { |
|
case 'b': |
|
// First DB record of the block = or after confirm_block |
|
lookblocks.height = confirm_block; |
|
lookblocks.blockhash[0] = '\0'; |
|
b_look.data = (void *)(&lookblocks); |
|
b_end_item = find_after_in_ktree(blocks_root, &b_look, cmp_blocks, ctx); |
|
if (!b_end_item) { |
|
LOGWARNING("%s(): no DB block height found matching or after %d", |
|
__func__, confirm_block); |
|
return; |
|
} |
|
DATA_BLOCKS(e_blocks, b_end_item); |
|
confirm_last_workinfoid = e_blocks->workinfoid; |
|
|
|
// Now find the last DB record of the previous block |
|
lookblocks.height = e_blocks->height; |
|
lookblocks.blockhash[0] = '\0'; |
|
b_look.data = (void *)(&lookblocks); |
|
b_begin_item = find_before_in_ktree(blocks_root, &b_look, cmp_blocks, ctx); |
|
if (!b_begin_item) |
|
confirm_first_workinfoid = 0; |
|
else { |
|
DATA_BLOCKS(b_blocks, b_begin_item); |
|
// First DB record of the block 'begin' |
|
lookblocks.height = b_blocks->height; |
|
lookblocks.blockhash[0] = '\0'; |
|
b_look.data = (void *)(&lookblocks); |
|
b_begin_item = find_after_in_ktree(blocks_root, &b_look, cmp_blocks, ctx); |
|
// Not possible |
|
if (!b_begin_item) |
|
confirm_first_workinfoid = 0; |
|
else { |
|
DATA_BLOCKS(b_blocks, b_begin_item); |
|
confirm_first_workinfoid = b_blocks->workinfoid; |
|
} |
|
} |
|
snprintf(first_buf, sizeof(first_buf), |
|
"block %d", |
|
b_begin_item ? b_blocks->height : 0); |
|
first_reason = first_buf; |
|
snprintf(last_buf, sizeof(last_buf), |
|
"block %d", |
|
e_blocks->height); |
|
last_reason = last_buf; |
|
break; |
|
case 'i': |
|
LOGWARNING("%s(): info displayed - exiting", __func__); |
|
exit(0); |
|
case 'c': |
|
case 'r': |
|
confirm_first_workinfoid = confirm_range_start; |
|
confirm_last_workinfoid = confirm_range_finish; |
|
first_reason = "start range"; |
|
last_reason = "end range"; |
|
break; |
|
case 'w': |
|
confirm_first_workinfoid = confirm_range_start; |
|
// last from default |
|
if (confirm_last_workinfoid < confirm_first_workinfoid) { |
|
LOGWARNING("%s(): no unconfirmed sharesummary records before start", |
|
__func__, buf); |
|
return; |
|
} |
|
first_reason = "start range"; |
|
break; |
|
default: |
|
quithere(1, "Code fail"); |
|
} |
|
} |
|
|
|
/* These two below find the closest valid workinfo to the ones chosen |
|
* however we still use the original ones chosen to select/ignore data */ |
|
|
|
/* Find the workinfo before confirm_first_workinfoid+1 |
|
* i.e. the one we want or the previous before it */ |
|
lookworkinfo.workinfoid = confirm_first_workinfoid + 1; |
|
lookworkinfo.expirydate.tv_sec = date_begin.tv_sec; |
|
lookworkinfo.expirydate.tv_usec = date_begin.tv_usec; |
|
wi_look.data = (void *)(&lookworkinfo); |
|
wi_item = find_before_in_ktree(workinfo_root, &wi_look, cmp_workinfo, ctx); |
|
if (wi_item) { |
|
DATA_WORKINFO(workinfo, wi_item); |
|
copy_tv(&start, &(workinfo->createdate)); |
|
if (workinfo->workinfoid != confirm_first_workinfoid) { |
|
LOGWARNING("%s() start workinfo not found ... using time of %"PRId64, |
|
__func__, workinfo->workinfoid); |
|
} |
|
} else { |
|
start.tv_sec = start.tv_usec = 0; |
|
LOGWARNING("%s() no start workinfo found ... using time 0", __func__); |
|
} |
|
|
|
/* Find the workinfo after confirm_last_workinfoid-1 |
|
* i.e. the one we want or the next after it */ |
|
lookworkinfo.workinfoid = confirm_last_workinfoid - 1; |
|
lookworkinfo.expirydate.tv_sec = date_eot.tv_sec; |
|
lookworkinfo.expirydate.tv_usec = date_eot.tv_usec; |
|
wi_look.data = (void *)(&lookworkinfo); |
|
wi_item = find_after_in_ktree(workinfo_root, &wi_look, cmp_workinfo, ctx); |
|
if (wi_item) { |
|
DATA_WORKINFO(workinfo, wi_item); |
|
/* Now find the one after the one we found to determine the |
|
* confirm_finish timestamp */ |
|
lookworkinfo.workinfoid = workinfo->workinfoid; |
|
lookworkinfo.expirydate.tv_sec = date_eot.tv_sec; |
|
lookworkinfo.expirydate.tv_usec = date_eot.tv_usec; |
|
wi_look.data = (void *)(&lookworkinfo); |
|
wi_item = find_after_in_ktree(workinfo_root, &wi_look, cmp_workinfo, ctx); |
|
if (wi_item) { |
|
DATA_WORKINFO(workinfo, wi_item); |
|
copy_tv(&confirm_finish, &(workinfo->createdate)); |
|
confirm_finish.tv_sec += WORKINFO_AGE; |
|
} else { |
|
confirm_finish.tv_sec = date_eot.tv_sec; |
|
confirm_finish.tv_usec = date_eot.tv_usec; |
|
} |
|
} else { |
|
confirm_finish.tv_sec = date_eot.tv_sec; |
|
confirm_finish.tv_usec = date_eot.tv_usec; |
|
LOGWARNING("%s() no finish workinfo found ... using EOT", __func__); |
|
} |
|
|
|
LOGWARNING("%s() workinfo range: %"PRId64" to %"PRId64" ('%s' to '%s')", |
|
__func__, confirm_first_workinfoid, confirm_last_workinfoid, |
|
first_reason, last_reason); |
|
|
|
tv_to_buf(&start, buf, sizeof(buf)); |
|
LOGWARNING("%s() load start timestamp %s", __func__, buf); |
|
tv_to_buf(&confirm_finish, buf, sizeof(buf)); |
|
LOGWARNING("%s() load finish timestamp %s", __func__, buf); |
|
|
|
/* Save the DB info for comparing to the reload |
|
* i.e. the reload will generate from scratch all the |
|
* sharesummaries and workinfo from the CCLs */ |
|
sharesummary_workinfoid_save = sharesummary_workinfoid_root; |
|
sharesummary_save = sharesummary_root; |
|
workinfo_save = workinfo_root; |
|
|
|
sharesummary_workinfoid_root = new_ktree(); |
|
sharesummary_root = new_ktree(); |
|
workinfo_root = new_ktree(); |
|
|
|
if (start.tv_sec < DATE_BEGIN) { |
|
start.tv_sec = DATE_BEGIN; |
|
start.tv_usec = 0L; |
|
filename = rotating_filename(restorefrom, start.tv_sec); |
|
fp = fopen(filename, "re"); |
|
if (fp) |
|
fclose(fp); |
|
else { |
|
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; |
|
int fd = open(filename, O_CREAT|O_RDONLY, mode); |
|
if (fd == -1) { |
|
int ern = errno; |
|
quithere(1, "Couldn't create '%s' (%d) %s", |
|
filename, ern, strerror(ern)); |
|
} |
|
close(fd); |
|
} |
|
free(filename); |
|
} |
|
|
|
if (!reload_from(&start)) { |
|
LOGEMERG("%s() ABORTING from reload_from()", __func__); |
|
return; |
|
} |
|
|
|
if (confirm_check_createdate) { |
|
LOGERR("%s(): CCL mismatches %"PRId64"/%"PRId64" %.6f/%.6f unordered " |
|
"%"PRId64"/%"PRId64" %.6f", |
|
__func__, ccl_mismatch, ccl_mismatch_abs, |
|
ccl_mismatch_min, ccl_mismatch_max, |
|
ccl_unordered, ccl_unordered_abs, ccl_unordered_most); |
|
return; |
|
} |
|
|
|
compare_summaries(sharesummary_workinfoid_save, "DB", |
|
sharesummary_workinfoid_root, "ReLoad", |
|
true, true); |
|
compare_summaries(sharesummary_workinfoid_root, "ReLoad", |
|
sharesummary_workinfoid_save, "DB", |
|
true, false); |
|
} |
|
|
|
static void confirm_summaries() |
|
{ |
|
pthread_t log_pt; |
|
char *range, *minus; |
|
|
|
// Simple value check to abort early |
|
if (confirm_range && *confirm_range) { |
|
switch(tolower(confirm_range[0])) { |
|
case 'b': |
|
case 'c': |
|
case 'r': |
|
case 'w': |
|
if (strlen(confirm_range) < 2) { |
|
LOGEMERG("%s() invalid confirm range length '%s'", |
|
__func__, confirm_range); |
|
return; |
|
} |
|
break; |
|
case 'i': |
|
break; |
|
default: |
|
LOGEMERG("%s() invalid confirm range '%s'", |
|
__func__, confirm_range); |
|
return; |
|
} |
|
switch(tolower(confirm_range[0])) { |
|
case 'b': |
|
confirm_block = atoi(confirm_range+1); |
|
if (confirm_block <= 0) { |
|
LOGEMERG("%s() invalid confirm block '%s' - must be >0", |
|
__func__, confirm_range); |
|
return; |
|
} |
|
break; |
|
case 'i': |
|
break; |
|
case 'c': |
|
confirm_check_createdate = true; |
|
case 'r': |
|
range = strdup(confirm_range); |
|
minus = strchr(range+1, '-'); |
|
if (!minus || minus == range+1) { |
|
LOGEMERG("%s() invalid confirm range '%s' - must be %cNNN-MMM", |
|
__func__, confirm_range, tolower(confirm_range[0])); |
|
return; |
|
} |
|
*(minus++) = '\0'; |
|
confirm_range_start = atoll(range+1); |
|
if (confirm_range_start <= 0) { |
|
LOGEMERG("%s() invalid confirm start in '%s' - must be >0", |
|
__func__, confirm_range); |
|
return; |
|
} |
|
confirm_range_finish = atoll(minus); |
|
if (confirm_range_finish <= 0) { |
|
LOGEMERG("%s() invalid confirm finish in '%s' - must be >0", |
|
__func__, confirm_range); |
|
return; |
|
} |
|
if (confirm_range_finish < confirm_range_start) { |
|
LOGEMERG("%s() invalid confirm range in '%s' - finish < start", |
|
__func__, confirm_range); |
|
return; |
|
} |
|
free(range); |
|
break; |
|
case 'w': |
|
confirm_range_start = atoll(confirm_range+1); |
|
if (confirm_range_start <= 0) { |
|
LOGEMERG("%s() invalid confirm start '%s' - must be >0", |
|
__func__, confirm_range); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
logqueue_free = k_new_list("LogQueue", sizeof(LOGQUEUE), |
|
ALLOC_LOGQUEUE, LIMIT_LOGQUEUE, true); |
|
logqueue_store = k_new_store(logqueue_free); |
|
|
|
create_pthread(&log_pt, logger, NULL); |
|
|
|
rename_proc("dby_confirmer"); |
|
|
|
alloc_storage(); |
|
|
|
if (!getdata1()) { |
|
LOGEMERG("%s() ABORTING from getdata1()", __func__); |
|
return; |
|
} |
|
|
|
if (!getdata2()) { |
|
LOGEMERG("%s() ABORTING from getdata2()", __func__); |
|
return; |
|
} |
|
|
|
confirm_reload(); |
|
} |
|
|
|
static void check_restore_dir(char *name) |
|
{ |
|
struct stat statbuf; |
|
|
|
if (!restorefrom) { |
|
restorefrom = strdup("logs"); |
|
if (!restorefrom) |
|
quithere(1, "OOM"); |
|
} |
|
|
|
if (!(*restorefrom)) |
|
quit(1, "ERR: '-r dir' can't be empty"); |
|
|
|
trail_slash(&restorefrom); |
|
|
|
if (stat(restorefrom, &statbuf)) |
|
quit(1, "ERR: -r '%s' directory doesn't exist", restorefrom); |
|
|
|
restorefrom = realloc(restorefrom, strlen(restorefrom)+strlen(name)+1); |
|
if (!restorefrom) |
|
quithere(1, "OOM"); |
|
|
|
strcat(restorefrom, name); |
|
} |
|
|
|
static struct option long_options[] = { |
|
{ "dbprefix", required_argument, 0, 'b' }, |
|
{ "config", required_argument, 0, 'c' }, |
|
{ "dbname", required_argument, 0, 'd' }, |
|
{ "help", no_argument, 0, 'h' }, |
|
{ "killold", no_argument, 0, 'k' }, |
|
{ "loglevel", required_argument, 0, 'l' }, |
|
{ "name", required_argument, 0, 'n' }, |
|
{ "dbpass", required_argument, 0, 'p' }, |
|
{ "ckpool-logdir", required_argument, 0, 'r' }, |
|
{ "logdir", required_argument, 0, 'R' }, |
|
{ "sockdir", required_argument, 0, 's' }, |
|
{ "dbuser", required_argument, 0, 'u' }, |
|
{ "version", no_argument, 0, 'v' }, |
|
{ "confirm", no_argument, 0, 'y' }, |
|
{ "confirmrange", required_argument, 0, 'Y' }, |
|
{ 0, 0, 0, 0 } |
|
}; |
|
|
|
static void sighandler(int sig) |
|
{ |
|
LOGWARNING("Received signal %d, shutting down", sig); |
|
everyone_die = true; |
|
cksleep_ms(420); |
|
exit(0); |
|
} |
|
|
|
int main(int argc, char **argv) |
|
{ |
|
struct sigaction handler; |
|
char buf[512]; |
|
ckpool_t ckp; |
|
int c, ret, i = 0, j; |
|
char *kill; |
|
tv_t now; |
|
|
|
printf("CKDB Master V%s (C) Kano (see source code)\n", CKDB_VERSION); |
|
|
|
feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW); |
|
|
|
global_ckp = &ckp; |
|
memset(&ckp, 0, sizeof(ckp)); |
|
ckp.loglevel = LOG_NOTICE; |
|
|
|
while ((c = getopt_long(argc, argv, "c:d:hkl:n:p:r:R:s:u:vyY:", long_options, &i)) != -1) { |
|
switch(c) { |
|
case 'c': |
|
ckp.config = strdup(optarg); |
|
break; |
|
case 'd': |
|
db_name = strdup(optarg); |
|
kill = optarg; |
|
while (*kill) |
|
*(kill++) = ' '; |
|
break; |
|
case 'h': |
|
for (j = 0; long_options[j].val; j++) { |
|
struct option *jopt = &long_options[j]; |
|
|
|
if (jopt->has_arg) { |
|
char *upper = alloca(strlen(jopt->name) + 1); |
|
int offset = 0; |
|
|
|
do { |
|
upper[offset] = toupper(jopt->name[offset]); |
|
} while (upper[offset++] != '\0'); |
|
printf("-%c %s | --%s %s\n", jopt->val, |
|
upper, jopt->name, upper); |
|
} else |
|
printf("-%c | --%s\n", jopt->val, jopt->name); |
|
} |
|
exit(0); |
|
case 'k': |
|
ckp.killold = true; |
|
break; |
|
case 'l': |
|
ckp.loglevel = atoi(optarg); |
|
if (ckp.loglevel < LOG_EMERG || ckp.loglevel > LOG_DEBUG) { |
|
quit(1, "Invalid loglevel (range %d - %d): %d", |
|
LOG_EMERG, LOG_DEBUG, ckp.loglevel); |
|
} |
|
break; |
|
case 'n': |
|
ckp.name = strdup(optarg); |
|
break; |
|
case 'p': |
|
db_pass = strdup(optarg); |
|
kill = optarg; |
|
if (*kill) |
|
*(kill++) = ' '; |
|
while (*kill) |
|
*(kill++) = '\0'; |
|
break; |
|
case 'r': |
|
restorefrom = strdup(optarg); |
|
break; |
|
case 'R': |
|
ckp.logdir = strdup(optarg); |
|
break; |
|
case 's': |
|
ckp.socket_dir = strdup(optarg); |
|
break; |
|
case 'u': |
|
db_user = strdup(optarg); |
|
kill = optarg; |
|
while (*kill) |
|
*(kill++) = ' '; |
|
break; |
|
case 'v': |
|
exit(0); |
|
case 'y': |
|
confirm_sharesummary = true; |
|
break; |
|
case 'Y': |
|
confirm_range = strdup(optarg); |
|
// Auto enable it also |
|
confirm_sharesummary = true; |
|
break; |
|
} |
|
} |
|
|
|
if (confirm_sharesummary) |
|
dbcode = "y"; |
|
else |
|
dbcode = ""; |
|
|
|
if (!db_name) |
|
db_name = "ckdb"; |
|
if (!db_user) |
|
db_user = "postgres"; |
|
if (!ckp.name) |
|
ckp.name = "ckdb"; |
|
snprintf(buf, 15, "%s%s", ckp.name, dbcode); |
|
prctl(PR_SET_NAME, buf, 0, 0, 0); |
|
memset(buf, 0, 15); |
|
|
|
check_restore_dir(ckp.name); |
|
|
|
if (!ckp.config) { |
|
ckp.config = strdup(ckp.name); |
|
realloc_strcat(&ckp.config, ".conf"); |
|
} |
|
|
|
if (!ckp.socket_dir) { |
|
ckp.socket_dir = strdup("/opt/"); |
|
realloc_strcat(&ckp.socket_dir, ckp.name); |
|
} |
|
trail_slash(&ckp.socket_dir); |
|
|
|
/* Ignore sigpipe */ |
|
signal(SIGPIPE, SIG_IGN); |
|
|
|
ret = mkdir(ckp.socket_dir, 0770); |
|
if (ret && errno != EEXIST) |
|
quit(1, "Failed to make directory %s", ckp.socket_dir); |
|
|
|
// parse_config(&ckp); |
|
|
|
if (!ckp.logdir) |
|
ckp.logdir = strdup("dblogs"); |
|
|
|
/* Create the log directory */ |
|
trail_slash(&ckp.logdir); |
|
ret = mkdir(ckp.logdir, 0700); |
|
if (ret && errno != EEXIST) |
|
quit(1, "Failed to make log directory %s", ckp.logdir); |
|
|
|
/* Create the logfile */ |
|
sprintf(buf, "%s%s%s.log", ckp.logdir, ckp.name, dbcode); |
|
ckp.logfp = fopen(buf, "ae"); |
|
if (!ckp.logfp) |
|
quit(1, "Failed to open log file %s", buf); |
|
ckp.logfd = fileno(ckp.logfp); |
|
|
|
snprintf(logname, sizeof(logname), "%s%s-db%s-", |
|
ckp.logdir, ckp.name, dbcode); |
|
|
|
setnow(&now); |
|
srandom((unsigned int)(now.tv_usec * 4096 + now.tv_sec % 4096)); |
|
|
|
ckp.main.ckp = &ckp; |
|
ckp.main.processname = strdup("main"); |
|
|
|
if (confirm_sharesummary) { |
|
// TODO: add a system lock to stop running 2 at once? |
|
confirm_summaries(); |
|
} else { |
|
ckp.main.sockname = strdup("listener"); |
|
write_namepid(&ckp.main); |
|
create_process_unixsock(&ckp.main); |
|
|
|
create_pthread(&ckp.pth_listener, listener, &ckp.main); |
|
|
|
handler.sa_handler = sighandler; |
|
handler.sa_flags = 0; |
|
sigemptyset(&handler.sa_mask); |
|
sigaction(SIGTERM, &handler, NULL); |
|
sigaction(SIGINT, &handler, NULL); |
|
|
|
/* Shutdown from here if the listener is sent a shutdown message */ |
|
join_pthread(ckp.pth_listener); |
|
} |
|
|
|
clean_up(&ckp); |
|
|
|
return 0; |
|
}
|
|
|