ckpool/src/ckdb.c

9824 lines
277 KiB
C

/*
* 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 <regex.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.7"
#define CKDB_VERSION DB_VERSION"-0.51"
#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 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
* TODO: allow bitcoin addresses - this will also need to be handled
* while filling the queue during reload, once we allow BTC addresses
* 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: last_auth, last_share, last_stats all handled by
* DB load up to whatever the CCL reload point is, and then
* corrected with the CCL reload
* last_idle will be the last idle userstats in the CCL load or 0
* Code currently doesn't use last_idle, so for now this is OK
*
* 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 differr;
double best_sdiff; // TODO
} 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 */
// 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 };
// 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; \
item = optional_name(_root, "createby", 1, NULL); \
if (item) \
STRNCPY(_row->createby, DATA_TRANSFER(item)->data); \
item = optional_name(_root, "createcode", 1, NULL); \
if (item) \
STRNCPY(_row->createcode, DATA_TRANSFER(item)->data); \
item = optional_name(_root, "createinet", 1, NULL); \
if (item) \
STRNCPY(_row->createinet, DATA_TRANSFER(item)->data); \
} 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; \
item = optional_name(_root, "createby", 1, NULL); \
if (item) \
STRNCPY(_row->createby, DATA_TRANSFER(item)->data); \
item = optional_name(_root, "createcode", 1, NULL); \
if (item) \
STRNCPY(_row->createcode, DATA_TRANSFER(item)->data); \
item = optional_name(_root, "createinet", 1, NULL); \
if (item) \
STRNCPY(_row->createinet, DATA_TRANSFER(item)->data); \
} 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 PQPARAM27 PQPARAM16 ",$17,$18,$19,$20,$21,$22,$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
* i - just show the dbload information then exit
*/
static char *confirm_range;
static int confirm_block;
static int64_t confirm_range_start;
static int64_t confirm_range_finish;
// 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;
// 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_SHARELOG,
CMD_AUTH,
CMD_ADDUSER,
CMD_NEWPASS,
CMD_CHKPASS,
CMD_POOLSTAT,
CMD_USERSTAT,
CMD_BLOCK,
CMD_NEWID,
CMD_PAYMENTS,
CMD_WORKERS,
CMD_ALLUSERS,
CMD_HOMEPAGE,
CMD_DSP,
CMD_STATS,
CMD_END
};
// LOGQUEUE
typedef struct logqueue {
char *msg;
} LOGQUEUE;
#define ALLOC_LOGQUEUE 1024
#define LIMIT_LOGQUEUE 0
#define DATA_LOGQUEUE(_item) ((LOGQUEUE *)(_item->data))
static K_LIST *logqueue_free;
static K_STORE *logqueue_store;
static pthread_mutex_t wq_waitlock;
static pthread_cond_t wq_waitcond;
// 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 DATA_WORKQUEUE(_item) ((WORKQUEUE *)(_item->data))
static K_LIST *workqueue_free;
static K_STORE *workqueue_store;
// TRANSFER
#define NAME_SIZE 63
#define VALUE_SIZE 1023
typedef struct transfer {
char name[NAME_SIZE+1];
char value[VALUE_SIZE+1];
char *data;
} TRANSFER;
#define ALLOC_TRANSFER 64
#define LIMIT_TRANSFER 0
#define DATA_TRANSFER(_item) ((TRANSFER *)(_item->data))
static K_LIST *transfer_free;
// older version missing field defaults
static TRANSFER auth_1 = { "poolinstance", "", auth_1.value };
static K_ITEM auth_poolinstance = { "tmp", NULL, NULL, (void *)(&auth_1) };
static TRANSFER poolstats_1 = { "elapsed", "0", poolstats_1.value };
static K_ITEM poolstats_elapsed = { "tmp", NULL, NULL, (void *)(&poolstats_1) };
static TRANSFER userstats_1 = { "elapsed", "0", userstats_1.value };
static K_ITEM userstats_elapsed = { "tmp", NULL, NULL, (void *)(&userstats_1) };
static TRANSFER userstats_2 = { "workername", "all", userstats_2.value };
static K_ITEM userstats_workername = { "tmp", NULL, NULL, (void *)(&userstats_2) };
static TRANSFER userstats_3 = { "idle", FALSE_STR, userstats_3.value };
static K_ITEM userstats_idle = { "tmp", NULL, NULL, (void *)(&userstats_3) };
static TRANSFER userstats_4 = { "eos", TRUE_STR, userstats_4.value };
static K_ITEM userstats_eos = { "tmp", NULL, NULL, (void *)(&userstats_4) };
static TRANSFER shares_1 = { "secondaryuserid", TRUE_STR, shares_1.value };
static K_ITEM shares_secondaryuserid = { "", NULL, NULL, (void *)(&shares_1) };
static TRANSFER shareerrors_1 = { "secondaryuserid", TRUE_STR, shareerrors_1.value };
static K_ITEM shareerrors_secondaryuserid = { "", 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];
char emailaddress[TXT_BIG+1];
tv_t joineddate;
char passwordhash[TXT_BIG+1];
char secondaryuserid[TXT_SML+1];
HISTORYDATECONTROLFIELDS;
} USERS;
#define ALLOC_USERS 1024
#define LIMIT_USERS 0
#define DATA_USERS(_item) ((USERS *)(_item->data))
static K_TREE *users_root;
static K_TREE *userid_root;
static K_LIST *users_free;
static K_STORE *users_store;
/* TODO: for account settings - but do we want manual/auto payouts?
// USERACCOUNTS
typedef struct useraccounts {
int64_t userid;
int64_t payoutlimit;
char autopayout[TXT_FLG+1];
HISTORYDATECONTROLFIELDS;
} USERACCOUNTS;
#define ALLOC_USERACCOUNTS 1024
#define LIMIT_USERACCOUNTS 0
#define DATA_USERACCOUNTS(_item) ((USERACCOUNTS *)(_item->data))
static K_TREE *useraccounts_root;
static K_LIST *useraccounts_free;
static K_STORE *useraccounts_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 DATA_WORKERS(_item) ((WORKERS *)(_item->data))
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)
/* unused yet
// 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 DATA_PAYMENTADDRESSES(_item) ((PAYMENTADDRESSES *)(_item->data))
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 DATA_PAYMENTS(_item) ((PAYMENTS *)(_item->data))
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 DATA_ACCOUNTBALANCE(_item) ((ACCOUNTBALANCE *)(_item->data))
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 DATA_ACCOUNTADJUSTMENT(_item) ((ACCOUNTADJUSTMENT *)(_item->data))
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 DATA_IDCONTROL(_item) ((IDCONTROL *)(_item->data))
// 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 DATA_OPTIONCONTROL(_item) ((OPTIONCONTROL *)(_item->data))
static K_TREE *optioncontrol_root;
static K_LIST *optioncontrol_free;
static K_STORE *optioncontrol_store;
*/
// TODO: aging/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 DATA_WORKINFO(_item) ((WORKINFO *)(_item->data))
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;
// 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 DATA_SHARES(_item) ((SHARES *)(_item->data))
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 DATA_SHAREERRORS(_item) ((SHAREERRORS *)(_item->data))
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 DATA_SHARESUMMARY(_item) ((SHARESUMMARY *)(_item->data))
#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];
HISTORYDATECONTROLFIELDS;
} BLOCKS;
#define ALLOC_BLOCKS 100
#define LIMIT_BLOCKS 0
#define DATA_BLOCKS(_item) ((BLOCKS *)(_item->data))
#define BLOCKS_NEW 'n'
#define BLOCKS_CONFIRM '1'
static const char *blocks_new = "New";
static const char *blocks_confirm = "1-Confirm";
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 DATA_MININGPAYOUTS(_item) ((MININGPAYOUTS *)(_item->data))
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 DATA_EVENTLOG(_item) ((EVENTLOG *)(_item->data))
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];
HISTORYDATECONTROLFIELDS;
} AUTHS;
#define ALLOC_AUTHS 1000
#define LIMIT_AUTHS 0
#define DATA_AUTHS(_item) ((AUTHS *)(_item->data))
static K_TREE *auths_root;
static K_LIST *auths_free;
static K_STORE *auths_store;
// POOLSTATS poolstats.id.json={...}
// Store every > 9.5m?
#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 DATA_POOLSTATS(_item) ((POOLSTATS *)(_item->data))
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 DATA_USERSTATS(_item) ((USERSTATS *)(_item->data))
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;
} WORKERSTATUS;
#define ALLOC_WORKERSTATUS 1000
#define LIMIT_WORKERSTATUS 0
#define DATA_WORKERSTATUS(_item) ((WORKERSTATUS *)(_item->data))
static K_TREE *workerstatus_root;
static K_LIST *workerstatus_free;
static K_STORE *workerstatus_store;
static char logname[512];
static char *dbcode;
#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;
K_WLOCK(logqueue_free);
lq_item = k_unlink_head(logqueue_free);
DATA_LOGQUEUE(lq_item)->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;
}
#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 = NULL;
if (!item)
fprintf(stream, "%s() called with (null) item\n", __func__);
else {
t = DATA_TRANSFER(item);
fprintf(stream, " name='%s' data='%s' malloc=%c\n",
t->name, t->data,
(t->value == t->data) ? 'N' : 'Y');
}
}
// order by name asc
static cmp_t cmp_transfer(K_ITEM *a, K_ITEM *b)
{
return CMP_STR(DATA_TRANSFER(a)->name,
DATA_TRANSFER(b)->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);
look.data = (void *)(&transfer);
return find_in_ktree(trf_root, &look, cmp_transfer, ctx);
}
static K_ITEM *optional_name(K_TREE *trf_root, char *name, int len, char *patt)
{
K_ITEM *item;
char *value;
regex_t re;
int ret;
item = find_transfer(trf_root, name);
if (!item)
return NULL;
value = DATA_TRANSFER(item)->data;
if (!value || (int)strlen(value) < len)
return NULL;
if (patt) {
if (regcomp(&re, patt, REG_NOSUB) != 0)
return NULL;
ret = regexec(&re, value, (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)
{
K_ITEM *item;
char *value;
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;
}
value = DATA_TRANSFER(item)->data;
if (value)
dlen = strlen(value);
else
dlen = 0;
if (!value || (int)dlen < len) {
LOGERR("%s(): failed, field '%s' short (%s%d<%d) from %s():%d",
__func__, name, value ? 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, value, (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 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 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)
{
cmp_t c = CMP_BIGINT(DATA_WORKERSTATUS(a)->userid,
DATA_WORKERSTATUS(b)->userid);
if (c == 0) {
c = CMP_STR(DATA_WORKERSTATUS(a)->workername,
DATA_WORKERSTATUS(b)->workername);
}
return c;
}
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);
look.data = (void *)(&workerstatus);
K_RLOCK(workerstatus_free);
find = find_in_ktree(workerstatus_root, &look, cmp_workerstatus, ctx);
K_RUNLOCK(workerstatus_free);
return find;
}
static K_ITEM *_find_create_workerstatus(int64_t userid, char *workername, bool create)
{
K_ITEM *item = NULL;
WORKERSTATUS *row;
item = get_workerstatus(userid, workername);
if (!item && create) {
K_WLOCK(workerstatus_free);
item = k_unlink_head(workerstatus_free);
row = DATA_WORKERSTATUS(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) _find_create_workerstatus(_u, _w, true)
#define find_workerstatus(_u, _w) _find_create_workerstatus(_u, _w, false)
static cmp_t cmp_userstats_workerstatus(K_ITEM *a, K_ITEM *b);
static cmp_t cmp_sharesummary(K_ITEM *a, K_ITEM *b);
/* All data is loaded, now update workerstatus fields
* Since shares are all part of a sharesummary, there's no need to search shares
*/
static void workerstatus_ready()
{
K_TREE_CTX ws_ctx[1], us_ctx[1], ss_ctx[1];
K_ITEM *ws_item, look, *us_item, *ss_item;
USERSTATS userstats;
SHARESUMMARY sharesummary;
ws_item = first_in_ktree(workerstatus_root, ws_ctx);
while (ws_item) {
userstats.userid = DATA_WORKERSTATUS(ws_item)->userid;
STRNCPY(userstats.workername, DATA_WORKERSTATUS(ws_item)->workername);
userstats.statsdate.tv_sec = date_eot.tv_sec;
userstats.statsdate.tv_usec = date_eot.tv_usec;
look.data = (void *)(&userstats);
us_item = find_before_in_ktree(userstats_workerstatus_root, &look,
cmp_userstats_workerstatus, us_ctx);
if (us_item) {
if (DATA_USERSTATS(us_item)->idle) {
if (tv_newer(&(DATA_WORKERSTATUS(ws_item)->last_idle),
&(DATA_USERSTATS(us_item)->statsdate))) {
copy_tv(&(DATA_WORKERSTATUS(ws_item)->last_idle),
&(DATA_USERSTATS(us_item)->statsdate));
}
} else {
if (tv_newer(&(DATA_WORKERSTATUS(ws_item)->last_stats),
&(DATA_USERSTATS(us_item)->statsdate))) {
copy_tv(&(DATA_WORKERSTATUS(ws_item)->last_stats),
&(DATA_USERSTATS(us_item)->statsdate));
}
}
}
sharesummary.userid = DATA_WORKERSTATUS(ws_item)->userid;
STRNCPY(sharesummary.workername, DATA_WORKERSTATUS(ws_item)->workername);
sharesummary.workinfoid = MAXID;
look.data = (void *)(&sharesummary);
ss_item = find_before_in_ktree(sharesummary_root, &look, cmp_sharesummary, ss_ctx);
if (ss_item) {
if (tv_newer(&(DATA_WORKERSTATUS(ws_item)->last_share),
&(DATA_SHARESUMMARY(ss_item)->lastshare))) {
copy_tv(&(DATA_WORKERSTATUS(ws_item)->last_share),
&(DATA_SHARESUMMARY(ss_item)->lastshare));
DATA_WORKERSTATUS(ws_item)->last_diff =
DATA_SHARESUMMARY(ss_item)->lastdiffacc;
}
}
ws_item = next_in_ktree(ws_ctx);
}
}
static void workerstatus_update(AUTHS *auths, SHARES *shares, USERSTATS *userstats,
SHARESUMMARY *sharesummary)
{
WORKERSTATUS *row;
K_ITEM *item;
if (auths) {
item = find_create_workerstatus(auths->userid, auths->workername);
row = DATA_WORKERSTATUS(item);
if (tv_newer(&(row->last_auth), &(auths->createdate)))
copy_tv(&(row->last_auth), &(auths->createdate));
}
if (startup_complete && shares) {
item = find_create_workerstatus(shares->userid, shares->workername);
row = DATA_WORKERSTATUS(item);
if (tv_newer(&(row->last_share), &(shares->createdate))) {
copy_tv(&(row->last_share), &(shares->createdate));
row->last_diff = shares->diff;
}
}
if (startup_complete && userstats) {
item = find_create_workerstatus(userstats->userid, userstats->workername);
row = DATA_WORKERSTATUS(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));
}
}
if (startup_complete && sharesummary) {
item = find_create_workerstatus(sharesummary->userid, sharesummary->workername);
row = DATA_WORKERSTATUS(item);
if (tv_newer(&(row->last_share), &(sharesummary->lastshare))) {
copy_tv(&(row->last_share), &(sharesummary->lastshare));
row->last_diff = sharesummary->lastdiffacc;
}
}
}
// default tree order by username asc,expirydate desc
static cmp_t cmp_users(K_ITEM *a, K_ITEM *b)
{
cmp_t c = CMP_STR(DATA_USERS(a)->username,
DATA_USERS(b)->username);
if (c == 0) {
c = CMP_TV(DATA_USERS(b)->expirydate,
DATA_USERS(a)->expirydate);
}
return c;
}
// order by userid asc,expirydate desc
static cmp_t cmp_userid(K_ITEM *a, K_ITEM *b)
{
cmp_t c = CMP_BIGINT(DATA_USERS(a)->userid,
DATA_USERS(b)->userid);
if (c == 0) {
c = CMP_TV(DATA_USERS(b)->expirydate,
DATA_USERS(a)->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;
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;
look.data = (void *)(&users);
return find_in_ktree(userid_root, &look, cmp_userid, ctx);
}
static bool users_pass(PGconn *conn, K_ITEM *u_item, char *oldhash,
char *newhash, 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;
char *upd, *ins;
bool ok = false;
char *params[4 + HISTORYDATECOUNT];
int par;
LOGDEBUG("%s(): change", __func__);
if (strcasecmp(oldhash, DATA_USERS(u_item)->passwordhash))
return false;
K_WLOCK(users_free);
item = k_unlink_head(users_free);
K_WUNLOCK(users_free);
row = DATA_USERS(item);
memcpy(row, DATA_USERS(u_item), sizeof(*row));
STRNCPY(row->passwordhash, newhash);
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);
upd = "update users set expirydate=$1 where userid=$2 and passwordhash=$3 and expirydate=$4";
par = 0;
params[par++] = tv_to_buf(cd, NULL, 0);
params[par++] = bigint_to_buf(row->userid, NULL, 0);
params[par++] = str_to_buf(oldhash, NULL, 0);
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0);
PARCHKVAL(par, 4, 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);
params[par++] = str_to_buf(row->passwordhash, NULL, 0);
HISTORYDATEPARAMS(params, par, row);
PARCHKVAL(par, 3 + HISTORYDATECOUNT, params); // 8 as per ins
ins = "insert into users "
"(userid,username,emailaddress,joineddate,passwordhash,"
"secondaryuserid"
HISTORYDATECONTROL ") select "
"userid,username,emailaddress,joineddate,$3,"
"secondaryuserid,"
"$4,$5,$6,$7,$8 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(&(DATA_USERS(u_item)->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 bool 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[6 + HISTORYDATECOUNT];
int par;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(users_free);
item = k_unlink_head(users_free);
K_WUNLOCK(users_free);
row = DATA_USERS(item);
row->userid = nextid(conn, "userid", (int64_t)(666 + (rand() % 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);
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));
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->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);
HISTORYDATEPARAMS(params, par, row);
PARCHK(par, params);
ins = "insert into users "
"(userid,username,emailaddress,joineddate,passwordhash,"
"secondaryuserid"
HISTORYDATECONTROL ") values (" PQPARAM11 ")";
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);
return ok;
}
static bool users_fill(PGconn *conn)
{
ExecStatusType rescode;
PGresult *res;
K_ITEM *item;
int n, i;
USERS *row;
char *field;
char *sel;
int fields = 6;
bool ok;
LOGDEBUG("%s(): select", __func__);
sel = "select "
"userid,username,emailaddress,joineddate,passwordhash,"
"secondaryuserid"
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);
row = DATA_USERS(item);
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, "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);
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);
}
// order by userid asc,workername asc,expirydate desc
static cmp_t cmp_workers(K_ITEM *a, K_ITEM *b)
{
cmp_t c = CMP_BIGINT(DATA_WORKERS(a)->userid,
DATA_WORKERS(b)->userid);
if (c == 0) {
c = CMP_STR(DATA_WORKERS(a)->workername,
DATA_WORKERS(b)->workername);
if (c == 0) {
c = CMP_TV(DATA_WORKERS(b)->expirydate,
DATA_WORKERS(a)->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;
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);
row = DATA_WORKERS(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);
}
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__);
row = DATA_WORKERS(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;
K_RLOCK(users_free);
item = find_users(username);
K_RUNLOCK(users_free);
if (!item)
return NULL;
return new_worker(conn, update, DATA_USERS(item)->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);
row = DATA_WORKERS(item);
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);
}
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,paydate asc,payaddress asc,expirydate desc
static cmp_t cmp_payments(K_ITEM *a, K_ITEM *b)
{
cmp_t c = CMP_BIGINT(DATA_PAYMENTS(a)->userid,
DATA_PAYMENTS(b)->userid);
if (c == 0) {
c = CMP_TV(DATA_PAYMENTS(a)->paydate,
DATA_PAYMENTS(b)->paydate);
if (c == 0) {
c = CMP_STR(DATA_PAYMENTS(a)->payaddress,
DATA_PAYMENTS(b)->payaddress);
if (c == 0) {
c = CMP_TV(DATA_PAYMENTS(b)->expirydate,
DATA_PAYMENTS(a)->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
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);
row = DATA_PAYMENTS(item);
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)
{
cmp_t c = CMP_BIGINT(DATA_WORKINFO(a)->workinfoid,
DATA_WORKINFO(b)->workinfoid);
if (c == 0) {
c = CMP_TV(DATA_WORKINFO(a)->expirydate,
DATA_WORKINFO(b)->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" WHERE_FFL,
__func__, siz, 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)
{
cmp_t c = cmp_height(DATA_WORKINFO(a)->coinbase1,
DATA_WORKINFO(b)->coinbase1);
if (c == 0) {
c = CMP_TV(DATA_WORKINFO(a)->createdate,
DATA_WORKINFO(b)->createdate);
}
return c;
}
static K_ITEM *find_workinfo(int64_t workinfoid)
{
WORKINFO workinfo;
K_TREE_CTX ctx[1];
K_ITEM look;
workinfo.workinfoid = workinfoid;
workinfo.expirydate.tv_sec = default_expiry.tv_sec;
workinfo.expirydate.tv_usec = default_expiry.tv_usec;
look.data = (void *)(&workinfo);
return find_in_ktree(workinfo_root, &look, cmp_workinfo, ctx);
}
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];
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);
row = DATA_WORKINFO(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)) {
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);
}
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) {
if (cmp_height(DATA_WORKINFO(workinfo_current)->coinbase1,
DATA_WORKINFO(item)->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);
/* N.B. a DB check can be done to find sharesummaries that have missed being
* aged (and a possible problem with the aging process):
* e.g. for a date D in the past of at least a few hours
* select count(*) from sharesummary where createdate<'D' and complete='n';
* and can be easily corrected:
* update sharesummary set complete='a' where createdate<'D' and complete='n';
* It's important to make sure the D value is far enough in the past such that
* all the matching sharesummary records in ckdb have certainly completed
* ckdb would need to restart to get the updated DB information though it would
* not affect current ckdb code
* TODO: This will happen until auto aging is added here - since ckpool can not reliably
* age all workinfo that was active when it exits (e.g. a crash) so best to not
* try, and get ckdb to auto age old unaged data
*/
static bool workinfo_age(PGconn *conn, char *workinfoidstr, char *poolinstance,
char *by, char *code, char *inet, tv_t *cd)
{
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 workinfoid;
SHARESUMMARY sharesummary;
SHARES shares;
bool ok = false, conned = false, skipupdate;
char error[1024];
LOGDEBUG("%s(): complete", __func__);
TXT_TO_BIGINT("workinfoid", workinfoidstr, workinfoid);
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;
}
if (strcmp(poolinstance, DATA_WORKINFO(wi_item)->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,
DATA_WORKINFO(wi_item)->poolinstance);
goto bye;
}
// Find the first matching sharesummary
sharesummary.workinfoid = workinfoid;
sharesummary.userid = -1;
sharesummary.workername[0] = '\0';
ok = true;
ss_look.data = (void *)(&sharesummary);
ss_item = find_after_in_ktree(sharesummary_workinfoid_root, &ss_look, cmp_sharesummary_workinfoid, ss_ctx);
while (ss_item && DATA_SHARESUMMARY(ss_item)->workinfoid == workinfoid) {
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 (DATA_SHARESUMMARY(ss_item)->complete[0] == SUMMARY_COMPLETE) {
skipupdate = true;
if (confirm_sharesummary) {
LOGERR("%s(): Duplicate %s found during confirm %"PRId64"/%s/%"PRId64,
__func__, __func__,
DATA_SHARESUMMARY(ss_item)->userid,
DATA_SHARESUMMARY(ss_item)->workername,
DATA_SHARESUMMARY(ss_item)->workinfoid);
}
}
}
if (!skipupdate) {
if (conn == NULL && !confirm_sharesummary) {
conn = dbconnect();
conned = true;
}
if (!sharesummary_update(conn, NULL, NULL, ss_item, by, code, inet, cd)) {
LOGERR("%s(): Failed to age share summary %"PRId64"/%s/%"PRId64,
__func__, DATA_SHARESUMMARY(ss_item)->userid,
DATA_SHARESUMMARY(ss_item)->workername,
DATA_SHARESUMMARY(ss_item)->workinfoid);
ok = false;
}
}
// Discard the shares either way
shares.workinfoid = workinfoid;
shares.userid = DATA_SHARESUMMARY(ss_item)->userid;
strcpy(shares.workername, DATA_SHARESUMMARY(ss_item)->workername);
shares.createdate.tv_sec = 0;
shares.createdate.tv_usec = 0;
s_look.data = (void *)(&shares);
s_item = find_after_in_ktree(shares_root, &s_look, cmp_shares, s_ctx);
K_WLOCK(shares_free);
while (s_item) {
if (DATA_SHARES(s_item)->workinfoid != workinfoid ||
DATA_SHARES(s_item)->userid != shares.userid ||
strcmp(DATA_SHARES(s_item)->workername, shares.workername) != 0)
break;
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 && !error[0]) {
snprintf(error, sizeof(error),
"reload found aged shares: %"PRId64"/%"PRId64"/%s",
DATA_SHARES(s_item)->workinfoid,
DATA_SHARES(s_item)->userid,
DATA_SHARES(s_item)->workername);
}
k_add_head(shares_free, s_item);
s_item = tmp_item;
}
K_WUNLOCK(shares_free);
ss_item = next_in_ktree(ss_ctx);
if (error[0])
LOGERR("%s(): %s", __func__, error);
}
if (conned)
PQfinish(conn);
bye:
return ok;
}
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?
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);
row = DATA_WORKINFO(item);
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)
{
cmp_t c = CMP_BIGINT(DATA_SHARES(a)->workinfoid,
DATA_SHARES(b)->workinfoid);
if (c == 0) {
c = CMP_BIGINT(DATA_SHARES(a)->userid,
DATA_SHARES(b)->userid);
if (c == 0) {
c = CMP_STR(DATA_SHARES(a)->workername,
DATA_SHARES(b)->workername);
if (c == 0) {
c = CMP_TV(DATA_SHARES(a)->createdate,
DATA_SHARES(b)->createdate);
if (c == 0) {
c = CMP_STR(DATA_SHARES(a)->nonce,
DATA_SHARES(b)->nonce);
if (c == 0) {
c = CMP_TV(DATA_SHARES(b)->expirydate,
DATA_SHARES(a)->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 *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];
SHARES *shares;
bool ok = false;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(shares_free);
s_item = k_unlink_head(shares_free);
K_WUNLOCK(shares_free);
shares = DATA_SHARES(s_item);
// TODO: allow BTC address later?
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;
}
shares->userid = DATA_USERS(u_item)->userid;
TXT_TO_BIGINT("workinfoid", workinfoid, shares->workinfoid);
STRNCPY(shares->workername, workername);
TXT_TO_INT("clientid", clientid, shares->clientid);
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,
DATA_USERS(u_item)->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) {
if (DATA_SHARESUMMARY(ss_item)->complete[0] != SUMMARY_NEW) {
K_WLOCK(shares_free);
k_add_head(shares_free, s_item);
K_WUNLOCK(shares_free);
return true;
}
if (!DATA_SHARESUMMARY(ss_item)->reset) {
if (DATA_SHARESUMMARY(ss_item)->workinfoid >= pool.workinfoid) {
// Negate coz the shares will re-add
pool.diffacc -= DATA_SHARESUMMARY(ss_item)->sharecount;
pool.differr -= DATA_SHARESUMMARY(ss_item)->errorcount;
}
zero_sharesummary(DATA_SHARESUMMARY(ss_item), cd, shares->diff);
DATA_SHARESUMMARY(ss_item)->reset = true;
}
}
}
if (!confirm_sharesummary)
workerstatus_update(NULL, shares, NULL, 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()
{
// TODO: reload shares from workinfo from log file
// and verify workinfo while doing that
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)
{
cmp_t c = CMP_BIGINT(DATA_SHAREERRORS(a)->workinfoid,
DATA_SHAREERRORS(b)->workinfoid);
if (c == 0) {
c = CMP_BIGINT(DATA_SHAREERRORS(a)->userid,
DATA_SHAREERRORS(b)->userid);
if (c == 0) {
c = CMP_TV(DATA_SHAREERRORS(a)->createdate,
DATA_SHAREERRORS(b)->createdate);
if (c == 0) {
c = CMP_TV(DATA_SHAREERRORS(b)->expirydate,
DATA_SHAREERRORS(a)->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];
SHAREERRORS *shareerrors;
bool ok = false;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(shareerrors_free);
s_item = k_unlink_head(shareerrors_free);
K_WUNLOCK(shareerrors_free);
shareerrors = DATA_SHAREERRORS(s_item);
// TODO: allow BTC address later?
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;
}
shareerrors->userid = DATA_USERS(u_item)->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,
DATA_USERS(u_item)->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) {
if (DATA_SHARESUMMARY(ss_item)->complete[0] != SUMMARY_NEW) {
K_WLOCK(shareerrors_free);
k_add_head(shareerrors_free, s_item);
K_WUNLOCK(shareerrors_free);
return true;
}
if (!DATA_SHARESUMMARY(ss_item)->reset) {
if (DATA_SHARESUMMARY(ss_item)->workinfoid >= pool.workinfoid) {
// Negate coz the shares will re-add
pool.diffacc -= DATA_SHARESUMMARY(ss_item)->sharecount;
pool.differr -= DATA_SHARESUMMARY(ss_item)->errorcount;
}
zero_sharesummary(DATA_SHARESUMMARY(ss_item), cd, 0.0);
DATA_SHARESUMMARY(ss_item)->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()
{
// TODO: reload shareerrors from workinfo from log file
// and verify workinfo while doing that
return true;
}
static void dsp_sharesummary(K_ITEM *item, FILE *stream)
{
char createdate_buf[DATE_BUFSIZ];
SHARESUMMARY *s = NULL;
if (!item)
fprintf(stream, "%s() called with (null) item\n", __func__);
else {
s = DATA_SHARESUMMARY(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)
{
cmp_t c = CMP_BIGINT(DATA_SHARESUMMARY(a)->userid,
DATA_SHARESUMMARY(b)->userid);
if (c == 0) {
c = CMP_STR(DATA_SHARESUMMARY(a)->workername,
DATA_SHARESUMMARY(b)->workername);
if (c == 0) {
c = CMP_BIGINT(DATA_SHARESUMMARY(a)->workinfoid,
DATA_SHARESUMMARY(b)->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)
{
cmp_t c = CMP_BIGINT(DATA_SHARESUMMARY(a)->workinfoid,
DATA_SHARESUMMARY(b)->workinfoid);
if (c == 0) {
c = CMP_BIGINT(DATA_SHARESUMMARY(a)->userid,
DATA_SHARESUMMARY(b)->userid);
if (c == 0) {
c = CMP_STR(DATA_SHARESUMMARY(a)->workername,
DATA_SHARESUMMARY(b)->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;
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;
row = DATA_SHARESUMMARY(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);
}
item = find_sharesummary(userid, workername, workinfoid);
if (item) {
new = false;
row = DATA_SHARESUMMARY(item);
} else {
new = true;
K_WLOCK(sharesummary_free);
item = k_unlink_head(sharesummary_free);
K_WUNLOCK(sharesummary_free);
row = DATA_SHARESUMMARY(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++;
if (row->workinfoid >= pool.workinfoid)
pool.diffacc += s_row->diff;
break;
case SE_STALE:
row->diffsta += s_row->diff;
row->sharesta++;
if (row->workinfoid >= pool.workinfoid)
pool.differr += s_row->diff;
break;
case SE_DUPE:
row->diffdup += s_row->diff;
row->sharedup++;
if (row->workinfoid >= pool.workinfoid)
pool.differr += s_row->diff;
break;
case SE_HIGH_DIFF:
row->diffhi += s_row->diff;
row->sharehi++;
if (row->workinfoid >= pool.workinfoid)
pool.differr += s_row->diff;
break;
default:
row->diffrej += s_row->diff;
row->sharerej++;
if (row->workinfoid >= pool.workinfoid)
pool.differr += s_row->diff;
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);
}
}
}
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++] = double_to_buf(row->lastdiffacc, NULL, 0);
params[par++] = str_to_buf(row->complete, NULL, 0);
MODIFYUPDATEPARAMS(params, par, row);
PARCHKVAL(par, 21, 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,"
"lastdiffacc=$16,complete=$17"
",modifydate=$18,modifyby=$19,modifycode=$20,modifyinet=$21 "
"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;
}
}
}
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;
K_WLOCK(sharesummary_free);
for (i = 0; i < n; i++) {
item = k_unlink_head(sharesummary_free);
row = DATA_SHARESUMMARY(item);
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);
workerstatus_update(NULL, NULL, NULL, row);
// 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;
}
}
}
if (row->workinfoid >= pool.workinfoid) {
pool.diffacc += row->diffacc;
pool.differr += row->diffsta + row->diffdup +
row->diffhi + row->diffrej;
}
tick();
}
if (!ok)
k_add_head(sharesummary_free, item);
K_WUNLOCK(sharesummary_free);
PQclear(res);
if (ok) {
LOGDEBUG("%s(): built", __func__);
LOGWARNING("%s(): loaded %d sharesummary records", __func__, n);
}
return ok;
}
void sharesummary_reload()
{
PGconn *conn = dbconnect();
K_WLOCK(sharesummary_free);
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);
K_WUNLOCK(sharesummary_free);
sharesummary_fill(conn);
PQfinish(conn);
}
// order by height asc,blockhash asc,expirydate desc
static cmp_t cmp_blocks(K_ITEM *a, K_ITEM *b)
{
cmp_t c = CMP_INT(DATA_BLOCKS(a)->height,
DATA_BLOCKS(b)->height);
if (c == 0) {
c = CMP_STR(DATA_BLOCKS(a)->blockhash,
DATA_BLOCKS(b)->blockhash);
if (c == 0) {
c = CMP_TV(DATA_BLOCKS(b)->expirydate,
DATA_BLOCKS(a)->expirydate);
}
}
return c;
}
// 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;
look.data = (void *)(&blocks);
return find_in_ktree(blocks_root, &look, cmp_blocks, ctx);
}
static const char *blocks_confirmed(char *confirmed)
{
switch (confirmed[0]) {
case BLOCKS_NEW:
return blocks_new;
case BLOCKS_CONFIRM:
return blocks_confirm;
}
return blocks_unknown;
}
// TODO: determine how to handle orphan blocks after 1 confirm
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 blk_dsp[16+1], *ptr;
BLOCKS *row;
char *upd, *ins;
char *params[11 + HISTORYDATECOUNT];
bool ok = false, update_old = false;
int par = 0;
int n;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(blocks_free);
b_item = k_unlink_head(blocks_free);
K_WUNLOCK(blocks_free);
row = DATA_BLOCKS(b_item);
TXT_TO_INT("height", height, row->height);
STRNCPY(row->blockhash, blockhash);
HISTORYDATEINIT(row, cd, by, code, inet);
// TODO: do this better ... :)
ptr = blockhash + strlen(blockhash) - (sizeof(blk_dsp)-1) - 8;
if (ptr < blockhash)
ptr = blockhash;
STRNCPY(blk_dsp, ptr);
K_WLOCK(blocks_free);
old_b_item = find_blocks(row->height, blockhash);
switch (confirmed[0]) {
case BLOCKS_NEW:
// None should exist - so must be a duplicate
if (old_b_item) {
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(DATA_BLOCKS(old_b_item)->confirmed),
blocks_confirmed(confirmed),
height, blk_dsp, cd_buf);
}
return true;
}
K_WUNLOCK(blocks_free);
K_RLOCK(users_free);
u_item = find_users(username);
K_RUNLOCK(users_free);
if (!u_item)
row->userid = KANO;
else
row->userid = DATA_USERS(u_item)->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);
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);
HISTORYDATEPARAMS(params, par, row);
PARCHK(par, params);
ins = "insert into blocks "
"(height,blockhash,workinfoid,userid,workername,"
"clientid,enonce1,nonce2,nonce,reward,confirmed"
HISTORYDATECONTROL ") values (" PQPARAM16 ")";
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_CONFIRM:
if (!old_b_item) {
k_add_head(blocks_free, b_item);
K_WUNLOCK(blocks_free);
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s(): Can't confirm a non-existent block, Status: "
"%s, Block: %s/...%s/%s",
__func__, blocks_confirmed(confirmed),
height, blk_dsp, cd_buf);
return false;
}
/* This will also treat an unrecognised 'confirmed' as a
* duplicate since they shouldn't exist anyway */
if (DATA_BLOCKS(old_b_item)->confirmed[0] != BLOCKS_NEW) {
k_add_head(blocks_free, b_item);
K_WUNLOCK(blocks_free);
if (!igndup || DATA_BLOCKS(old_b_item)->confirmed[0] != BLOCKS_CONFIRM) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s(): Duplicate (%s) blocks ignored, Status: "
"%s, Block: %s/...%s/%s",
__func__,
blocks_confirmed(DATA_BLOCKS(old_b_item)->confirmed),
blocks_confirmed(confirmed),
height, blk_dsp, cd_buf);
}
return true;
}
K_WUNLOCK(blocks_free);
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;
}
STRNCPY(row->confirmed, confirmed);
// New is mostly a copy of the old
row->workinfoid = DATA_BLOCKS(old_b_item)->workinfoid;
STRNCPY(row->workername, DATA_BLOCKS(old_b_item)->workername);
row->clientid = DATA_BLOCKS(old_b_item)->clientid;
STRNCPY(row->enonce1, DATA_BLOCKS(old_b_item)->enonce1);
STRNCPY(row->nonce2, DATA_BLOCKS(old_b_item)->nonce2);
STRNCPY(row->nonce, DATA_BLOCKS(old_b_item)->nonce);
row->reward = DATA_BLOCKS(old_b_item)->reward;
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);
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"
HISTORYDATECONTROL ") select "
"height,blockhash,workinfoid,userid,workername,"
"clientid,enonce1,nonce2,nonce,reward,"
"$3,$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:
K_WUNLOCK(blocks_free);
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(&(DATA_BLOCKS(old_b_item)->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 tmp[128];
if (confirmed[0] != BLOCKS_NEW)
tmp[0] = '\0';
else {
tv_to_buf(&(DATA_BLOCKS(b_item)->createdate), cd_buf, sizeof(cd_buf));
snprintf(tmp, sizeof(tmp),
" Reward: %f, User: %s, Worker: %s, ShareEst: %.1f UTC:%s",
BTC_TO_D(DATA_BLOCKS(b_item)->reward),
username, workername, pool.diffacc, cd_buf);
if (pool.workinfoid < DATA_BLOCKS(b_item)->workinfoid) {
pool.workinfoid = DATA_BLOCKS(b_item)->workinfoid;
pool.diffacc = pool.differr =
pool.best_sdiff = 0.0;
}
}
LOGWARNING("%s(): BLOCK! Status: %s, Block: %s/...%s%s",
__func__,
blocks_confirmed(confirmed),
height, blk_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 = 11;
bool ok;
LOGDEBUG("%s(): select", __func__);
sel = "select "
"height,blockhash,workinfoid,userid,workername,"
"clientid,enonce1,nonce2,nonce,reward,confirmed"
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;
K_WLOCK(blocks_free);
for (i = 0; i < n; i++) {
item = k_unlink_head(blocks_free);
row = DATA_BLOCKS(item);
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_BLOB("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);
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);
K_WUNLOCK(blocks_free);
PQclear(res);
if (ok) {
LOGDEBUG("%s(): built", __func__);
LOGWARNING("%s(): loaded %d blocks records", __func__, n);
}
return ok;
}
void blocks_reload()
{
PGconn *conn = dbconnect();
K_WLOCK(blocks_free);
blocks_root = free_ktree(blocks_root, NULL);
k_list_transfer_to_head(blocks_store, blocks_free);
K_WUNLOCK(blocks_free);
blocks_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)
{
cmp_t c = CMP_BIGINT(DATA_AUTHS(a)->userid,
DATA_AUTHS(b)->userid);
if (c == 0) {
c = CMP_TV(DATA_AUTHS(a)->createdate,
DATA_AUTHS(b)->createdate);
if (c == 0) {
c = CMP_BIGINT(DATA_AUTHS(a)->authid,
DATA_AUTHS(b)->authid);
if (c == 0) {
c = CMP_TV(DATA_AUTHS(b)->expirydate,
DATA_AUTHS(a)->expirydate);
}
}
}
return c;
}
static char *auths_add(PGconn *conn, char *poolinstance, char *username,
char *workername, char *clientid, char *enonce1,
char *useragent, 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 *a_item, *u_item;
char cd_buf[DATE_BUFSIZ];
int n;
AUTHS *row;
char *ins;
char *secuserid = NULL;
char *params[7 + HISTORYDATECOUNT];
int par;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(auths_free);
a_item = k_unlink_head(auths_free);
K_WUNLOCK(auths_free);
row = DATA_AUTHS(a_item);
K_RLOCK(users_free);
u_item = find_users(username);
K_RUNLOCK(users_free);
if (!u_item)
goto unitem;
STRNCPY(row->poolinstance, poolinstance);
row->userid = DATA_USERS(u_item)->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);
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 (!igndup) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s(): Duplicate auths ignored %s/%s/%s",
__func__, poolinstance, workername, cd_buf);
}
return DATA_USERS(u_item)->secondaryuserid;
}
K_WUNLOCK(auths_free);
// Update even if DB fails
workerstatus_update(row, NULL, 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);
HISTORYDATEPARAMS(params, par, row);
PARCHK(par, params);
ins = "insert into auths "
"(authid,poolinstance,userid,workername,clientid,enonce1,useragent"
HISTORYDATECONTROL ") values (" PQPARAM12 ")";
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 = DATA_USERS(u_item)->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 = 6;
bool ok;
LOGDEBUG("%s(): select", __func__);
// TODO: keep last x - since a user may login and mine for 100 days
sel = "select "
"authid,userid,workername,clientid,enonce1,useragent"
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);
row = DATA_AUTHS(item);
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_BLOB("enonce1", field, row->enonce1);
PQ_GET_FLD(res, i, "useragent", field, ok);
if (!ok)
break;
TXT_TO_STR("useragent", field, row->useragent);
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, 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)
{
cmp_t c = CMP_STR(DATA_POOLSTATS(a)->poolinstance,
DATA_POOLSTATS(b)->poolinstance);
if (c == 0) {
c = CMP_TV(DATA_POOLSTATS(a)->createdate,
DATA_POOLSTATS(b)->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);
row = DATA_POOLSTATS(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);
row = DATA_POOLSTATS(item);
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 {
u = DATA_USERSTATS(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)
{
cmp_t c = CMP_BIGINT(DATA_USERSTATS(a)->userid,
DATA_USERSTATS(b)->userid);
if (c == 0) {
c = CMP_TV(DATA_USERSTATS(a)->statsdate,
DATA_USERSTATS(b)->statsdate);
if (c == 0) {
c = CMP_STR(DATA_USERSTATS(a)->poolinstance,
DATA_USERSTATS(b)->poolinstance);
if (c == 0) {
c = CMP_STR(DATA_USERSTATS(a)->workername,
DATA_USERSTATS(b)->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)
{
cmp_t c = CMP_BIGINT(DATA_USERSTATS(a)->userid,
DATA_USERSTATS(b)->userid);
if (c == 0) {
c = CMP_STR(DATA_USERSTATS(a)->workername,
DATA_USERSTATS(b)->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)
{
cmp_t c = CMP_TV(DATA_USERSTATS(a)->statsdate,
DATA_USERSTATS(b)->statsdate);
if (c == 0) {
c = CMP_BIGINT(DATA_USERSTATS(a)->userid,
DATA_USERSTATS(b)->userid);
if (c == 0) {
c = CMP_STR(DATA_USERSTATS(a)->workername,
DATA_USERSTATS(b)->workername);
if (c == 0) {
c = CMP_STR(DATA_USERSTATS(a)->poolinstance,
DATA_USERSTATS(b)->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)
{
cmp_t c = CMP_BIGINT(DATA_USERSTATS(a)->userid,
DATA_USERSTATS(b)->userid);
if (c == 0) {
c = CMP_STR(DATA_USERSTATS(a)->workername,
DATA_USERSTATS(b)->workername);
if (c == 0) {
c = CMP_TV(DATA_USERSTATS(a)->statsdate,
DATA_USERSTATS(b)->statsdate);
if (c == 0) {
c = CMP_STR(DATA_USERSTATS(a)->poolinstance,
DATA_USERSTATS(b)->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;
K_TREE_CTX ctx[1];
LOGDEBUG("%s(): add", __func__);
K_WLOCK(userstats_free);
us_item = k_unlink_head(userstats_free);
K_WUNLOCK(userstats_free);
row = DATA_USERSTATS(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;
row->userid = DATA_USERS(u_item)->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));
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);
if (us_match &&
DATA_USERSTATS(us_match)->userid == row->userid &&
strcmp(DATA_USERSTATS(us_match)->workername, row->workername) == 0 &&
DATA_USERSTATS(us_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, NULL);
/* 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(us_match)->hashrate += row->hashrate;
DATA_USERSTATS(us_match)->hashrate5m += row->hashrate5m;
DATA_USERSTATS(us_match)->hashrate1hr += row->hashrate1hr;
DATA_USERSTATS(us_match)->hashrate24hr += row->hashrate24hr;
// Minimum elapsed of the data set
if (DATA_USERSTATS(us_match)->elapsed > row->elapsed)
DATA_USERSTATS(us_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) {
if (tvdiff(&DATA_USERSTATS(us_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),
DATA_USERSTATS(us_next)->poolinstance,
DATA_USERSTATS(us_next)->userid,
DATA_USERSTATS(us_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);
row = DATA_USERSTATS(item);
// 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, NULL);
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", __func__, DB_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 = 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 = 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);
}
// TODO: skip ones not needed for confirm_summaries()
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();
workers_free = k_new_list("Workers", sizeof(WORKERS),
ALLOC_WORKERS, LIMIT_WORKERS, true);
workers_store = k_new_store(workers_free);
workers_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();
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;
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;
workerstatus_ready();
userstats_workerstatus_root = free_ktree(userstats_workerstatus_root, NULL);
workinfo_current = last_in_ktree(workinfo_height_root, ctx);
if (workinfo_current) {
STRNCPY(wi.coinbase1, DATA_WORKINFO(workinfo_current)->coinbase1);
wi.createdate.tv_sec = 0L;
wi.createdate.tv_usec = 0L;
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)
copy_tv(&last_bc, &(DATA_WORKINFO(found)->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;
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_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);
ok = users_add(conn, DATA_TRANSFER(i_username)->data,
DATA_TRANSFER(i_emailaddress)->data,
DATA_TRANSFER(i_passwordhash)->data,
by, code, inet, now, trf_root);
if (!ok) {
LOGERR("%s() %s.failed.DBE", __func__, id);
return strdup("failed.DBE");
}
LOGDEBUG("%s.ok.added %s", id, DATA_TRANSFER(i_username)->data);
snprintf(reply, siz, "ok.added %s", DATA_TRANSFER(i_username)->data);
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;
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 = require_name(trf_root, "oldhash", 64, (char *)hashpatt, reply, siz);
if (!i_oldhash)
return strdup(reply);
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(DATA_TRANSFER(i_username)->data);
K_RUNLOCK(users_free);
if (u_item) {
ok = users_pass(NULL, u_item,
DATA_TRANSFER(i_oldhash)->data,
DATA_TRANSFER(i_newhash)->data,
by, code, inet, now, trf_root);
}
if (!ok) {
LOGERR("%s.failed.%s", id, DATA_TRANSFER(i_username)->data);
return strdup("failed.");
}
LOGDEBUG("%s.ok.%s", id, DATA_TRANSFER(i_username)->data);
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);
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(DATA_TRANSFER(i_username)->data);
K_RUNLOCK(users_free);
if (!u_item)
ok = false;
else {
if (strcasecmp(DATA_TRANSFER(i_passwordhash)->data, DATA_USERS(u_item)->passwordhash) == 0)
ok = true;
else
ok = false;
}
if (!ok) {
LOGERR("%s.failed.%s", id, DATA_TRANSFER(i_username)->data);
return strdup("failed.");
}
LOGDEBUG("%s.ok.%s", id, DATA_TRANSFER(i_username)->data);
return strdup("ok.");
}
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;
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, DATA_TRANSFER(i_poolinstance)->data);
row.createdate.tv_sec = date_eot.tv_sec;
row.createdate.tv_usec = date_eot.tv_usec;
look.data = (void *)(&row);
ps = find_before_in_ktree(poolstats_root, &look, cmp_poolstats, ctx);
if (!ps)
store = true;
else {
// Find last stored matching the poolinstance and less than STATS_PER old
while (ps && !DATA_POOLSTATS(ps)->stored &&
strcmp(row.poolinstance, DATA_POOLSTATS(ps)->poolinstance) == 0 &&
tvdiff(cd, &(DATA_POOLSTATS(ps)->createdate)) < STATS_PER) {
ps = prev_in_ktree(ctx);
}
if (!ps || !DATA_POOLSTATS(ps)->stored ||
strcmp(row.poolinstance, DATA_POOLSTATS(ps)->poolinstance) != 0 ||
tvdiff(cd, &(DATA_POOLSTATS(ps)->createdate)) >= STATS_PER)
store = true;
else
store = false;
}
ok = poolstats_add(conn, store, DATA_TRANSFER(i_poolinstance)->data,
DATA_TRANSFER(i_elapsed)->data,
DATA_TRANSFER(i_users)->data,
DATA_TRANSFER(i_workers)->data,
DATA_TRANSFER(i_hashrate)->data,
DATA_TRANSFER(i_hashrate5m)->data,
DATA_TRANSFER(i_hashrate1hr)->data,
DATA_TRANSFER(i_hashrate24hr)->data,
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(DATA_TRANSFER(i_idle)->data, TRUE_STR) == 0);
i_eos = optional_name(trf_root, "eos", 1, NULL);
if (!i_eos)
i_eos = &userstats_eos;
eos = (strcasecmp(DATA_TRANSFER(i_eos)->data, TRUE_STR) == 0);
ok = userstats_add(DATA_TRANSFER(i_poolinstance)->data,
DATA_TRANSFER(i_elapsed)->data,
DATA_TRANSFER(i_username)->data,
DATA_TRANSFER(i_workername)->data,
DATA_TRANSFER(i_hashrate)->data,
DATA_TRANSFER(i_hashrate5m)->data,
DATA_TRANSFER(i_hashrate1hr)->data,
DATA_TRANSFER(i_hashrate24hr)->data,
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_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);
row = DATA_IDCONTROL(look);
STRNCPY(row->idname, DATA_TRANSFER(i_idname)->data);
TXT_TO_BIGINT("idvalue", DATA_TRANSFER(i_idvalue)->data, 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, DATA_TRANSFER(i_idname)->data, row->lastid);
snprintf(reply, siz, "ok.added %s %"PRId64,
DATA_TRANSFER(i_idname)->data, 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 payments;
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(DATA_TRANSFER(i_username)->data);
K_RUNLOCK(users_free);
if (!u_item)
return strdup("bad");
payments.userid = DATA_USERS(u_item)->userid;
payments.paydate.tv_sec = 0;
payments.paydate.tv_usec = 0;
look.data = (void *)(&payments);
p_item = find_after_in_ktree(payments_root, &look, cmp_payments, ctx);
APPEND_REALLOC_INIT(buf, off, len);
APPEND_REALLOC(buf, off, len, "ok.");
rows = 0;
while (p_item && DATA_PAYMENTS(p_item)->userid == DATA_USERS(u_item)->userid) {
tv_to_buf(&(DATA_PAYMENTS(p_item)->paydate), reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "paydate%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
str_to_buf(DATA_PAYMENTS(p_item)->payaddress, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "payaddress%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
bigint_to_buf(DATA_PAYMENTS(p_item)->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);
}
snprintf(tmp, sizeof(tmp), "rows=%d", rows);
APPEND_REALLOC(buf, off, len, tmp);
LOGDEBUG("%s.ok.%s", id, DATA_TRANSFER(i_username)->data);
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, wlook, *u_item, *w_item, uslook, *us_item, *ws_item;
K_TREE_CTX w_ctx[1], us_ctx[1];
WORKERS workers;
USERSTATS userstats;
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(DATA_TRANSFER(i_username)->data);
K_RUNLOCK(users_free);
if (!u_item)
return strdup("bad");
i_stats = optional_name(trf_root, "stats", 1, NULL);
if (!i_stats)
stats = false;
else
stats = (strcasecmp(DATA_TRANSFER(i_stats)->data, TRUE_STR) == 0);
workers.userid = DATA_USERS(u_item)->userid;
workers.workername[0] = '\0';
workers.expirydate.tv_sec = 0;
workers.expirydate.tv_usec = 0;
wlook.data = (void *)(&workers);
w_item = find_after_in_ktree(workers_root, &wlook, cmp_workers, w_ctx);
APPEND_REALLOC_INIT(buf, off, len);
APPEND_REALLOC(buf, off, len, "ok.");
rows = 0;
while (w_item && DATA_WORKERS(w_item)->userid == DATA_USERS(u_item)->userid) {
if (tvdiff(&(DATA_WORKERS(w_item)->expirydate), (tv_t *)&default_expiry) == 0.0) {
str_to_buf(DATA_WORKERS(w_item)->workername, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "workername%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
int_to_buf(DATA_WORKERS(w_item)->difficultydefault, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "difficultydefault%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
str_to_buf(DATA_WORKERS(w_item)->idlenotificationenabled, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "idlenotificationenabled%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
int_to_buf(DATA_WORKERS(w_item)->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_hashrate5m = w_hashrate1hr = 0.0;
w_elapsed = -1;
w_lastshare.tv_sec = 0;
w_lastdiff = 0;
ws_item = find_workerstatus(DATA_USERS(u_item)->userid,
DATA_WORKERS(w_item)->workername);
if (ws_item) {
w_lastshare.tv_sec = DATA_WORKERSTATUS(ws_item)->last_share.tv_sec;
w_lastdiff = DATA_WORKERSTATUS(ws_item)->last_diff;
}
// find last stored userid record
userstats.userid = DATA_USERS(u_item)->userid;
userstats.statsdate.tv_sec = date_eot.tv_sec;
userstats.statsdate.tv_usec = date_eot.tv_usec;
// find/cmp doesn't get to here
userstats.poolinstance[0] = '\0';
userstats.workername[0] = '\0';
uslook.data = (void *)(&userstats);
K_RLOCK(userstats_free);
us_item = find_before_in_ktree(userstats_root, &uslook, cmp_userstats, us_ctx);
while (us_item && DATA_USERSTATS(us_item)->userid == userstats.userid) {
if (strcmp(DATA_USERSTATS(us_item)->workername, DATA_WORKERS(w_item)->workername) == 0) {
if (tvdiff(now, &(DATA_USERSTATS(us_item)->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 += DATA_USERSTATS(us_item)->hashrate5m;
w_hashrate1hr += DATA_USERSTATS(us_item)->hashrate1hr;
if (w_elapsed == -1 || w_elapsed > DATA_USERSTATS(us_item)->elapsed)
w_elapsed = DATA_USERSTATS(us_item)->elapsed;
userstats_workername_root = add_to_ktree(userstats_workername_root,
us_item,
cmp_userstats_workername);
}
} else
break;
}
us_item = prev_in_ktree(us_ctx);
}
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((int)(w_lastdiff), reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "w_lastdiff%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);
}
snprintf(tmp, sizeof(tmp), "rows=%d", rows);
APPEND_REALLOC(buf, off, len, tmp);
LOGDEBUG("%s.ok.%s", id, DATA_TRANSFER(i_username)->data);
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];
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);
// 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);
while (us_item && tvdiff(now, &(DATA_USERSTATS(us_item)->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(usw_item)->userid = DATA_USERSTATS(us_item)->userid;
strcpy(DATA_USERSTATS(usw_item)->workername, DATA_USERSTATS(us_item)->workername);
DATA_USERSTATS(usw_item)->hashrate5m = DATA_USERSTATS(us_item)->hashrate5m;
DATA_USERSTATS(usw_item)->hashrate1hr = DATA_USERSTATS(us_item)->hashrate1hr;
userstats_workername_root = add_to_ktree(userstats_workername_root, usw_item, cmp_userstats_workername);
}
us_item = prev_in_ktree(us_ctx);
}
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) {
if (DATA_USERSTATS(usw_item)->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 {
str_to_buf(DATA_USERS(u_item)->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 = DATA_USERSTATS(usw_item)->userid;
u_hashrate5m = 0;
u_hashrate1hr = 0;
}
u_hashrate5m += DATA_USERSTATS(usw_item)->hashrate5m;
u_hashrate1hr += DATA_USERSTATS(usw_item)->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 {
str_to_buf(DATA_USERS(u_item)->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", rows);
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", DATA_TRANSFER(i_workinfoid)->data, 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, DATA_TRANSFER(i_workinfoid)->data,
DATA_TRANSFER(i_poolinstance)->data,
DATA_TRANSFER(i_transactiontree)->data,
DATA_TRANSFER(i_merklehash)->data,
DATA_TRANSFER(i_prevhash)->data,
DATA_TRANSFER(i_coinbase1)->data,
DATA_TRANSFER(i_coinbase2)->data,
DATA_TRANSFER(i_version)->data,
DATA_TRANSFER(i_bits)->data,
DATA_TRANSFER(i_ntime)->data,
DATA_TRANSFER(i_reward)->data,
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_enonce1;
K_ITEM *i_nonce2, *i_nonce, *i_diff, *i_sdiff, *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", DATA_TRANSFER(i_workinfoid)->data, 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_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, DATA_TRANSFER(i_workinfoid)->data,
DATA_TRANSFER(i_username)->data,
DATA_TRANSFER(i_workername)->data,
DATA_TRANSFER(i_clientid)->data,
DATA_TRANSFER(i_enonce1)->data,
DATA_TRANSFER(i_nonce2)->data,
DATA_TRANSFER(i_nonce)->data,
DATA_TRANSFER(i_diff)->data,
DATA_TRANSFER(i_sdiff)->data,
DATA_TRANSFER(i_secondaryuserid)->data,
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, DATA_TRANSFER(i_nonce)->data);
sconf:
snprintf(reply, siz, "ok.added %s", DATA_TRANSFER(i_nonce)->data);
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", DATA_TRANSFER(i_workinfoid)->data, 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, DATA_TRANSFER(i_workinfoid)->data,
DATA_TRANSFER(i_username)->data,
DATA_TRANSFER(i_workername)->data,
DATA_TRANSFER(i_clientid)->data,
DATA_TRANSFER(i_errn)->data,
DATA_TRANSFER(i_error)->data,
DATA_TRANSFER(i_secondaryuserid)->data,
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, DATA_TRANSFER(i_username)->data);
seconf:
snprintf(reply, siz, "ok.added %s", DATA_TRANSFER(i_username)->data);
return strdup(reply);
} else if (strcasecmp(cmd, STR_AGEWORKINFO) == 0) {
K_ITEM *i_workinfoid, *i_poolinstance;
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", DATA_TRANSFER(i_workinfoid)->data, 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);
ok = workinfo_age(conn, DATA_TRANSFER(i_workinfoid)->data,
DATA_TRANSFER(i_poolinstance)->data,
by, code, inet, cd);
if (!ok) {
LOGERR("%s(%s) %s.failed.DATA", __func__, cmd, id);
return strdup("failed.DATA");
}
LOGDEBUG("%s.ok.aged %.*s",
id, BIGINT_BUFSIZ,
DATA_TRANSFER(i_workinfoid)->data);
awconf:
snprintf(reply, siz, "ok.%.*s",
BIGINT_BUFSIZ,
DATA_TRANSFER(i_workinfoid)->data);
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;
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(i_confirmed)->data[0] = tolower(DATA_TRANSFER(i_confirmed)->data[0]);
switch(DATA_TRANSFER(i_confirmed)->data[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, DATA_TRANSFER(i_height)->data,
DATA_TRANSFER(i_blockhash)->data,
DATA_TRANSFER(i_confirmed)->data,
DATA_TRANSFER(i_workinfoid)->data,
DATA_TRANSFER(i_username)->data,
DATA_TRANSFER(i_workername)->data,
DATA_TRANSFER(i_clientid)->data,
DATA_TRANSFER(i_enonce1)->data,
DATA_TRANSFER(i_nonce2)->data,
DATA_TRANSFER(i_nonce)->data,
DATA_TRANSFER(i_reward)->data,
by, code, inet, cd, igndup, id,
trf_root);
break;
case BLOCKS_CONFIRM:
msg = "confirmed";
ok = blocks_add(conn, DATA_TRANSFER(i_height)->data,
DATA_TRANSFER(i_blockhash)->data,
DATA_TRANSFER(i_confirmed)->data,
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, DATA_TRANSFER(i_confirmed)->data);
return strdup("failed.DATA");
}
if (!ok) {
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;
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);
secuserid = auths_add(conn, DATA_TRANSFER(i_poolinstance)->data,
DATA_TRANSFER(i_username)->data,
DATA_TRANSFER(i_workername)->data,
DATA_TRANSFER(i_clientid)->data,
DATA_TRANSFER(i_enonce1)->data,
DATA_TRANSFER(i_useragent)->data,
by, code, inet, cd, igndup, trf_root);
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_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 userstats;
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);
} else {
snprintf(tmp, sizeof(tmp), "lastbc=?%c", FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
}
// TODO: handle orphans
b_item = last_in_ktree(blocks_root, ctx);
if (b_item) {
tvs_to_buf(&(DATA_BLOCKS(b_item)->createdate), reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "lastblock=%s%cconfirmed=%s%c",
reply, FLDSEP,
DATA_BLOCKS(b_item)->confirmed, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
} else {
snprintf(tmp, sizeof(tmp), "lastblock=?%cconfirmed=?%c", 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.differr, 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) {
int_to_buf(DATA_POOLSTATS(p_item)->users, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "users=%s%c", reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
int_to_buf(DATA_POOLSTATS(p_item)->workers, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "workers=%s%c", reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(DATA_POOLSTATS(p_item)->hashrate5m, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "p_hashrate5m=%s%c", reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(DATA_POOLSTATS(p_item)->hashrate1hr, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "p_hashrate1hr=%s%c", reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
bigint_to_buf(DATA_POOLSTATS(p_item)->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(DATA_TRANSFER(i_username)->data);
K_RUNLOCK(users_free);
}
has_uhr = false;
if (p_item && u_item) {
K_TREE *userstats_workername_root = new_ktree();
u_hashrate5m = u_hashrate1hr = 0.0;
u_elapsed = -1;
// find last stored userid record
userstats.userid = DATA_USERS(u_item)->userid;
userstats.statsdate.tv_sec = date_eot.tv_sec;
userstats.statsdate.tv_usec = date_eot.tv_usec;
// find/cmp doesn't get to here
STRNCPY(userstats.poolinstance, EMPTY);
STRNCPY(userstats.workername, EMPTY);
look.data = (void *)(&userstats);
K_RLOCK(userstats_free);
us_item = find_before_in_ktree(userstats_root, &look, cmp_userstats, ctx);
while (us_item &&
DATA_USERSTATS(us_item)->userid == userstats.userid &&
tvdiff(now, &(DATA_USERSTATS(us_item)->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 (DATA_USERSTATS(us_item)->poolinstance[0] &&
!find_in_ktree(userstats_workername_root, us_item, cmp_userstats_workername, w_ctx)) {
u_hashrate5m += DATA_USERSTATS(us_item)->hashrate5m;
u_hashrate1hr += DATA_USERSTATS(us_item)->hashrate1hr;
if (u_elapsed == -1 ||
u_elapsed > DATA_USERSTATS(us_item)->elapsed)
u_elapsed = DATA_USERSTATS(us_item)->elapsed;
has_uhr = true;
userstats_workername_root = add_to_ktree(userstats_workername_root,
us_item,
cmp_userstats_workername);
}
us_item = prev_in_ktree(ctx);
}
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 ? DATA_TRANSFER(i_username)->data : "N");
return buf;
}
static char *cmd_dsp(__maybe_unused PGconn *conn, __maybe_unused char *cmd,
char *id, __maybe_unused tv_t *now,
__maybe_unused char *by, __maybe_unused char *code,
__maybe_unused char *inet, __maybe_unused tv_t *notcd,
__maybe_unused K_TREE *trf_root)
{
__maybe_unused K_ITEM *i_file;
__maybe_unused char reply[1024] = "";
__maybe_unused size_t siz = sizeof(reply);
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
// WARNING: This is a gaping security hole - only use in development
LOGDEBUG("%s.disabled.dsp", id);
return strdup("disabled.dsp");
/*
i_file = require_name(trf_root, "file", 1, NULL, reply, siz);
if (!i_file)
return strdup(reply);
dsp_ktree(transfer_free, trf_root, DATA_TRANSFER(i_file)->data, NULL);
dsp_ktree(sharesummary_free, sharesummary_root, DATA_TRANSFER(i_file)->data, NULL);
dsp_ktree(userstats_free, userstats_root, DATA_TRANSFER(i_file)->data, NULL);
LOGDEBUG("%s.ok.dsp.file='%s'", id, DATA_TRANSFER(i_file)->data);
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 i = 0;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
APPEND_REALLOC_INIT(buf, off, len);
APPEND_REALLOC(buf, off, len, "ok.");
APPEND_REALLOC(buf, off, len, cmd);
// 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), \
"%cname%d=" #_obj "%callocated%d=%d%cstore%d=%d" \
"%ctrees%d=%d%cram%d=%"PRIu64, \
i ? FLDSEP : '.', i, \
FLDSEP, i, klist->total, \
FLDSEP, i, klist->total - klist->count, \
FLDSEP, i, _trees, \
FLDSEP, i, ram); \
APPEND_REALLOC(buf, off, len, tmp); \
tot += ram; \
i++;
USEINFO(users, 1, 2);
USEINFO(workers, 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), "%ctotalram=%"PRIu64, FLDSEP, tot);
APPEND_REALLOC(buf, off, len, tmp);
LOGDEBUG("%s.ok.%s...", id, cmd);
return buf;
}
// TODO: limit access
#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
*/
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_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_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_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_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_DSP, "dsp", false, false, cmd_dsp, ACCESS_SYSTEM },
{ CMD_STATS, "stats", true, false, cmd_stats, 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] = "";
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);
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(DATA_TRANSFER(item)->value))
DATA_TRANSFER(item)->data = strdup(json_str);
else {
STRNCPY(DATA_TRANSFER(item)->value, json_str);
DATA_TRANSFER(item)->data = DATA_TRANSFER(item)->value;
}
break;
case JSON_REAL:
snprintf(DATA_TRANSFER(item)->value,
sizeof(DATA_TRANSFER(item)->value),
"%f", json_real_value(json_value));
DATA_TRANSFER(item)->data = DATA_TRANSFER(item)->value;
break;
case JSON_INTEGER:
snprintf(DATA_TRANSFER(item)->value,
sizeof(DATA_TRANSFER(item)->value),
"%"PRId64,
(int64_t)json_integer_value(json_value));
DATA_TRANSFER(item)->data = DATA_TRANSFER(item)->value;
break;
case JSON_TRUE:
case JSON_FALSE:
snprintf(DATA_TRANSFER(item)->value,
sizeof(DATA_TRANSFER(item)->value),
"%s", (json_typ == JSON_TRUE) ?
TRUE_STR : FALSE_STR);
DATA_TRANSFER(item)->data = DATA_TRANSFER(item)->value;
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(DATA_TRANSFER(item)->data, 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(DATA_TRANSFER(item)->data,
off, len, " ");
}
APPEND_REALLOC(DATA_TRANSFER(item)->data,
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(DATA_TRANSFER(item)->name, json_key);
if (!ok || find_in_ktree(*trf_root, item, cmp_transfer, ctx)) {
if (DATA_TRANSFER(item)->data != DATA_TRANSFER(item)->value)
free(DATA_TRANSFER(item)->data);
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);
STRNCPY(DATA_TRANSFER(item)->name, data);
STRNCPY(DATA_TRANSFER(item)->value, eq);
DATA_TRANSFER(item)->data = DATA_TRANSFER(item)->value;
if (find_in_ktree(*trf_root, item, cmp_transfer, ctx)) {
if (DATA_TRANSFER(item)->data != DATA_TRANSFER(item)->value)
free(DATA_TRANSFER(item)->data);
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;
txt_to_ctv("createdate", DATA_TRANSFER(item)->data, cd, sizeof(*cd));
if (cd->tv_sec == 0) {
LOGERR("%s(): failed, %s has invalid createdate '%s'",
__func__, cmdptr, DATA_TRANSFER(item)->data);
free(cmdptr);
return CMD_REPLY;
}
}
free(cmdptr);
return cmds[*which_cmds].cmd_val;
}
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;
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;
copy_tv(&process, &DATA_USERSTATS(last)->statsdate);
}
first = first_in_ktree(userstats_statsdate_root, ctx);
// Oldest non DB stat
// TODO: make the index start with summarylevel? so can find faster
while (first && DATA_USERSTATS(first)->summarylevel[0] != SUMMARY_NONE)
first = next_in_ktree(ctx);
if (!first)
break;
statrange = tvdiff(&process, &(DATA_USERSTATS(first)->statsdate));
// Is there data ready for summarising?
if (statrange <= USERSTATS_AGE)
break;
copy_tv(&when, &(DATA_USERSTATS(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);
userstats = DATA_USERSTATS(new);
memcpy(userstats, DATA_USERSTATS(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) {
statrange = tvdiff(&when, &(DATA_USERSTATS(next)->statsdate));
if (statrange <= 0)
break;
tmp = next_in_ktree(ctx);
if (DATA_USERSTATS(next)->summarylevel[0] == SUMMARY_NONE &&
DATA_USERSTATS(next)->userid == userstats->userid &&
strcmp(DATA_USERSTATS(next)->workername, userstats->workername) == 0) {
count++;
userstats->hashrate += DATA_USERSTATS(next)->hashrate;
userstats->hashrate5m += DATA_USERSTATS(next)->hashrate5m;
userstats->hashrate1hr += DATA_USERSTATS(next)->hashrate1hr;
userstats->hashrate24hr += DATA_USERSTATS(next)->hashrate24hr;
if (userstats->elapsed > DATA_USERSTATS(next)->elapsed)
userstats->elapsed = DATA_USERSTATS(next)->elapsed;
userstats->summarycount += DATA_USERSTATS(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(13);
if (!everyone_die)
summarise_poolstats();
if (!everyone_die)
summarise_userstats();
}
return NULL;
}
static void *logger(__maybe_unused void *arg)
{
K_ITEM *lq_item;
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) {
LOGFILE(DATA_LOGQUEUE(lq_item)->msg);
free(DATA_LOGQUEUE(lq_item)->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)))
LOGFILE(DATA_LOGQUEUE(lq_item)->msg);
K_WUNLOCK(logqueue_free);
setnow(&now);
snprintf(buf, sizeof(buf), "logstop.%ld,%ld",
now.tv_sec, now.tv_usec);
LOGFILE(buf);
return NULL;
}
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_chkpass = NULL, *reply_chkpass = NULL;
char *last_adduser = NULL, *reply_adduser = NULL;
char *last_newpass = NULL, *reply_newpass = NULL;
char *last_newid = NULL, *reply_newid = 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;
K_STORE *trf_store;
K_TREE *trf_root;
K_ITEM *item;
size_t siz;
tv_t now, cd;
bool dup, want_first;
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 {
if (want_first) {
ck_wlock(&fpm_lock);
first_pool_message = strdup(buf);
ck_wunlock(&fpm_lock);
want_first = false;
}
/* 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
* adduser duplicates are handled by the DB code
* auth, chkpass, adduser, newpass, newid -
* remember individual last message and reply and repeat
* the reply without reprocessing the message
*/
dup = false;
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_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;
// Always process immediately:
case CMD_AUTH:
case CMD_CHKPASS:
case CMD_ADDUSER:
case CMD_NEWPASS:
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:
if (last_auth)
free(last_auth);
last_auth = buf;
buf = NULL;
if (reply_auth)
free(reply_auth);
reply_auth = rep;
break;
case CMD_CHKPASS:
if (last_chkpass)
free(last_chkpass);
last_chkpass = buf;
buf = NULL;
if (reply_chkpass)
free(reply_chkpass);
reply_chkpass = rep;
break;
case CMD_ADDUSER:
if (last_adduser)
free(last_adduser);
last_adduser = buf;
buf = NULL;
if (reply_adduser)
free(reply_adduser);
reply_adduser = rep;
break;
case CMD_NEWPASS:
if (last_newpass)
free(last_newpass);
last_newpass = buf;
buf = NULL;
if (reply_newpass)
free(reply_newpass);
reply_newpass = rep;
break;
case CMD_NEWID:
if (last_newid)
free(last_newid);
last_newid = buf;
buf = NULL;
if (reply_newid)
free(reply_newid);
reply_newid = rep;
break;
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_DSP:
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:
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);
workqueue = DATA_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) {
if (DATA_TRANSFER(item)->data != DATA_TRANSFER(item)->value)
free(DATA_TRANSFER(item)->data);
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);
}
}
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;
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:
// Non pool commands, shouldn't be there
case CMD_ADDUSER:
case CMD_NEWPASS:
case CMD_CHKPASS:
case CMD_NEWID:
case CMD_PAYMENTS:
case CMD_WORKERS:
case CMD_ALLUSERS:
case CMD_HOMEPAGE:
case CMD_DSP:
case CMD_STATS:
LOGERR("%s() Message line %"PRIu64" '%s' - invalid - ignored",
__func__, count, cmd);
break;
case CMD_AUTH:
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) {
if (DATA_TRANSFER(item)->data != DATA_TRANSFER(item)->value)
free(DATA_TRANSFER(item)->data);
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;
}
// Log files are every ...
#define ROLL_S 3600
// 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;
tv_to_buf(start, buf, sizeof(buf));
LOGWARNING("%s(): from %s", __func__, buf);
filename = rotating_filename(restorefrom, start->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 (!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(): read %"PRIu64" line%s from %s",
__func__,
count, count == 1 ? "" : "s",
filename);
total += count;
fclose(fp);
free(filename);
if (matched)
break;
start->tv_sec += ROLL_S;
if (confirm_sharesummary && tv_newer(&confirm_finish, start))
break;
filename = rotating_filename(restorefrom, start->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) {
start->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(start, &now)) {
finished = true;
break;
}
filename = rotating_filename(restorefrom, start->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;
K_ITEM *item;
char *ans;
workqueue = DATA_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) {
if (DATA_TRANSFER(item)->data != DATA_TRANSFER(item)->value)
free(DATA_TRANSFER(item)->data);
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);
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;
}
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);
}
}
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 sharesummary;
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];
sharesummary.workinfoid = confirm_first_workinfoid;
sharesummary.userid = -1;
sharesummary.workername[0] = '\0';
look.data = (void *)(&sharesummary);
total = ok = missing = diff = 0;
lss = find_after_in_ktree(leftsum, &look, cmp_sharesummary_workinfoid, ctxl);
while (lss) {
if (DATA_SHARESUMMARY(lss)->workinfoid > confirm_last_workinfoid)
break;
total++;
if (first_used == 0)
first_used = DATA_SHARESUMMARY(lss)->workinfoid;
last_used = DATA_SHARESUMMARY(lss)->workinfoid;
rss = find_in_ktree(rightsum, lss, cmp_sharesummary_workinfoid, ctxr);
if (!rss) {
missing++;
if (miss_first == 0) {
miss_first = DATA_SHARESUMMARY(lss)->workinfoid;
copy_tv(&miss_first_cd, &(DATA_SHARESUMMARY(lss)->createdate));
}
miss_last = DATA_SHARESUMMARY(lss)->workinfoid;
copy_tv(&miss_last_cd, &(DATA_SHARESUMMARY(lss)->createdate));
if (show_missing) {
LOGERR("ERROR: %s %"PRId64"/%s/%ld,%ld %.19s missing from %s",
leftname,
DATA_SHARESUMMARY(lss)->workinfoid,
DATA_SHARESUMMARY(lss)->workername,
DATA_SHARESUMMARY(lss)->createdate.tv_sec,
DATA_SHARESUMMARY(lss)->createdate.tv_usec,
tv_to_buf(&(DATA_SHARESUMMARY(lss)->createdate), cd_buf, sizeof(cd_buf)),
rightname);
}
} else if (DATA_SHARESUMMARY(rss)->diffacc != DATA_SHARESUMMARY(lss)->diffacc) {
diff++;
if (diff_first == 0) {
diff_first = DATA_SHARESUMMARY(lss)->workinfoid;
copy_tv(&diff_first_cd, &(DATA_SHARESUMMARY(lss)->createdate));
}
diff_last = DATA_SHARESUMMARY(lss)->workinfoid;
copy_tv(&diff_last_cd, &(DATA_SHARESUMMARY(lss)->createdate));
if (show_diff) {
LOGERR("ERROR: %"PRId64"/%s/%ld,%ld %.19s - diffacc: %s: %.0f %s: %.0f",
DATA_SHARESUMMARY(lss)->workinfoid,
DATA_SHARESUMMARY(lss)->workername,
DATA_SHARESUMMARY(lss)->createdate.tv_sec,
DATA_SHARESUMMARY(lss)->createdate.tv_usec,
tv_to_buf(&(DATA_SHARESUMMARY(lss)->createdate), cd_buf, sizeof(cd_buf)),
leftname,
DATA_SHARESUMMARY(lss)->diffacc,
rightname,
DATA_SHARESUMMARY(rss)->diffacc);
}
} else
ok++;
lss = next_in_ktree(ctxl);
}
LOGERR("RESULT: %s->%s Total %"PRIu64" workinfoid %"PRId64"-%"PRId64
" missing: %"PRIu64" different: %"PRIu64,
leftname, rightname, total, first_used, last_used,
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 (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 look, *wi_item, *wif_item, *wil_item;
K_ITEM *b_begin_item, *b_end_item;
K_ITEM *ss_begin_item, *ss_end_item;
WORKINFO workinfo;
BLOCKS blocks;
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;
}
tv_to_buf(&(DATA_WORKINFO(wif_item)->createdate),
cd_buf, sizeof(cd_buf));
LOGWARNING("%s(): DB first workinfoid %"PRId64" %s",
__func__, DATA_WORKINFO(wif_item)->workinfoid, cd_buf);
tv_to_buf(&(DATA_WORKINFO(wil_item)->createdate),
cd_buf, sizeof(cd_buf));
LOGWARNING("%s(): DB last workinfoid %"PRId64" %s",
__func__, DATA_WORKINFO(wil_item)->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 {
tv_to_buf(&(DATA_BLOCKS(b_begin_item)->createdate),
cd_buf, sizeof(cd_buf));
LOGWARNING("%s(): DB first block %d/%"PRId64" %s",
__func__,
DATA_BLOCKS(b_begin_item)->height,
DATA_BLOCKS(b_begin_item)->workinfoid,
cd_buf);
tv_to_buf(&(DATA_BLOCKS(b_end_item)->createdate),
cd_buf, sizeof(cd_buf));
LOGWARNING("%s(): DB last block %d/%"PRId64" %s",
__func__,
DATA_BLOCKS(b_end_item)->height,
DATA_BLOCKS(b_end_item)->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 {
tv_to_buf(&(DATA_SHARESUMMARY(ss_begin_item)->createdate),
cd_buf, sizeof(cd_buf));
LOGWARNING("%s(): DB first sharesummary %"PRId64"/%s %s",
__func__,
DATA_SHARESUMMARY(ss_begin_item)->workinfoid,
DATA_SHARESUMMARY(ss_begin_item)->workername,
cd_buf);
tv_to_buf(&(DATA_SHARESUMMARY(ss_end_item)->createdate),
cd_buf, sizeof(cd_buf));
LOGWARNING("%s(): DB last sharesummary %"PRId64"/%s %s",
__func__,
DATA_SHARESUMMARY(ss_end_item)->workinfoid,
DATA_SHARESUMMARY(ss_end_item)->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
* TODO: auto aging will clear 'n' sections inside the 'a's that
* will always occur when ckpool restarts, since ckpool will never
* send workinfo age records for workinfo that's active at shutdown -
* Aging these can (for now) easily be done manually in psql */
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;
}
// 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
blocks.height = confirm_block;
blocks.blockhash[0] = '\0';
look.data = (void *)(&blocks);
b_end_item = find_after_in_ktree(blocks_root, &look, cmp_blocks, ctx);
if (!b_end_item) {
LOGWARNING("%s(): no DB block height found matching or after %d",
__func__, confirm_block);
return;
}
confirm_last_workinfoid = DATA_BLOCKS(b_end_item)->workinfoid;
// Now find the last DB record of the previous block
blocks.height = DATA_BLOCKS(b_end_item)->height;
blocks.blockhash[0] = '\0';
look.data = (void *)(&blocks);
b_begin_item = find_before_in_ktree(blocks_root, &look, cmp_blocks, ctx);
if (!b_begin_item)
confirm_first_workinfoid = 0;
else {
// First DB record of the block 'begin'
blocks.height = DATA_BLOCKS(b_begin_item)->height;
blocks.blockhash[0] = '\0';
look.data = (void *)(&blocks);
b_begin_item = find_after_in_ktree(blocks_root, &look, cmp_blocks, ctx);
// Not possible
if (!b_begin_item)
confirm_first_workinfoid = 0;
else
confirm_first_workinfoid = DATA_BLOCKS(b_begin_item)->workinfoid;
}
snprintf(first_buf, sizeof(first_buf),
"block %d",
b_begin_item ? DATA_BLOCKS(b_begin_item)->height : 0);
first_reason = first_buf;
snprintf(last_buf, sizeof(last_buf),
"block %d",
DATA_BLOCKS(b_end_item)->height);
last_reason = last_buf;
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;
case 'r':
confirm_first_workinfoid = confirm_range_start;
confirm_last_workinfoid = confirm_range_finish;
first_reason = "start range";
last_reason = "end range";
break;
case 'i':
LOGWARNING("%s(): info displayed - exiting", __func__);
exit(0);
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 */
workinfo.workinfoid = confirm_first_workinfoid + 1;
workinfo.expirydate.tv_sec = date_begin.tv_sec;
workinfo.expirydate.tv_usec = date_begin.tv_usec;
look.data = (void *)(&workinfo);
wi_item = find_before_in_ktree(workinfo_root, &look, cmp_workinfo, ctx);
if (wi_item) {
copy_tv(&start, &(DATA_WORKINFO(wi_item)->createdate));
if (DATA_WORKINFO(wi_item)->workinfoid != confirm_first_workinfoid) {
LOGWARNING("%s() start workinfo not found ... using time of %"PRId64,
__func__, DATA_WORKINFO(wi_item)->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 */
workinfo.workinfoid = confirm_last_workinfoid - 1;
workinfo.expirydate.tv_sec = date_eot.tv_sec;
workinfo.expirydate.tv_usec = date_eot.tv_usec;
look.data = (void *)(&workinfo);
wi_item = find_after_in_ktree(workinfo_root, &look, cmp_workinfo, ctx);
if (wi_item) {
/* Now find the one after the one we found to determine the
* confirm_finish timestamp */
workinfo.workinfoid = DATA_WORKINFO(wi_item)->workinfoid;
workinfo.expirydate.tv_sec = date_eot.tv_sec;
workinfo.expirydate.tv_usec = date_eot.tv_usec;
look.data = (void *)(&workinfo);
wi_item = find_after_in_ktree(workinfo_root, &look, cmp_workinfo, ctx);
if (wi_item) {
copy_tv(&confirm_finish, &(DATA_WORKINFO(wi_item)->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);
/* 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;
}
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) {
if (confirm_range[0] != 'i' && strlen(confirm_range) < 2) {
LOGEMERG("%s() invalid confirm range length '%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 '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;
}
case 'r':
range = strdup(confirm_range);
minus = strchr(range+1, '-');
if (!minus || minus == range+1) {
LOGEMERG("%s() invalid confirm range '%s' - must be rNNN-MMM",
__func__, confirm_range);
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 'i':
break;
default:
LOGEMERG("%s() invalid confirm range '%s'",
__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();
}
#define RELOADFILES "ckdb"
static void check_restore_dir()
{
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)+sizeof(RELOADFILES));
if (!restorefrom)
quithere(1, "OOM");
strcat(restorefrom, RELOADFILES);
}
static struct option long_options[] = {
{ "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' },
{ "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 }
};
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: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 'n':
ckp.name = strdup(optarg);
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 'r':
restorefrom = 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 'p':
db_pass = strdup(optarg);
kill = optarg;
if (*kill)
*(kill++) = ' ';
while (*kill)
*(kill++) = '\0';
break;
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 = "";
check_restore_dir();
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);
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);
srand((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_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;
}