13296 lines
369 KiB

/*
* Copyright 1995-2014 Andrew Smith
* Copyright 2014 Con Kolivas
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version. See COPYING for more details.
*/
#include "config.h"
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fenv.h>
#include <getopt.h>
#include <jansson.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <stdint.h>
#include <regex.h>
#include <sha2.h>
#ifdef HAVE_LIBPQ_FE_H
#include <libpq-fe.h>
#elif defined (HAVE_POSTGRESQL_LIBPQ_FE_H)
#include <postgresql/libpq-fe.h>
#endif
#include "ckpool.h"
#include "libckpool.h"
#include "klist.h"
#include "ktree.h"
/* TODO: any tree/list accessed in new threads needs
* to ensure all code using those trees/lists use locks
* This code's lock implementation is equivalent to table level locking
* Consider adding row level locking (a per kitem usage count) if needed
* TODO: verify all tables with multithread access are locked
*/
#define DB_VLOCK "1"
#define DB_VERSION "0.9.2"
#define CKDB_VERSION DB_VERSION"-0.302"
#define WHERE_FFL " - from %s %s() line %d"
#define WHERE_FFL_HERE __FILE__, __func__, __LINE__
#define WHERE_FFL_PASS file, func, line
#define WHERE_FFL_ARGS __maybe_unused const char *file, \
__maybe_unused const char *func, \
__maybe_unused const int line
#define STRINT(x) STRINT2(x)
#define STRINT2(x) #x
// So they can fit into a 1 byte flag field
#define TRUE_STR "Y"
#define FALSE_STR "N"
#define TRUE_CHR 'Y'
#define FALSE_CHR 'N'
#define coinbase1height(_cb1) _coinbase1height(_cb1, WHERE_FFL_HERE)
#define cmp_height(_cb1a, _cb1b) _cmp_height(_cb1a, _cb1b, WHERE_FFL_HERE)
static char *EMPTY = "";
static char *db_name;
static char *db_user;
static char *db_pass;
// Currently hard coded at 4 characters
static char *status_chars = "|/-\\";
static char *restorefrom;
/* Startup
* -------
* During startup we load the DB and track where it is up to with
* dbstatus, we then reload "ckpool's ckdb logfiles" (CCLs) based
* on dbstatus
* Once the DB is loaded, we can immediately start receiving ckpool
* messages since ckpool already has logged all messages to the CLLs
* and ckpool only verifies authorise responses
* Thus we can queue all messages:
* workinfo, shares, shareerror, ageworkinfo, poolstats, userstats
* and block
* with an ok.queued reply to ckpool, to be processed after the reload
* completes and just process authorise messages immediately while the
* reload runs
* This can't cause a duplicate process of an authorise message since a
* reload will ignore any messages before the last DB auths message,
* however, if ckdb and ckpool get out of sync due to ckpool starting
* during the reload (as mentioned below) it is possible for ckdb to
* find an authorise message in the CCLs that was processed in the
* message queue and thus is already in the DB.
* This error would be very rare and also not an issue
* To avoid this, we start the ckpool message queue after loading
* the users, auths, idcontrol and workers DB tables, before loading the
* much larger sharesummary, workinfo, userstats and poolstats DB tables
* so that ckdb is effectively ready for messages almost immediately
* The first ckpool message also allows us to know where ckpool is up to
* in the CCLs and thus where to stop processing the CCLs to stay in
* sync with ckpool
* If ckpool isn't running, then the reload will complete at the end of
* the last CCL file, however if the 1st message arrives from ckpool while
* processing the CCLs, that will mark the point where to stop processing
* but can also produce a fatal error at the end of processing, reporting
* the ckpool message, if the message was not found in the CCL processing
* after the message was received
* This can be caused by two circumstances:
* 1) the disk had not yet written it to the CCL when ckdb read EOF and
* ckpool was started at about the same time as the reload completed.
* This can be seen if the message displayed in the fatal error IS NOT
* in ckdb's message logfile.
* A ckdb restart will resolve this
* 2) ckpool was started at the time of the end of the reload, but the
* message was written to disk and found in the CCL before it was
* processed in the message queue.
* This can be seen if the message displayed in the fatal error IS in
* ckdb's message logfile and means the messages after it in ckdb's
* message logfile have already been processed.
* Again, a ckdb restart will resolve this
* In both the above (very rare) cases, if ckdb was to continue running,
* it would break the synchronisation and could cause DB problems, so
* ckdb aborting and needing a complete restart resolves it
* The users table, required for the authorise messages, is always updated
* immediately and is not affected by ckpool messages until we
* During the reload, when checking the timeframe for summarisation, we
* use the current last userstats createdate as 'now' to avoid touching a
* timeframe where data could still be waiting to be loaded
*/
/* Reload data needed
* ------------------
* After the DB load completes, load "ckpool's ckdb logfile" (CCL), and
* all later CCLs, that contains the oldest date of all of the following:
* RAM shares: oldest DB sharesummary firstshare where complete='n'
* All shares before this have been summarised to the DB with
* complete='a' (or 'y') and were deleted from RAM
* If there are none with complete='n' but are others in the DB,
* then the newest firstshare is used
* RAM shareerrors: as above
* DB+RAM sharesummary: created from shares, so as above
* Some shares after this may have been summarised to other
* sharesummary complete='n', but for any such sharesummary
* we reset it back to the first share found and it will
* correct itself during the CCL reload
* TODO: Verify that all DB sharesummaries with complete='n'
* have done this
* DB+RAM workinfo: start from newest DB createdate workinfo
* DB+RAM auths: start from newest DB createdate auths
* DB+RAM poolstats: newest createdate poolstats
* TODO: subtract how much we need in RAM of the 'between'
* non db records - will depend on TODO: pool stats reporting
* requirements
* DB+RAM userstats: start of the time band of the latest DB record,
* since all data before this has been summarised to the DB
* The userstats summarisation always processes the oldest
* RAM data to the DB
* TODO: multiple pools is not yet handled by ckdb
* TODO: handle a pool restart with a different instance name
* since this would always make the userstats reload point
* just after the instance name was changed - however
* this can be handled for now by simply ignoring the
* poolinstance
* DB+RAM workers: created by auths so auths will resolve it
* DB+RAM blocks: resolved by workinfo - any unsaved blocks (if any)
* will be after the last DB workinfo
* DB+RAM accountbalance (TODO): resolved by shares/workinfo/blocks
* RAM workerstatus: all except last_idle are set at the end of the
* CCL reload
* Code currently doesn't use last_idle
*
* idcontrol: only userid reuse is critical and the user is added
* immeditately to the DB before replying to the add message
*
* Tables that are/will be written straight to the DB, so are OK:
* users, useraccounts, paymentaddresses, payments,
* accountadjustment, optioncontrol, miningpayouts,
* eventlog
*
* The code deals with the issue of 'now' when reloading by:
* createdate is considered 'now' for all data during a reload and is
* a mandatory field always checked for in ckpool data
* Implementing this is simple by assuming 'now' is always createdate
* i.e. during reload but also during normal running to avoid timing
* issues with ckpool data
* Other data supplies the current time to the functions that require
* 'now' since that data is not part of a reload
*
* During reload, any data before the calculated reload stamp for
* that data is discarded
* Any data that matches the reload stamp is processed with an
* ignore duplicates flag for all except as below.
* Any data after the stamp, is processed normally for all except:
* 1) userstats: any record that falls in a DB userstats that would
* summarise that record is discarded,
* otherwise the userstats is processed normally
* 2) shares/shareerrors: any record that matches an incomplete DB
* sharesummary that hasn't been reset, will reset the sharesummary
* so that the sharesummary will be recalculated
* The record is processed normally with or without the reset
* If the sharesummary is complete, the record is discarded
* 3) ageworkinfo records are also handled by the shares date
* while processing, any records already aged are not updated
* and a warning is displayed if there were any matching shares
*/
typedef struct loadstatus {
tv_t oldest_sharesummary_firstshare_n;
tv_t newest_sharesummary_firstshare_a;
tv_t newest_sharesummary_firstshare_ay;
tv_t sharesummary_firstshare; // whichever of above 2 used
tv_t oldest_sharesummary_firstshare_a;
tv_t newest_sharesummary_firstshare_y;
tv_t newest_createdate_workinfo;
tv_t newest_createdate_auths;
tv_t newest_createdate_poolstats;
tv_t newest_starttimeband_userstats;
tv_t newest_createdate_blocks;
int64_t oldest_workinfoid_n; // of oldest firstshare sharesummary n
int64_t oldest_workinfoid_a; // of oldest firstshare sharesummary a
int64_t newest_workinfoid_a; // of newest firstshare sharesummary a
int64_t newest_workinfoid_y; // of newest firstshare sharesummary y
} LOADSTATUS;
static LOADSTATUS dbstatus;
// Share stats since last block
typedef struct poolstatus {
int64_t workinfoid; // Last block
double diffacc;
double diffinv; // Non-acc
double shareacc;
double shareinv; // Non-acc
double best_sdiff; // TODO (maybe)
} POOLSTATUS;
static POOLSTATUS pool;
/* TODO: when we know about orphans, the count reset to zero
* will need to be undone - i.e. recalculate this data from
* the memory tables - maybe ... */
// size limit on the command string
#define CMD_SIZ 31
#define ID_SIZ 31
// size to allocate for pgsql text and display (bigger than needed)
#define DATE_BUFSIZ (63+1)
#define CDATE_BUFSIZ (127+1)
#define BIGINT_BUFSIZ (63+1)
#define INT_BUFSIZ (63+1)
#define DOUBLE_BUFSIZ (63+1)
#define TXT_BIG 256
#define TXT_MED 128
#define TXT_SML 64
#define TXT_FLAG 1
// TAB
#define FLDSEP 0x09
#define FLDSEPSTR "\011"
// Ensure long long and int64_t are both 8 bytes (and thus also the same)
#define ASSERT1(condition) __maybe_unused static char sizeof_longlong_must_be_8[(condition)?1:-1]
#define ASSERT2(condition) __maybe_unused static char sizeof_int64_t_must_be_8[(condition)?1:-1]
ASSERT1(sizeof(long long) == 8);
ASSERT2(sizeof(int64_t) == 8);
#define MAXID 0x7fffffffffffffffLL
#define PGOK(_res) ((_res) == PGRES_COMMAND_OK || \
(_res) == PGRES_TUPLES_OK || \
(_res) == PGRES_EMPTY_QUERY)
// Clear text printable version of txt up to first '\0'
static char *safe_text(char *txt)
{
unsigned char *ptr = (unsigned char *)txt;
size_t len;
char *ret, *buf;
if (!txt) {
buf = strdup("(Null)");
if (!buf)
quithere(1, "malloc OOM");
return buf;
}
// Allocate the maximum needed
len = (strlen(txt)+1)*4+1;
ret = buf = malloc(len);
if (!buf)
quithere(1, "malloc (%d) OOM", (int)len);
while (*ptr) {
if (*ptr >= ' ' && *ptr <= '~')
*(buf++) = *(ptr++);
else {
snprintf(buf, 5, "0x%02x", *(ptr++));
buf += 4;
}
}
strcpy(buf, "0x00");
return ret;
}
static char *pqerrmsg(PGconn *conn)
{
char *ptr, *buf = strdup(PQerrorMessage(conn));
ptr = buf + strlen(buf) - 1;
while (ptr >= buf && (*ptr == '\n' || *ptr == '\r'))
*(ptr--) = '\0';
while (--ptr >= buf) {
if (*ptr == '\n' || *ptr == '\r' || *ptr == '\t')
*ptr = ' ';
}
return buf;
}
#define PGLOG(__LOG, __str, __rescode, __conn) do { \
char *__buf = pqerrmsg(__conn); \
__LOG("%s(): %s failed (%d) '%s'", __func__, \
__str, (int)rescode, __buf); \
free(__buf); \
} while (0)
#define PGLOGERR(_str, _rescode, _conn) PGLOG(LOGERR, _str, _rescode, _conn)
#define PGLOGEMERG(_str, _rescode, _conn) PGLOG(LOGEMERG, _str, _rescode, _conn)
/* N.B. STRNCPY() truncates, whereas txt_to_str() aborts ckdb if src > trg
* If copying data from the DB, code should always use txt_to_str() since
* data should never be lost/truncated if it came from the DB -
* that simply implies a code bug or a database change that must be fixed */
#define STRNCPY(trg, src) do { \
strncpy((char *)(trg), (char *)(src), sizeof(trg)); \
trg[sizeof(trg) - 1] = '\0'; \
} while (0)
#define STRNCPYSIZ(trg, src, siz) do { \
strncpy((char *)(trg), (char *)(src), siz); \
trg[siz - 1] = '\0'; \
} while (0)
#define AR_SIZ 1024
#define APPEND_REALLOC_INIT(_buf, _off, _len) do { \
_len = AR_SIZ; \
(_buf) = malloc(_len); \
if (!(_buf)) \
quithere(1, "malloc (%d) OOM", (int)_len); \
(_buf)[0] = '\0'; \
_off = 0; \
} while(0)
#define APPEND_REALLOC(_dst, _dstoff, _dstsiz, _src) do { \
size_t _newlen, _srclen = strlen(_src); \
_newlen = (_dstoff) + _srclen; \
if (_newlen >= (_dstsiz)) { \
_dstsiz = _newlen + AR_SIZ - (_newlen % AR_SIZ); \
_dst = realloc(_dst, _dstsiz); \
if (!(_dst)) \
quithere(1, "realloc (%d) OOM", (int)_dstsiz); \
} \
strcpy((_dst)+(_dstoff), _src); \
_dstoff += _srclen; \
} while(0)
enum data_type {
TYPE_STR,
TYPE_BIGINT,
TYPE_INT,
TYPE_TV,
TYPE_TVS,
TYPE_CTV,
TYPE_BLOB,
TYPE_DOUBLE
};
#define TXT_TO_STR(__nam, __fld, __data) txt_to_str(__nam, __fld, (__data), sizeof(__data))
#define TXT_TO_BIGINT(__nam, __fld, __data) txt_to_bigint(__nam, __fld, &(__data), sizeof(__data))
#define TXT_TO_INT(__nam, __fld, __data) txt_to_int(__nam, __fld, &(__data), sizeof(__data))
#define TXT_TO_TV(__nam, __fld, __data) txt_to_tv(__nam, __fld, &(__data), sizeof(__data))
#define TXT_TO_CTV(__nam, __fld, __data) txt_to_ctv(__nam, __fld, &(__data), sizeof(__data))
#define TXT_TO_BLOB(__nam, __fld, __data) txt_to_blob(__nam, __fld, __data)
#define TXT_TO_DOUBLE(__nam, __fld, __data) txt_to_double(__nam, __fld, &(__data), sizeof(__data))
#define PQ_GET_FLD(__res, __row, __name, __fld, __ok) do { \
int __col = PQfnumber(__res, __name); \
if (__col == -1) { \
LOGERR("%s(): Unknown field '%s' row %d", __func__, __name, __row); \
__ok = false; \
} else \
__fld = PQgetvalue(__res, __row, __col); \
} while (0)
// HISTORY FIELDS
#define HISTORYDATECONTROL ",createdate,createby,createcode,createinet,expirydate"
#define HISTORYDATECOUNT 5
#define HISTORYDATEFLDS(_res, _row, _data, _ok) do { \
char *_fld; \
PQ_GET_FLD(_res, _row, "createdate", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_TV("createdate", _fld, (_data)->createdate); \
PQ_GET_FLD(_res, _row, "createby", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_STR("createby", _fld, (_data)->createby); \
PQ_GET_FLD(_res, _row, "createcode", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_STR("createcode", _fld, (_data)->createcode); \
PQ_GET_FLD(_res, _row, "createinet", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_STR("createinet", _fld, (_data)->createinet); \
PQ_GET_FLD(_res, _row, "expirydate", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_TV("expirydate", _fld, (_data)->expirydate); \
} while (0)
#define HISTORYDATECONTROLFIELDS \
tv_t createdate; \
char createby[TXT_SML+1]; \
char createcode[TXT_MED+1]; \
char createinet[TXT_MED+1]; \
tv_t expirydate
#define HISTORYDATEPARAMS(_params, _his_pos, _row) do { \
_params[_his_pos++] = tv_to_buf(&(_row->createdate), NULL, 0); \
_params[_his_pos++] = str_to_buf(_row->createby, NULL, 0); \
_params[_his_pos++] = str_to_buf(_row->createcode, NULL, 0); \
_params[_his_pos++] = str_to_buf(_row->createinet, NULL, 0); \
_params[_his_pos++] = tv_to_buf(&(_row->expirydate), NULL, 0); \
} while (0)
// 6-Jun-6666 06:06:06+00
#define DEFAULT_EXPIRY 148204965966L
// 1-Jun-6666 00:00:00+00
#define COMPARE_EXPIRY 148204512000L
static const tv_t default_expiry = { DEFAULT_EXPIRY, 0L };
// No actual need to test tv_usec
#define CURRENT(_tv) (((_tv)->tv_sec == DEFAULT_EXPIRY) ? true : false)
// 31-Dec-9999 23:59:59+00
#define DATE_S_EOT 253402300799L
#define DATE_uS_EOT 0L
static const tv_t date_eot = { DATE_S_EOT, DATE_uS_EOT };
// All data will be after: 2-Jan-2014 00:00:00+00
#define DATE_BEGIN 1388620800L
static const tv_t date_begin = { DATE_BEGIN, 0L };
#define HISTORYDATEINIT(_row, _cd, _by, _code, _inet) do { \
_row->createdate.tv_sec = (_cd)->tv_sec; \
_row->createdate.tv_usec = (_cd)->tv_usec; \
STRNCPY(_row->createby, _by); \
STRNCPY(_row->createcode, _code); \
STRNCPY(_row->createinet, _inet); \
_row->expirydate.tv_sec = default_expiry.tv_sec; \
_row->expirydate.tv_usec = default_expiry.tv_usec; \
} while (0)
// Override _row defaults if transfer fields are present
#define HISTORYDATETRANSFER(_root, _row) do { \
K_ITEM *__item; \
TRANSFER *__transfer; \
__item = optional_name(_root, "createby", 1, NULL); \
if (__item) { \
DATA_TRANSFER(__transfer, __item); \
STRNCPY(_row->createby, __transfer->mvalue); \
} \
__item = optional_name(_root, "createcode", 1, NULL); \
if (__item) { \
DATA_TRANSFER(__transfer, __item); \
STRNCPY(_row->createcode, __transfer->mvalue); \
} \
__item = optional_name(_root, "createinet", 1, NULL); \
if (__item) { \
DATA_TRANSFER(__transfer, __item); \
STRNCPY(_row->createinet, __transfer->mvalue); \
} \
} while (0)
// MODIFY FIELDS
#define MODIFYDATECONTROL ",createdate,createby,createcode,createinet" \
",modifydate,modifyby,modifycode,modifyinet"
#define MODIFYDATECOUNT 8
#define MODIFYUPDATECOUNT 4
#define MODIFYDATEFLDS(_res, _row, _data, _ok) do { \
char *_fld; \
PQ_GET_FLD(_res, _row, "createdate", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_TV("createdate", _fld, (_data)->createdate); \
PQ_GET_FLD(_res, _row, "createby", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_STR("createby", _fld, (_data)->createby); \
PQ_GET_FLD(_res, _row, "createcode", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_STR("createcode", _fld, (_data)->createcode); \
PQ_GET_FLD(_res, _row, "createinet", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_STR("createinet", _fld, (_data)->createinet); \
PQ_GET_FLD(_res, _row, "modifydate", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_TV("modifydate", _fld, (_data)->modifydate); \
PQ_GET_FLD(_res, _row, "modifyby", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_STR("modifyby", _fld, (_data)->modifyby); \
PQ_GET_FLD(_res, _row, "modifycode", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_STR("modifycode", _fld, (_data)->modifycode); \
PQ_GET_FLD(_res, _row, "modifyinet", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_STR("modifyinet", _fld, (_data)->modifyinet); \
} while (0)
#define MODIFYDATECONTROLFIELDS \
tv_t createdate; \
char createby[TXT_SML+1]; \
char createcode[TXT_MED+1]; \
char createinet[TXT_MED+1]; \
tv_t modifydate; \
char modifyby[TXT_SML+1]; \
char modifycode[TXT_MED+1]; \
char modifyinet[TXT_MED+1]
#define MODIFYDATEPARAMS(_params, _mod_pos, _row) do { \
_params[_mod_pos++] = tv_to_buf(&(_row->createdate), NULL, 0); \
_params[_mod_pos++] = str_to_buf(_row->createby, NULL, 0); \
_params[_mod_pos++] = str_to_buf(_row->createcode, NULL, 0); \
_params[_mod_pos++] = str_to_buf(_row->createinet, NULL, 0); \
_params[_mod_pos++] = tv_to_buf(&(_row->modifydate), NULL, 0); \
_params[_mod_pos++] = str_to_buf(_row->modifyby, NULL, 0); \
_params[_mod_pos++] = str_to_buf(_row->modifycode, NULL, 0); \
_params[_mod_pos++] = str_to_buf(_row->modifyinet, NULL, 0); \
} while (0)
#define MODIFYUPDATEPARAMS(_params, _mod_pos, _row) do { \
_params[_mod_pos++] = tv_to_buf(&(_row->modifydate), NULL, 0); \
_params[_mod_pos++] = str_to_buf(_row->modifyby, NULL, 0); \
_params[_mod_pos++] = str_to_buf(_row->modifycode, NULL, 0); \
_params[_mod_pos++] = str_to_buf(_row->modifyinet, NULL, 0); \
} while (0)
#define MODIFYDATEINIT(_row, _cd, _by, _code, _inet) do { \
_row->createdate.tv_sec = (_cd)->tv_sec; \
_row->createdate.tv_usec = (_cd)->tv_usec; \
STRNCPY(_row->createby, _by); \
STRNCPY(_row->createcode, _code); \
STRNCPY(_row->createinet, _inet); \
_row->modifydate.tv_sec = 0; \
_row->modifydate.tv_usec = 0; \
_row->modifyby[0] = '\0'; \
_row->modifycode[0] = '\0'; \
_row->modifyinet[0] = '\0'; \
} while (0)
#define MODIFYUPDATE(_row, _cd, _by, _code, _inet) do { \
_row->modifydate.tv_sec = (_cd)->tv_sec; \
_row->modifydate.tv_usec = (_cd)->tv_usec; \
STRNCPY(_row->modifyby, _by); \
STRNCPY(_row->modifycode, _code); \
STRNCPY(_row->modifyinet, _inet); \
} while (0)
// SIMPLE FIELDS
#define SIMPLEDATECONTROL ",createdate,createby,createcode,createinet"
#define SIMPLEDATECOUNT 4
#define SIMPLEDATEFLDS(_res, _row, _data, _ok) do { \
char *_fld; \
PQ_GET_FLD(_res, _row, "createdate", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_TV("createdate", _fld, (_data)->createdate); \
PQ_GET_FLD(_res, _row, "createby", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_STR("createby", _fld, (_data)->createby); \
PQ_GET_FLD(_res, _row, "createcode", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_STR("createcode", _fld, (_data)->createcode); \
PQ_GET_FLD(_res, _row, "createinet", _fld, _ok); \
if (!_ok) \
break; \
TXT_TO_STR("createinet", _fld, (_data)->createinet); \
} while (0)
#define SIMPLEDATECONTROLFIELDS \
tv_t createdate; \
char createby[TXT_SML+1]; \
char createcode[TXT_MED+1]; \
char createinet[TXT_MED+1]
#define SIMPLEDATEPARAMS(_params, _his_pos, _row) do { \
_params[_his_pos++] = tv_to_buf(&(_row->createdate), NULL, 0); \
_params[_his_pos++] = str_to_buf(_row->createby, NULL, 0); \
_params[_his_pos++] = str_to_buf(_row->createcode, NULL, 0); \
_params[_his_pos++] = str_to_buf(_row->createinet, NULL, 0); \
} while (0)
#define SIMPLEDATEINIT(_row, _cd, _by, _code, _inet) do { \
_row->createdate.tv_sec = (_cd)->tv_sec; \
_row->createdate.tv_usec = (_cd)->tv_usec; \
STRNCPY(_row->createby, _by); \
STRNCPY(_row->createcode, _code); \
STRNCPY(_row->createinet, _inet); \
} while (0)
#define SIMPLEDATEDEFAULT(_row, _cd) do { \
_row->createdate.tv_sec = (_cd)->tv_sec; \
_row->createdate.tv_usec = (_cd)->tv_usec; \
STRNCPY(_row->createby, by_default); \
STRNCPY(_row->createcode, (char *)__func__); \
STRNCPY(_row->createinet, inet_default); \
} while (0)
// Override _row defaults if transfer fields are present
#define SIMPLEDATETRANSFER(_root, _row) do { \
K_ITEM *__item; \
TRANSFER *__transfer; \
__item = optional_name(_root, "createby", 1, NULL); \
if (__item) { \
DATA_TRANSFER(__transfer, __item); \
STRNCPY(_row->createby, __transfer->mvalue); \
} \
__item = optional_name(_root, "createcode", 1, NULL); \
if (__item) { \
DATA_TRANSFER(__transfer, __item); \
STRNCPY(_row->createcode, __transfer->mvalue); \
} \
__item = optional_name(_root, "createinet", 1, NULL); \
if (__item) { \
DATA_TRANSFER(__transfer, __item); \
STRNCPY(_row->createinet, __transfer->mvalue); \
} \
} while (0)
// For easy parameter constant strings
#define PQPARAM1 "$1"
#define PQPARAM2 "$1,$2"
#define PQPARAM3 "$1,$2,$3"
#define PQPARAM4 "$1,$2,$3,$4"
#define PQPARAM5 "$1,$2,$3,$4,$5"
#define PQPARAM6 "$1,$2,$3,$4,$5,$6"
#define PQPARAM7 "$1,$2,$3,$4,$5,$6,$7"
#define PQPARAM8 "$1,$2,$3,$4,$5,$6,$7,$8"
#define PQPARAM9 PQPARAM8 ",$9"
#define PQPARAM10 PQPARAM8 ",$9,$10"
#define PQPARAM11 PQPARAM8 ",$9,$10,$11"
#define PQPARAM12 PQPARAM8 ",$9,$10,$11,$12"
#define PQPARAM13 PQPARAM8 ",$9,$10,$11,$12,$13"
#define PQPARAM14 PQPARAM8 ",$9,$10,$11,$12,$13,$14"
#define PQPARAM15 PQPARAM8 ",$9,$10,$11,$12,$13,$14,$15"
#define PQPARAM16 PQPARAM8 ",$9,$10,$11,$12,$13,$14,$15,$16"
#define PQPARAM22 PQPARAM16 ",$17,$18,$19,$20,$21,$22"
#define PQPARAM27 PQPARAM22 ",$23,$24,$25,$26,$27"
#define PARCHK(_par, _params) do { \
if (_par != (int)(sizeof(_params)/sizeof(_params[0]))) { \
quithere(1, "params[] usage (%d) != size (%d)", \
_par, (int)(sizeof(_params)/sizeof(_params[0]))); \
} \
} while (0)
#define PARCHKVAL(_par, _val, _params) do { \
if (_par != _val) { \
quithere(1, "params[] usage (%d) != expected (%d)", \
_par, _val); \
} \
if (_val > (int)(sizeof(_params)/sizeof(_params[0]))) { \
quithere(1, "params[] usage (%d) > size (%d)", \
_val, (int)(sizeof(_params)/sizeof(_params[0]))); \
} \
} while (0)
#define BTC_TO_D(_amt) ((double)((_amt) / 100000000.0))
// argv -y - don't run in ckdb mode, just confirm sharesummaries
static bool confirm_sharesummary;
/* Optional workinfoid range -Y to supply when confirming sharesummaries
* N.B. if you specify -Y it will enable -y, so -y isn't also required
*
* Default (NULL) is to confirm all aged sharesummaries
* Default should normally be used every time
* The below options are mainly for debugging or
* a quicker confirm if required due to not running confirms regularly
* TODO: ... once the code includes flagging confirmed sharesummaries
* Valid options are:
* bNNN - confirm all workinfoid's from the previous db block before
* NNN (or 0) up to the workinfoid of the 1st db block height
* equal or after NNN
* wNNN - confirm all workinfoid's from NNN up to the last aged
* sharesummary
* rNNN-MMM - confirm all workinfoid's from NNN to MMM inclusive
* cNNN-MMM - check the CCL record timestamps then exit
* i - just show the DB load information then exit
*/
static char *confirm_range;
static int confirm_block;
static int64_t confirm_range_start;
static int64_t confirm_range_finish;
static bool confirm_check_createdate;
static int64_t ccl_mismatch_abs;
static int64_t ccl_mismatch;
static double ccl_mismatch_min;
static double ccl_mismatch_max;
static int64_t ccl_unordered_abs;
static int64_t ccl_unordered;
static double ccl_unordered_most;
// The workinfoid range we are processing
static int64_t confirm_first_workinfoid;
static int64_t confirm_last_workinfoid;
/* Stop the reload 11min after the 'last' workinfoid+1 appears
* ckpool uses 10min - but add 1min to be sure */
#define WORKINFO_AGE 660
static tv_t confirm_finish;
static tv_t reload_timestamp;
// DB users,workers,auth load is complete
static bool db_auths_complete = false;
// DB load is complete
static bool db_load_complete = false;
// Different input data handling
static bool reloading = false;
// Data load is complete
static bool startup_complete = false;
// Tell everyone to die
static bool everyone_die = false;
static cklock_t fpm_lock;
static char *first_pool_message;
static sem_t socketer_sem;
static const char *userpatt = "^[^/\\._"FLDSEPSTR"]*$"; // disallow: '/' '.' '_' and FLDSEP
static const char *mailpatt = "^[A-Za-z0-9_-][A-Za-z0-9_\\.-]*@[A-Za-z0-9][A-Za-z0-9\\.-]*[A-Za-z0-9]$";
static const char *idpatt = "^[_A-Za-z][_A-Za-z0-9]*$";
static const char *intpatt = "^[0-9][0-9]*$";
static const char *hashpatt = "^[A-Fa-f0-9]*$";
#define JSON_TRANSFER "json="
#define JSON_TRANSFER_LEN (sizeof(JSON_TRANSFER)-1)
// Methods for sharelog (common function for all)
#define STR_WORKINFO "workinfo"
#define STR_SHARES "shares"
#define STR_SHAREERRORS "shareerror"
#define STR_AGEWORKINFO "ageworkinfo"
static char *by_default = "code";
static char *inet_default = "127.0.0.1";
enum cmd_values {
CMD_UNSET,
CMD_REPLY, // Means something was wrong - send back reply
CMD_SHUTDOWN,
CMD_PING,
CMD_VERSION,
CMD_LOGLEVEL,
CMD_SHARELOG,
CMD_AUTH,
CMD_ADDRAUTH,
CMD_ADDUSER,
CMD_NEWPASS,
CMD_CHKPASS,
CMD_USERSET,
CMD_POOLSTAT,
CMD_USERSTAT,
CMD_BLOCK,
CMD_BLOCKLIST,
CMD_BLOCKSTATUS,
CMD_NEWID,
CMD_PAYMENTS,
CMD_WORKERS,
CMD_ALLUSERS,
CMD_HOMEPAGE,
CMD_GETATTS,
CMD_SETATTS,
CMD_DSP,
CMD_STATS,
CMD_PPLNS,
CMD_END
};
// For NON-list stack/heap K_ITEMS
#define INIT_GENERIC(_item, _name) do { \
(_item)->name = _name ## _free->name; \
} while (0)
#define DATA_GENERIC(_var, _item, _name, _nonull) do { \
if ((_item) == NULL) { \
if (_nonull) { \
quithere(1, "Attempt to cast NULL item data (as '%s')", \
_name ## _free->name); \
} else \
(_var) = NULL; \
} else { \
if ((_item)->name != _name ## _free->name) { \
quithere(1, "Attempt to cast item '%s' data as '%s'", \
(_item)->name, \
_name ## _free->name); \
} \
(_var) = ((struct _name *)((_item)->data)); \
} \
} while (0)
// LOGQUEUE
typedef struct logqueue {
char *msg;
} LOGQUEUE;
#define ALLOC_LOGQUEUE 1024
#define LIMIT_LOGQUEUE 0
#define INIT_LOGQUEUE(_item) INIT_GENERIC(_item, logqueue)
#define DATA_LOGQUEUE(_var, _item) DATA_GENERIC(_var, _item, logqueue, true)
static K_LIST *logqueue_free;
static K_STORE *logqueue_store;
// WORKQUEUE
typedef struct workqueue {
char *buf;
int which_cmds;
enum cmd_values cmdnum;
char cmd[CMD_SIZ+1];
char id[ID_SIZ+1];
tv_t now;
char by[TXT_SML+1];
char code[TXT_MED+1];
char inet[TXT_MED+1];
tv_t cd;
K_TREE *trf_root;
K_STORE *trf_store;
} WORKQUEUE;
#define ALLOC_WORKQUEUE 1024
#define LIMIT_WORKQUEUE 0
#define CULL_WORKQUEUE 16
#define INIT_WORKQUEUE(_item) INIT_GENERIC(_item, workqueue)
#define DATA_WORKQUEUE(_var, _item) DATA_GENERIC(_var, _item, workqueue, true)
static K_LIST *workqueue_free;
static K_STORE *workqueue_store;
static pthread_mutex_t wq_waitlock;
static pthread_cond_t wq_waitcond;
// TRANSFER
#define NAME_SIZE 63
#define VALUE_SIZE 1023
typedef struct transfer {
char name[NAME_SIZE+1];
char svalue[VALUE_SIZE+1];
char *mvalue;
} TRANSFER;
#define ALLOC_TRANSFER 64
#define LIMIT_TRANSFER 0
#define CULL_TRANSFER 64
#define INIT_TRANSFER(_item) INIT_GENERIC(_item, transfer)
#define DATA_TRANSFER(_var, _item) DATA_GENERIC(_var, _item, transfer, true)
static K_LIST *transfer_free;
#define transfer_data(_item) _transfer_data(_item, WHERE_FFL_HERE)
// For mutiple variable function calls that need the data
static char *_transfer_data(K_ITEM *item, WHERE_FFL_ARGS)
{
TRANSFER *transfer;
char *mvalue;
if (!item) {
quitfrom(1, file, func, line,
"Attempt to use NULL transfer item");
}
if (item->name != transfer_free->name) {
quitfrom(1, file, func, line,
"Attempt to cast item '%s' data as '%s'",
item->name,
transfer_free->name);
}
transfer = (TRANSFER *)(item->data);
if (!transfer) {
quitfrom(1, file, func, line,
"Transfer item has NULL data");
}
mvalue = transfer->mvalue;
if (!mvalue) {
/* N.B. name and svalue strings will have \0 termination
* even if they are both corrupt, since mvalue is NULL */
quitfrom(1, file, func, line,
"Transfer '%s' '%s' has NULL mvalue",
transfer->name, transfer->svalue);
}
return mvalue;
}
// So the records below have the same 'name' as the klist
static const char Transfer[] = "Transfer";
// older version missing field defaults
static TRANSFER auth_1 = { "poolinstance", "", auth_1.svalue };
static K_ITEM auth_poolinstance = { Transfer, NULL, NULL, (void *)(&auth_1) };
static TRANSFER auth_2 = { "preauth", FALSE_STR, auth_2.svalue };
static K_ITEM auth_preauth = { Transfer, NULL, NULL, (void *)(&auth_2) };
static TRANSFER poolstats_1 = { "elapsed", "0", poolstats_1.svalue };
static K_ITEM poolstats_elapsed = { Transfer, NULL, NULL, (void *)(&poolstats_1) };
static TRANSFER userstats_1 = { "elapsed", "0", userstats_1.svalue };
static K_ITEM userstats_elapsed = { Transfer, NULL, NULL, (void *)(&userstats_1) };
static TRANSFER userstats_2 = { "workername", "all", userstats_2.svalue };
static K_ITEM userstats_workername = { Transfer, NULL, NULL, (void *)(&userstats_2) };
static TRANSFER userstats_3 = { "idle", FALSE_STR, userstats_3.svalue };
static K_ITEM userstats_idle = { Transfer, NULL, NULL, (void *)(&userstats_3) };
static TRANSFER userstats_4 = { "eos", TRUE_STR, userstats_4.svalue };
static K_ITEM userstats_eos = { Transfer, NULL, NULL, (void *)(&userstats_4) };
static TRANSFER shares_1 = { "secondaryuserid", TRUE_STR, shares_1.svalue };
static K_ITEM shares_secondaryuserid = { Transfer, NULL, NULL, (void *)(&shares_1) };
static TRANSFER shareerrors_1 = { "secondaryuserid", TRUE_STR, shareerrors_1.svalue };
static K_ITEM shareerrors_secondaryuserid = { Transfer, NULL, NULL, (void *)(&shareerrors_1) };
// Time limit that this problem occurred
// 24-Aug-2014 05:20+00 (1st one shortly after this)
static tv_t missing_secuser_min = { 1408857600L, 0L };
// 24-Aug-2014 06:00+00
static tv_t missing_secuser_max = { 1408860000L, 0L };
// USERS
typedef struct users {
int64_t userid;
char username[TXT_BIG+1];
// Anything in 'status' disables the account
char status[TXT_BIG+1];
char emailaddress[TXT_BIG+1];
tv_t joineddate;
char passwordhash[TXT_BIG+1];
char secondaryuserid[TXT_SML+1];
char salt[TXT_BIG+1];
HISTORYDATECONTROLFIELDS;
} USERS;
#define ALLOC_USERS 1024
#define LIMIT_USERS 0
#define INIT_USERS(_item) INIT_GENERIC(_item, users)
#define DATA_USERS(_var, _item) DATA_GENERIC(_var, _item, users, true)
#define DATA_USERS_NULL(_var, _item) DATA_GENERIC(_var, _item, users, false)
#define SHA256SIZHEX 64
#define SHA256SIZBIN 32
#define SALTSIZHEX 32
#define SALTSIZBIN 16
static K_TREE *users_root;
static K_TREE *userid_root;
static K_LIST *users_free;
static K_STORE *users_store;
// USERATTS
typedef struct useratts {
int64_t userid;
char attname[TXT_SML+1];
char status[TXT_BIG+1];
char attstr[TXT_BIG+1];
char attstr2[TXT_BIG+1];
int64_t attnum;
int64_t attnum2;
tv_t attdate;
tv_t attdate2;
HISTORYDATECONTROLFIELDS;
} USERATTS;
#define ALLOC_USERATTS 1024
#define LIMIT_USERATTS 0
#define INIT_USERATTS(_item) INIT_GENERIC(_item, useratts)
#define DATA_USERATTS(_var, _item) DATA_GENERIC(_var, _item, useratts, true)
#define DATA_USERATTS_NULL(_var, _item) DATA_GENERIC(_var, _item, useratts, false)
static K_TREE *useratts_root;
static K_LIST *useratts_free;
static K_STORE *useratts_store;
// WORKERS
typedef struct workers {
int64_t workerid;
int64_t userid;
char workername[TXT_BIG+1]; // includes username
int32_t difficultydefault;
char idlenotificationenabled[TXT_FLAG+1];
int32_t idlenotificationtime;
HISTORYDATECONTROLFIELDS;
} WORKERS;
#define ALLOC_WORKERS 1024
#define LIMIT_WORKERS 0
#define INIT_WORKERS(_item) INIT_GENERIC(_item, workers)
#define DATA_WORKERS(_var, _item) DATA_GENERIC(_var, _item, workers, true)
#define DATA_WORKERS_NULL(_var, _item) DATA_GENERIC(_var, _item, workers, false)
static K_TREE *workers_root;
static K_LIST *workers_free;
static K_STORE *workers_store;
#define DIFFICULTYDEFAULT_MIN 10
#define DIFFICULTYDEFAULT_MAX 1000000
#define DIFFICULTYDEFAULT_DEF DIFFICULTYDEFAULT_MIN
#define DIFFICULTYDEFAULT_DEF_STR STRINT(DIFFICULTYDEFAULT_DEF)
#define IDLENOTIFICATIONENABLED "y"
#define IDLENOTIFICATIONDISABLED " "
#define IDLENOTIFICATIONENABLED_DEF IDLENOTIFICATIONDISABLED
#define IDLENOTIFICATIONTIME_MIN 10
#define IDLENOTIFICATIONTIME_MAX 60
#define IDLENOTIFICATIONTIME_DEF IDLENOTIFICATIONTIME_MIN
#define IDLENOTIFICATIONTIME_DEF_STR STRINT(IDLENOTIFICATIONTIME_DEF)
// PAYMENTADDRESSES
typedef struct paymentaddresses {
int64_t paymentaddressid;
int64_t userid;
char payaddress[TXT_BIG+1];
int32_t payratio;
HISTORYDATECONTROLFIELDS;
} PAYMENTADDRESSES;
#define ALLOC_PAYMENTADDRESSES 1024
#define LIMIT_PAYMENTADDRESSES 0
#define INIT_PAYMENTADDRESSES(_item) INIT_GENERIC(_item, paymentaddresses)
#define DATA_PAYMENTADDRESSES(_var, _item) DATA_GENERIC(_var, _item, paymentaddresses, true)
static K_TREE *paymentaddresses_root;
static K_LIST *paymentaddresses_free;
static K_STORE *paymentaddresses_store;
// PAYMENTS
typedef struct payments {
int64_t paymentid;
int64_t userid;
tv_t paydate;
char payaddress[TXT_BIG+1];
char originaltxn[TXT_BIG+1];
int64_t amount;
char committxn[TXT_BIG+1];
char commitblockhash[TXT_BIG+1];
HISTORYDATECONTROLFIELDS;
} PAYMENTS;
#define ALLOC_PAYMENTS 1024
#define LIMIT_PAYMENTS 0
#define INIT_PAYMENTS(_item) INIT_GENERIC(_item, payments)
#define DATA_PAYMENTS(_var, _item) DATA_GENERIC(_var, _item, payments, true)
#define DATA_PAYMENTS_NULL(_var, _item) DATA_GENERIC(_var, _item, payments, false)
static K_TREE *payments_root;
static K_LIST *payments_free;
static K_STORE *payments_store;
/* unused yet
// ACCOUNTBALANCE
typedef struct accountbalance {
int64_t userid;
int64_t confirmedpaid;
int64_t confirmedunpaid;
int64_t pendingconfirm;
int32_t heightupdate;
HISTORYDATECONTROLFIELDS;
} ACCOUNTBALANCE;
#define ALLOC_ACCOUNTBALANCE 1024
#define LIMIT_ACCOUNTBALANCE 0
#define INIT_ACCOUNTBALANCE(_item) INIT_GENERIC(_item, accountbalance)
#define DATA_ACCOUNTBALANCE(_var, _item) DATA_GENERIC(_var, _item, accountbalance, true)
static K_TREE *accountbalance_root;
static K_LIST *accountbalance_free;
static K_STORE *accountbalance_store;
// ACCOUNTADJUSTMENT
typedef struct accountadjustment {
int64_t userid;
char authority[TXT_BIG+1];
char *reason;
int64_t amount;
HISTORYDATECONTROLFIELDS;
} ACCOUNTADJUSTMENT;
#define ALLOC_ACCOUNTADJUSTMENT 100
#define LIMIT_ACCOUNTADJUSTMENT 0
#define INIT_ACCOUNTADJUSTMENT(_item) INIT_GENERIC(_item, accountadjustment)
#define DATA_ACCOUNTADJUSTMENT(_var, _item) DATA_GENERIC(_var, _item, accountadjustment, true)
static K_TREE *accountadjustment_root;
static K_LIST *accountadjustment_free;
static K_STORE *accountadjustment_store;
*/
// IDCONTROL
typedef struct idcontrol {
char idname[TXT_SML+1];
int64_t lastid;
MODIFYDATECONTROLFIELDS;
} IDCONTROL;
#define ALLOC_IDCONTROL 16
#define LIMIT_IDCONTROL 0
#define INIT_IDCONTROL(_item) INIT_GENERIC(_item, idcontrol)
#define DATA_IDCONTROL(_var, _item) DATA_GENERIC(_var, _item, idcontrol, true)
// These are only used for db access - not stored in memory
//static K_TREE *idcontrol_root;
static K_LIST *idcontrol_free;
static K_STORE *idcontrol_store;
/* unused yet
// OPTIONCONTROL
typedef struct optioncontrol {
char optionname[TXT_SML+1];
char *optionvalue;
tv_t activationdate;
int32_t activationheight;
HISTORYDATECONTROLFIELDS;
} OPTIONCONTROL;
#define ALLOC_OPTIONCONTROL 64
#define LIMIT_OPTIONCONTROL 0
#define INIT_OPTIONCONTROL(_item) INIT_GENERIC(_item, optioncontrol)
#define DATA_OPTIONCONTROL(_var, _item) DATA_GENERIC(_var, _item, optioncontrol, true)
static K_TREE *optioncontrol_root;
static K_LIST *optioncontrol_free;
static K_STORE *optioncontrol_store;
*/
// TODO: discarding workinfo,shares
// WORKINFO workinfo.id.json={...}
typedef struct workinfo {
int64_t workinfoid;
char poolinstance[TXT_BIG+1];
char *transactiontree;
char *merklehash;
char prevhash[TXT_BIG+1];
char coinbase1[TXT_BIG+1];
char coinbase2[TXT_BIG+1];
char version[TXT_SML+1];
char bits[TXT_SML+1];
char ntime[TXT_SML+1];
int64_t reward;
HISTORYDATECONTROLFIELDS;
} WORKINFO;
// ~10 hrs
#define ALLOC_WORKINFO 1400
#define LIMIT_WORKINFO 0
#define INIT_WORKINFO(_item) INIT_GENERIC(_item, workinfo)
#define DATA_WORKINFO(_var, _item) DATA_GENERIC(_var, _item, workinfo, true)
static K_TREE *workinfo_root;
// created during data load then destroyed since not needed later
static K_TREE *workinfo_height_root;
static K_LIST *workinfo_free;
static K_STORE *workinfo_store;
// one in the current block
static K_ITEM *workinfo_current;
// first workinfo of current block
static tv_t last_bc;
// current network diff
static double current_ndiff;
// SHARES shares.id.json={...}
typedef struct shares {
int64_t workinfoid;
int64_t userid;
char workername[TXT_BIG+1];
int32_t clientid;
char enonce1[TXT_SML+1];
char nonce2[TXT_BIG+1];
char nonce[TXT_SML+1];
double diff;
double sdiff;
int32_t errn;
char error[TXT_SML+1];
char secondaryuserid[TXT_SML+1];
HISTORYDATECONTROLFIELDS;
} SHARES;
#define ALLOC_SHARES 10000
#define LIMIT_SHARES 0
#define INIT_SHARES(_item) INIT_GENERIC(_item, shares)
#define DATA_SHARES(_var, _item) DATA_GENERIC(_var, _item, shares, true)
static K_TREE *shares_root;
static K_LIST *shares_free;
static K_STORE *shares_store;
// SHAREERRORS shareerrors.id.json={...}
typedef struct shareerrors {
int64_t workinfoid;
int64_t userid;
char workername[TXT_BIG+1];
int32_t clientid;
int32_t errn;
char error[TXT_SML+1];
char secondaryuserid[TXT_SML+1];
HISTORYDATECONTROLFIELDS;
} SHAREERRORS;
#define ALLOC_SHAREERRORS 10000
#define LIMIT_SHAREERRORS 0
#define INIT_SHAREERRORS(_item) INIT_GENERIC(_item, shareerrors)
#define DATA_SHAREERRORS(_var, _item) DATA_GENERIC(_var, _item, shareerrors, true)
static K_TREE *shareerrors_root;
static K_LIST *shareerrors_free;
static K_STORE *shareerrors_store;
// SHARESUMMARY
typedef struct sharesummary {
int64_t userid;
char workername[TXT_BIG+1];
int64_t workinfoid;
double diffacc;
double diffsta;
double diffdup;
double diffhi;
double diffrej;
double shareacc;
double sharesta;
double sharedup;
double sharehi;
double sharerej;
int64_t sharecount;
int64_t errorcount;
int64_t countlastupdate; // non-DB field
bool inserted; // non-DB field
bool saveaged; // non-DB field
bool reset; // non-DB field
tv_t firstshare;
tv_t lastshare;
double lastdiffacc;
char complete[TXT_FLAG+1];
MODIFYDATECONTROLFIELDS;
} SHARESUMMARY;
/* After this many shares added, we need to update the DB record
The DB record is added with the 1st share */
#define SHARESUMMARY_UPDATE_EVERY 10
#define ALLOC_SHARESUMMARY 10000
#define LIMIT_SHARESUMMARY 0
#define INIT_SHARESUMMARY(_item) INIT_GENERIC(_item, sharesummary)
#define DATA_SHARESUMMARY(_var, _item) DATA_GENERIC(_var, _item, sharesummary, true)
#define DATA_SHARESUMMARY_NULL(_var, _item) DATA_GENERIC(_var, _item, sharesummary, false)
#define SUMMARY_NEW 'n'
#define SUMMARY_COMPLETE 'a'
#define SUMMARY_CONFIRM 'y'
static K_TREE *sharesummary_root;
static K_TREE *sharesummary_workinfoid_root;
static K_LIST *sharesummary_free;
static K_STORE *sharesummary_store;
// BLOCKS block.id.json={...}
typedef struct blocks {
int32_t height;
char blockhash[TXT_BIG+1];
int64_t workinfoid;
int64_t userid;
char workername[TXT_BIG+1];
int32_t clientid;
char enonce1[TXT_SML+1];
char nonce2[TXT_BIG+1];
char nonce[TXT_SML+1];
int64_t reward;
char confirmed[TXT_FLAG+1];
double diffacc;
double diffinv;
double shareacc;
double shareinv;
int64_t elapsed;
char statsconfirmed[TXT_FLAG+1];
HISTORYDATECONTROLFIELDS;
} BLOCKS;
#define ALLOC_BLOCKS 100
#define LIMIT_BLOCKS 0
#define INIT_BLOCKS(_item) INIT_GENERIC(_item, blocks)
#define DATA_BLOCKS(_var, _item) DATA_GENERIC(_var, _item, blocks, true)
#define DATA_BLOCKS_NULL(_var, _item) DATA_GENERIC(_var, _item, blocks, false)
#define BLOCKS_NEW 'n'
#define BLOCKS_NEW_STR "n"
#define BLOCKS_CONFIRM '1'
#define BLOCKS_CONFIRM_STR "1"
#define BLOCKS_42 'F'
#define BLOCKS_42_STR "F"
#define BLOCKS_ORPHAN 'O'
#define BLOCKS_ORPHAN_STR "O"
#define BLOCKS_STATSPENDING FALSE_CHR
#define BLOCKS_STATSPENDING_STR FALSE_STR
#define BLOCKS_STATSCONFIRMED TRUE_CHR
#define BLOCKS_STATSCONFIRMED_STR TRUE_STR
static const char *blocks_new = "New";
static const char *blocks_confirm = "1-Confirm";
static const char *blocks_42 = "42-Confirm";
static const char *blocks_orphan = "Orphan";
static const char *blocks_unknown = "?Unknown?";
#define KANO -27972
static K_TREE *blocks_root;
static K_LIST *blocks_free;
static K_STORE *blocks_store;
// MININGPAYOUTS
typedef struct miningpayouts {
int64_t miningpayoutid;
int64_t userid;
int32_t height;
char blockhash[TXT_BIG+1];
int64_t amount;
HISTORYDATECONTROLFIELDS;
} MININGPAYOUTS;
#define ALLOC_MININGPAYOUTS 1000
#define LIMIT_MININGPAYOUTS 0
#define INIT_MININGPAYOUTS(_item) INIT_GENERIC(_item, miningpayouts)
#define DATA_MININGPAYOUTS(_var, _item) DATA_GENERIC(_var, _item, miningpayouts, true)
static K_TREE *miningpayouts_root;
static K_LIST *miningpayouts_free;
static K_STORE *miningpayouts_store;
/*
// EVENTLOG
typedef struct eventlog {
int64_t eventlogid;
char poolinstance[TXT_BIG+1];
char eventlogcode[TXT_SML+1];
char *eventlogdescription;
HISTORYDATECONTROLFIELDS;
} EVENTLOG;
#define ALLOC_EVENTLOG 100
#define LIMIT_EVENTLOG 0
#define INIT_EVENTLOG(_item) INIT_GENERIC(_item, eventlog)
#define DATA_EVENTLOG(_var, _item) DATA_GENERIC(_var, _item, eventlog, true)
static K_TREE *eventlog_root;
static K_LIST *eventlog_free;
static K_STORE *eventlog_store;
*/
// AUTHS authorise.id.json={...}
typedef struct auths {
int64_t authid;
char poolinstance[TXT_BIG+1];
int64_t userid;
char workername[TXT_BIG+1];
int32_t clientid;
char enonce1[TXT_SML+1];
char useragent[TXT_BIG+1];
char preauth[TXT_FLAG+1];
HISTORYDATECONTROLFIELDS;
} AUTHS;
#define ALLOC_AUTHS 1000
#define LIMIT_AUTHS 0
#define INIT_AUTHS(_item) INIT_GENERIC(_item, auths)
#define DATA_AUTHS(_var, _item) DATA_GENERIC(_var, _item, auths, true)
static K_TREE *auths_root;
static K_LIST *auths_free;
static K_STORE *auths_store;
// POOLSTATS poolstats.id.json={...}
// Store every > 9.5m?
// TODO: redo like userstats, but every 10min
#define STATS_PER (9.5*60.0)
typedef struct poolstats {
char poolinstance[TXT_BIG+1];
int64_t elapsed;
int32_t users;
int32_t workers;
double hashrate;
double hashrate5m;
double hashrate1hr;
double hashrate24hr;
bool stored; // Non-db field
SIMPLEDATECONTROLFIELDS;
} POOLSTATS;
#define ALLOC_POOLSTATS 10000
#define LIMIT_POOLSTATS 0
#define INIT_POOLSTATS(_item) INIT_GENERIC(_item, poolstats)
#define DATA_POOLSTATS(_var, _item) DATA_GENERIC(_var, _item, poolstats, true)
#define DATA_POOLSTATS_NULL(_var, _item) DATA_GENERIC(_var, _item, poolstats, false)
static K_TREE *poolstats_root;
static K_LIST *poolstats_free;
static K_STORE *poolstats_store;
// USERSTATS userstats.id.json={...}
// Pool sends each user (staggered) once per 10m
typedef struct userstats {
char poolinstance[TXT_BIG+1];
int64_t userid;
char workername[TXT_BIG+1];
int64_t elapsed;
double hashrate;
double hashrate5m;
double hashrate1hr;
double hashrate24hr;
bool idle; // Non-db field
char summarylevel[TXT_FLAG+1]; // Initially SUMMARY_NONE in RAM
int32_t summarycount;
tv_t statsdate;
SIMPLEDATECONTROLFIELDS;
} USERSTATS;
/* USERSTATS protocol includes a boolean 'eos' that when true,
* we have received the full set of data for the given
* createdate batch, and thus can move all (complete) records
* matching the createdate from userstats_eos_store into the tree */
#define ALLOC_USERSTATS 10000
#define LIMIT_USERSTATS 0
#define INIT_USERSTATS(_item) INIT_GENERIC(_item, userstats)
#define DATA_USERSTATS(_var, _item) DATA_GENERIC(_var, _item, userstats, true)
#define DATA_USERSTATS_NULL(_var, _item) DATA_GENERIC(_var, _item, userstats, false)
static K_TREE *userstats_root;
static K_TREE *userstats_statsdate_root; // ordered by statsdate first
static K_TREE *userstats_workerstatus_root; // during data load
static K_LIST *userstats_free;
static K_STORE *userstats_store;
// Awaiting EOS
static K_STORE *userstats_eos_store;
// Temporary while summarising
static K_STORE *userstats_summ;
/* 1.5 x how often we expect to get user's stats from ckpool
* This is used when grouping the sub-worker stats into a single user
* We add each worker's latest stats to the total - except we ignore
* any worker with newest stats being older than USERSTATS_PER_S */
#define USERSTATS_PER_S 900
/* on the allusers page, show any with stats in the last ... */
#define ALLUSERS_LIMIT_S 3600
#define SUMMARY_NONE '0'
#define SUMMARY_DB '1'
#define SUMMARY_FULL '2'
/* Userstats get stored in the DB for each time band of this
* amount from midnight (UTC+00)
* Thus we simply put each stats value in the time band of the
* stat's timestamp
* Userstats are sumarised in the the same userstats table
* If USERSTATS_DB_S is close to the expected time per USERSTATS
* then it will have higher variance i.e. obviously: a higher
* average of stats per sample will mean a lower SD of the number
* of stats per sample
* The #if below ensures USERSTATS_DB_S times an integer = a day
* so the last band is the same size as the rest -
* and will graph easily
* Obvious WARNING - the smaller this is, the more stats in the DB
* This is summary level '1'
*/
#define USERSTATS_DB_S 3600
#if (((24*60*60) % USERSTATS_DB_S) != 0)
#error "USERSTATS_DB_S times an integer must = a day"
#endif
#if ((24*60*60) < USERSTATS_DB_S)
#error "USERSTATS_DB_S must be at most 1 day"
#endif
/* We summarise and discard userstats that are older than the
* maximum of USERSTATS_DB_S, USERSTATS_PER_S, ALLUSERS_LIMIT_S
*/
#if (USERSTATS_PER_S > ALLUSERS_LIMIT_S)
#if (USERSTATS_PER_S > USERSTATS_DB_S)
#define USERSTATS_AGE USERSTATS_PER_S
#else
#define USERSTATS_AGE USERSTATS_DB_S
#endif
#else
#if (ALLUSERS_LIMIT_S > USERSTATS_DB_S)
#define USERSTATS_AGE ALLUSERS_LIMIT_S
#else
#define USERSTATS_AGE USERSTATS_DB_S
#endif
#endif
/* TODO: summarisation of the userstats after this many days are done
* at the day level and the above stats are deleted from the db
* Obvious WARNING - the larger this is, the more stats in the DB
* This is summary level '2'
*/
#define USERSTATS_DB_D 7
#define USERSTATS_DB_DS (USERSTATS_DB_D * (60*60*24))
// true if _new is newer, i.e. _old is before _new
#define tv_newer(_old, _new) (((_old)->tv_sec == (_new)->tv_sec) ? \
((_old)->tv_usec < (_new)->tv_usec) : \
((_old)->tv_sec < (_new)->tv_sec))
#define tv_equal(_a, _b) (((_a)->tv_sec == (_b)->tv_sec) && \
((_a)->tv_usec == (_b)->tv_usec))
// WORKERSTATUS from various incoming data
typedef struct workerstatus {
int64_t userid;
char workername[TXT_BIG+1];
tv_t last_auth;
tv_t last_share;
double last_diff;
tv_t last_stats;
tv_t last_idle;
// Below gets reset on each block
double diffacc;
double diffinv; // Non-acc
double shareacc;
double shareinv; // Non-acc
} WORKERSTATUS;
#define ALLOC_WORKERSTATUS 1000
#define LIMIT_WORKERSTATUS 0
#define INIT_WORKERSTATUS(_item) INIT_GENERIC(_item, workerstatus)
#define DATA_WORKERSTATUS(_var, _item) DATA_GENERIC(_var, _item, workerstatus, true)
static K_TREE *workerstatus_root;
static K_LIST *workerstatus_free;
static K_STORE *workerstatus_store;
static void _txt_to_data(enum data_type typ, char *nam, char *fld, void *data, size_t siz, WHERE_FFL_ARGS)
{
char *tmp;
switch (typ) {
case TYPE_STR:
// A database field being bigger than local storage is a fatal error
if (siz < (strlen(fld)+1)) {
quithere(1, "Field %s structure size %d is smaller than db %d" WHERE_FFL,
nam, (int)siz, (int)strlen(fld)+1, WHERE_FFL_PASS);
}
strcpy((char *)data, fld);
break;
case TYPE_BIGINT:
if (siz != sizeof(int64_t)) {
quithere(1, "Field %s bigint incorrect structure size %d - should be %d"
WHERE_FFL,
nam, (int)siz, (int)sizeof(int64_t), WHERE_FFL_PASS);
}
*((long long *)data) = atoll(fld);
break;
case TYPE_INT:
if (siz != sizeof(int32_t)) {
quithere(1, "Field %s int incorrect structure size %d - should be %d"
WHERE_FFL,
nam, (int)siz, (int)sizeof(int32_t), WHERE_FFL_PASS);
}
*((int32_t *)data) = atoi(fld);
break;
case TYPE_TV:
if (siz != sizeof(tv_t)) {
quithere(1, "Field %s tv_t incorrect structure size %d - should be %d"
WHERE_FFL,
nam, (int)siz, (int)sizeof(tv_t), WHERE_FFL_PASS);
}
unsigned int yyyy, mm, dd, HH, MM, SS, uS = 0, tz;
char pm[2];
struct tm tm;
time_t tim;
int n;
n = sscanf(fld, "%u-%u-%u %u:%u:%u%1[+-]%u",
&yyyy, &mm, &dd, &HH, &MM, &SS, pm, &tz);
if (n != 8) {
// allow uS
n = sscanf(fld, "%u-%u-%u %u:%u:%u.%u%1[+-]%u",
&yyyy, &mm, &dd, &HH, &MM, &SS, &uS, pm, &tz);
if (n != 9) {
quithere(1, "Field %s tv_t unhandled date '%s' (%d)" WHERE_FFL,
nam, fld, n, WHERE_FFL_PASS);
}
}
tm.tm_sec = (int)SS;
tm.tm_min = (int)MM;
tm.tm_hour = (int)HH;
tm.tm_mday = (int)dd;
tm.tm_mon = (int)mm - 1;
tm.tm_year = (int)yyyy - 1900;
tm.tm_isdst = -1;
tim = timegm(&tm);
if (tim > COMPARE_EXPIRY) {
((tv_t *)data)->tv_sec = default_expiry.tv_sec;
((tv_t *)data)->tv_usec = default_expiry.tv_usec;
} else {
// 2 digit tz (vs 4 digit)
if (tz < 25)
tz *= 60;
// time was converted ignoring tz - so correct it
if (pm[0] == '-')
tim += 60 * tz;
else
tim -= 60 * tz;
((tv_t *)data)->tv_sec = tim;
((tv_t *)data)->tv_usec = uS;
}
break;
case TYPE_CTV:
if (siz != sizeof(tv_t)) {
quithere(1, "Field %s tv_t incorrect structure size %d - should be %d"
WHERE_FFL,
nam, (int)siz, (int)sizeof(tv_t), WHERE_FFL_PASS);
}
long sec, nsec;
int c;
// Caller test for tv_sec=0 for failure
((tv_t *)data)->tv_sec = 0L;
((tv_t *)data)->tv_usec = 0L;
c = sscanf(fld, "%ld,%ld", &sec, &nsec);
if (c > 0) {
((tv_t *)data)->tv_sec = (time_t)sec;
if (c > 1)
((tv_t *)data)->tv_usec = (nsec + 500) / 1000;
if (((tv_t *)data)->tv_sec >= COMPARE_EXPIRY) {
((tv_t *)data)->tv_sec = default_expiry.tv_sec;
((tv_t *)data)->tv_usec = default_expiry.tv_usec;
}
}
break;
case TYPE_BLOB:
tmp = strdup(fld);
if (!tmp) {
quithere(1, "Field %s (%d) OOM" WHERE_FFL,
nam, (int)strlen(fld), WHERE_FFL_PASS);
}
*((char **)data) = tmp;
break;
case TYPE_DOUBLE:
if (siz != sizeof(double)) {
quithere(1, "Field %s int incorrect structure size %d - should be %d"
WHERE_FFL,
nam, (int)siz, (int)sizeof(double), WHERE_FFL_PASS);
}
*((double *)data) = atof(fld);
break;
default:
quithere(1, "Unknown field %s (%d) to convert" WHERE_FFL,
nam, (int)typ, WHERE_FFL_PASS);
break;
}
}
#define txt_to_str(_nam, _fld, _data, _siz) _txt_to_str(_nam, _fld, _data, _siz, WHERE_FFL_HERE)
#define txt_to_bigint(_nam, _fld, _data, _siz) _txt_to_bigint(_nam, _fld, _data, _siz, WHERE_FFL_HERE)
#define txt_to_int(_nam, _fld, _data, _siz) _txt_to_int(_nam, _fld, _data, _siz, WHERE_FFL_HERE)
#define txt_to_tv(_nam, _fld, _data, _siz) _txt_to_tv(_nam, _fld, _data, _siz, WHERE_FFL_HERE)
#define txt_to_ctv(_nam, _fld, _data, _siz) _txt_to_ctv(_nam, _fld, _data, _siz, WHERE_FFL_HERE)
#define txt_to_blob(_nam, _fld, _data) _txt_to_blob(_nam, _fld, _data, WHERE_FFL_HERE)
#define txt_to_double(_nam, _fld, _data, _siz) _txt_to_double(_nam, _fld, _data, _siz, WHERE_FFL_HERE)
// N.B. STRNCPY* macros truncate, whereas this aborts ckdb if src > trg
static void _txt_to_str(char *nam, char *fld, char data[], size_t siz, WHERE_FFL_ARGS)
{
_txt_to_data(TYPE_STR, nam, fld, (void *)data, siz, WHERE_FFL_PASS);
}
static void _txt_to_bigint(char *nam, char *fld, int64_t *data, size_t siz, WHERE_FFL_ARGS)
{
_txt_to_data(TYPE_BIGINT, nam, fld, (void *)data, siz, WHERE_FFL_PASS);
}
static void _txt_to_int(char *nam, char *fld, int32_t *data, size_t siz, WHERE_FFL_ARGS)
{
_txt_to_data(TYPE_INT, nam, fld, (void *)data, siz, WHERE_FFL_PASS);
}
static void _txt_to_tv(char *nam, char *fld, tv_t *data, size_t siz, WHERE_FFL_ARGS)
{
_txt_to_data(TYPE_TV, nam, fld, (void *)data, siz, WHERE_FFL_PASS);
}
// Convert msg S,nS to tv_t
static void _txt_to_ctv(char *nam, char *fld, tv_t *data, size_t siz, WHERE_FFL_ARGS)
{
_txt_to_data(TYPE_CTV, nam, fld, (void *)data, siz, WHERE_FFL_PASS);
}
static void _txt_to_blob(char *nam, char *fld, char *data, WHERE_FFL_ARGS)
{
_txt_to_data(TYPE_BLOB, nam, fld, (void *)(&data), 0, WHERE_FFL_PASS);
}
static void _txt_to_double(char *nam, char *fld, double *data, size_t siz, WHERE_FFL_ARGS)
{
_txt_to_data(TYPE_DOUBLE, nam, fld, (void *)data, siz, WHERE_FFL_PASS);
}
static char *_data_to_buf(enum data_type typ, void *data, char *buf, size_t siz, WHERE_FFL_ARGS)
{
struct tm tm;
if (!buf) {
switch (typ) {
case TYPE_STR:
case TYPE_BLOB:
siz = strlen((char *)data) + 1;
break;
case TYPE_BIGINT:
siz = BIGINT_BUFSIZ;
break;
case TYPE_INT:
siz = INT_BUFSIZ;
break;
case TYPE_TV:
case TYPE_TVS:
siz = DATE_BUFSIZ;
break;
case TYPE_CTV:
siz = CDATE_BUFSIZ;
break;
case TYPE_DOUBLE:
siz = DOUBLE_BUFSIZ;
break;
default:
quithere(1, "Unknown field (%d) to convert" WHERE_FFL,
(int)typ, WHERE_FFL_PASS);
break;
}
buf = malloc(siz);
if (!buf)
quithere(1, "OOM (%d)" WHERE_FFL, (int)siz, WHERE_FFL_PASS);
}
switch (typ) {
case TYPE_STR:
case TYPE_BLOB:
snprintf(buf, siz, "%s", (char *)data);
break;
case TYPE_BIGINT:
snprintf(buf, siz, "%"PRId64, *((uint64_t *)data));
break;
case TYPE_INT:
snprintf(buf, siz, "%"PRId32, *((uint32_t *)data));
break;
case TYPE_TV:
gmtime_r(&(((tv_t *)data)->tv_sec), &tm);
snprintf(buf, siz, "%d-%02d-%02d %02d:%02d:%02d.%06ld+00",
tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
(((tv_t *)data)->tv_usec));
break;
case TYPE_CTV:
snprintf(buf, siz, "%ld,%ld",
(((tv_t *)data)->tv_sec),
(((tv_t *)data)->tv_usec));
break;
case TYPE_TVS:
snprintf(buf, siz, "%ld", (((tv_t *)data)->tv_sec));
break;
case TYPE_DOUBLE:
snprintf(buf, siz, "%f", *((double *)data));
break;
}
return buf;
}
#define str_to_buf(_data, _buf, _siz) _str_to_buf(_data, _buf, _siz, WHERE_FFL_HERE)
#define bigint_to_buf(_data, _buf, _siz) _bigint_to_buf(_data, _buf, _siz, WHERE_FFL_HERE)
#define int_to_buf(_data, _buf, _siz) _int_to_buf(_data, _buf, _siz, WHERE_FFL_HERE)
#define tv_to_buf(_data, _buf, _siz) _tv_to_buf(_data, _buf, _siz, WHERE_FFL_HERE)
#define ctv_to_buf(_data, _buf, _siz) _ctv_to_buf(_data, _buf, _siz, WHERE_FFL_HERE)
#define tvs_to_buf(_data, _buf, _siz) _tvs_to_buf(_data, _buf, _siz, WHERE_FFL_HERE)
//#define blob_to_buf(_data, _buf, _siz) _blob_to_buf(_data, _buf, _siz, WHERE_FFL_HERE)
#define double_to_buf(_data, _buf, _siz) _double_to_buf(_data, _buf, _siz, WHERE_FFL_HERE)
static char *_str_to_buf(char data[], char *buf, size_t siz, WHERE_FFL_ARGS)
{
return _data_to_buf(TYPE_STR, (void *)data, buf, siz, WHERE_FFL_PASS);
}
static char *_bigint_to_buf(int64_t data, char *buf, size_t siz, WHERE_FFL_ARGS)
{
return _data_to_buf(TYPE_BIGINT, (void *)(&data), buf, siz, WHERE_FFL_PASS);
}
static char *_int_to_buf(int32_t data, char *buf, size_t siz, WHERE_FFL_ARGS)
{
return _data_to_buf(TYPE_INT, (void *)(&data), buf, siz, WHERE_FFL_PASS);
}
static char *_tv_to_buf(tv_t *data, char *buf, size_t siz, WHERE_FFL_ARGS)
{
return _data_to_buf(TYPE_TV, (void *)data, buf, siz, WHERE_FFL_PASS);
}
// Convert tv to S,uS
static char *_ctv_to_buf(tv_t *data, char *buf, size_t siz, WHERE_FFL_ARGS)
{
return _data_to_buf(TYPE_CTV, (void *)data, buf, siz, WHERE_FFL_PASS);
}
// Convert tv to seconds (ignore uS)
static char *_tvs_to_buf(tv_t *data, char *buf, size_t siz, WHERE_FFL_ARGS)
{
return _data_to_buf(TYPE_TVS, (void *)data, buf, siz, WHERE_FFL_PASS);
}
/* unused yet
static char *_blob_to_buf(char *data, char *buf, size_t siz, WHERE_FFL_ARGS)
{
return _data_to_buf(TYPE_BLOB, (void *)data, buf, siz, WHERE_FFL_PASS);
}
*/
static char *_double_to_buf(double data, char *buf, size_t siz, WHERE_FFL_ARGS)
{
return _data_to_buf(TYPE_DOUBLE, (void *)(&data), buf, siz, WHERE_FFL_PASS);
}
static char logname[512];
static char *dbcode;
// CCLs are every ...
#define ROLL_S 3600
#define LOGQUE(_msg) log_queue_message(_msg)
#define LOGFILE(_msg) rotating_log_nolock(_msg)
#define LOGDUP "dup."
// low spec version of rotating_log() - no locking
static bool rotating_log_nolock(char *msg)
{
char *filename;
FILE *fp;
bool ok = false;
filename = rotating_filename(logname, time(NULL));
fp = fopen(filename, "a+e");
if (unlikely(!fp)) {
LOGERR("Failed to fopen %s in rotating_log!", filename);
goto stageleft;
}
fprintf(fp, "%s\n", msg);
fclose(fp);
ok = true;
stageleft:
free(filename);
return ok;
}
static void log_queue_message(char *msg)
{
K_ITEM *lq_item;
LOGQUEUE *lq;
K_WLOCK(logqueue_free);
lq_item = k_unlink_head(logqueue_free);
DATA_LOGQUEUE(lq, lq_item);
lq->msg = strdup(msg);
k_add_tail(logqueue_store, lq_item);
K_WUNLOCK(logqueue_free);
}
void logmsg(int loglevel, const char *fmt, ...)
{
int logfd = 0;
char *buf = NULL;
struct tm tm;
time_t now_t;
va_list ap;
char stamp[128];
char *extra = EMPTY;
if (loglevel > global_ckp->loglevel)
return;
now_t = time(NULL);
localtime_r(&now_t, &tm);
snprintf(stamp, sizeof(stamp),
"[%d-%02d-%02d %02d:%02d:%02d]",
tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec);
if (!fmt) {
fprintf(stderr, "%s %s() called without fmt\n", stamp, __func__);
return;
}
if (!global_ckp)
extra = " !!NULL global_ckp!!";
else
logfd = global_ckp->logfd;
va_start(ap, fmt);
VASPRINTF(&buf, fmt, ap);
va_end(ap);
if (logfd) {
FILE *LOGFP = global_ckp->logfp;
flock(logfd, LOCK_EX);
fprintf(LOGFP, "%s %s", stamp, buf);
if (loglevel <= LOG_ERR && errno != 0)
fprintf(LOGFP, " with errno %d: %s", errno, strerror(errno));
errno = 0;
fprintf(LOGFP, "\n");
flock(logfd, LOCK_UN);
}
if (loglevel <= LOG_WARNING) {
if (loglevel <= LOG_ERR && errno != 0) {
fprintf(stderr, "%s %s with errno %d: %s%s\n",
stamp, buf, errno, strerror(errno), extra);
errno = 0;
} else
fprintf(stderr, "%s %s%s\n", stamp, buf, extra);
fflush(stderr);
}
free(buf);
}
static void setnow(tv_t *now)
{
ts_t spec;
spec.tv_sec = 0;
spec.tv_nsec = 0;
clock_gettime(CLOCK_REALTIME, &spec);
now->tv_sec = spec.tv_sec;
now->tv_usec = spec.tv_nsec / 1000;
}
// Limits are all +/-1s since on the live machine all were well within that
static void check_createdate_ccl(char *cmd, tv_t *cd)
{
static tv_t last_cd;
static char last_cmd[CMD_SIZ+1];
char cd_buf1[DATE_BUFSIZ], cd_buf2[DATE_BUFSIZ];
char *filename;
double td;
if (cd->tv_sec < reload_timestamp.tv_sec ||
cd->tv_sec >= (reload_timestamp.tv_sec + ROLL_S)) {
ccl_mismatch_abs++;
td = tvdiff(cd, &reload_timestamp);
if (td < -1 || td > ROLL_S + 1) {
ccl_mismatch++;
filename = rotating_filename("", reload_timestamp.tv_sec);
tv_to_buf(cd, cd_buf1, sizeof(cd_buf1));
LOGERR("%s(): CCL contains mismatch data: cmd:%s CCL:%.10s cd:%s",
__func__, cmd, filename, cd_buf1);
free(filename);
}
if (ccl_mismatch_min > td)
ccl_mismatch_min = td;
if (ccl_mismatch_max < td)
ccl_mismatch_max = td;
}
td = tvdiff(cd, &last_cd);
if (td < 0.0) {
ccl_unordered_abs++;
if (ccl_unordered_most > td)
ccl_unordered_most = td;
}
if (td < -1.0) {
ccl_unordered++;
tv_to_buf(&last_cd, cd_buf1, sizeof(cd_buf1));
tv_to_buf(cd, cd_buf2, sizeof(cd_buf2));
LOGERR("%s(): CCL time unordered: %s<->%s %ld,%ld<->%ld,%ld %s<->%s",
__func__, last_cmd, cmd, last_cd.tv_sec,last_cd.tv_usec,
cd->tv_sec, cd->tv_usec, cd_buf1, cd_buf2);
}
copy_tv(&last_cd, cd);
STRNCPY(last_cmd, cmd);
}
#define CKPQ_READ true
#define CKPQ_WRITE false
#define CKPQexec(_conn, _qry, _isread) _CKPQexec(_conn, _qry, _isread, WHERE_FFL_HERE)
// Bug check to ensure no unexpected write txns occur
static PGresult *_CKPQexec(PGconn *conn, const char *qry, bool isread, WHERE_FFL_ARGS)
{
// It would slow it down, but could check qry for insert/update/...
if (!isread && confirm_sharesummary)
quitfrom(1, file, func, line, "BUG: write txn during confirm");
return PQexec(conn, qry);
}
#define CKPQexecParams(_conn, _qry, _p1, _p2, _p3, _p4, _p5, _p6, _isread) \
_CKPQexecParams(_conn, _qry, _p1, _p2, _p3, _p4, _p5, _p6, \
_isread, WHERE_FFL_HERE)
static PGresult *_CKPQexecParams(PGconn *conn, const char *qry,
int nParams,
const Oid *paramTypes,
const char *const * paramValues,
const int *paramLengths,
const int *paramFormats,
int resultFormat,
bool isread, WHERE_FFL_ARGS)
{
// It would slow it down, but could check qry for insert/update/...
if (!isread && confirm_sharesummary)
quitfrom(1, file, func, line, "BUG: write txn during confirm");
return PQexecParams(conn, qry, nParams, paramTypes, paramValues, paramLengths,
paramFormats, resultFormat);
}
// Force use CKPQ... for PQ functions in use
#define PQexec CKPQexec
#define PQexecParams CKPQexecParams
static uint64_t ticks;
static time_t last_tick;
static void tick()
{
time_t now;
char ch;
now = time(NULL);
if (now != last_tick) {
last_tick = now;
ch = status_chars[ticks++ & 0x3];
putchar(ch);
putchar('\r');
fflush(stdout);
}
}
static void dsp_transfer(K_ITEM *item, FILE *stream)
{
TRANSFER *t;
if (!item)
fprintf(stream, "%s() called with (null) item\n", __func__);
else {
DATA_TRANSFER(t, item);
fprintf(stream, " name='%s' mvalue='%s' malloc=%c\n",
t->name, t->mvalue,
(t->svalue == t->mvalue) ? 'N' : 'Y');
}
}
// order by name asc
static cmp_t cmp_transfer(K_ITEM *a, K_ITEM *b)
{
TRANSFER *ta, *tb;
DATA_TRANSFER(ta, a);
DATA_TRANSFER(tb, b);
return CMP_STR(ta->name, tb->name);
}
static K_ITEM *find_transfer(K_TREE *trf_root, char *name)
{
TRANSFER transfer;
K_TREE_CTX ctx[1];
K_ITEM look;
STRNCPY(transfer.name, name);
INIT_TRANSFER(&look);
look.data = (void *)(&transfer);
return find_in_ktree(trf_root, &look, cmp_transfer, ctx);
}
#define optional_name(_root, _name, _len, _patt) \
_optional_name(_root, _name, _len, _patt, WHERE_FFL_HERE)
static K_ITEM *_optional_name(K_TREE *trf_root, char *name, int len, char *patt,
WHERE_FFL_ARGS)
{
TRANSFER *trf;
K_ITEM *item;
char *mvalue;
regex_t re;
size_t dlen;
int ret;
item = find_transfer(trf_root, name);
if (!item)
return NULL;
DATA_TRANSFER(trf, item);
mvalue = trf->mvalue;
if (mvalue)
dlen = strlen(mvalue);
else
dlen = 0;
if (!mvalue || (int)dlen < len) {
if (!mvalue) {
LOGERR("%s(): field '%s' NULL (%d) from %s():%d",
__func__, name, (int)dlen, len, func, line);
}
return NULL;
}
if (patt) {
if (regcomp(&re, patt, REG_NOSUB) != 0)
return NULL;
ret = regexec(&re, mvalue, (size_t)0, NULL, 0);
regfree(&re);
if (ret != 0)
return NULL;
}
return item;
}
#define require_name(_root, _name, _len, _patt, _reply, _siz) \
_require_name(_root, _name, _len, _patt, _reply, \
_siz, WHERE_FFL_HERE)
static K_ITEM *_require_name(K_TREE *trf_root, char *name, int len, char *patt,
char *reply, size_t siz, WHERE_FFL_ARGS)
{
TRANSFER *trf;
K_ITEM *item;
char *mvalue;
regex_t re;
size_t dlen;
int ret;
item = find_transfer(trf_root, name);
if (!item) {
LOGERR("%s(): failed, field '%s' missing from %s():%d",
__func__, name, func, line);
snprintf(reply, siz, "failed.missing %s", name);
return NULL;
}
DATA_TRANSFER(trf, item);
mvalue = trf->mvalue;
if (mvalue)
dlen = strlen(mvalue);
else
dlen = 0;
if (!mvalue || (int)dlen < len) {
LOGERR("%s(): failed, field '%s' short (%s%d<%d) from %s():%d",
__func__, name, mvalue ? EMPTY : "null",
(int)dlen, len, func, line);
snprintf(reply, siz, "failed.short %s", name);
return NULL;
}
if (patt) {
if (regcomp(&re, patt, REG_NOSUB) != 0) {
LOGERR("%s(): failed, field '%s' failed to"
" compile patt from %s():%d",
__func__, name, func, line);
snprintf(reply, siz, "failed.REC %s", name);
return NULL;
}
ret = regexec(&re, mvalue, (size_t)0, NULL, 0);
regfree(&re);
if (ret != 0) {
LOGERR("%s(): failed, field '%s' invalid from %s():%d",
__func__, name, func, line);
snprintf(reply, siz, "failed.invalid %s", name);
return NULL;
}
}
return item;
}
static PGconn *dbconnect()
{
char conninfo[128];
PGconn *conn;
snprintf(conninfo, sizeof(conninfo),
"host=127.0.0.1 dbname=%s user=%s%s%s",
db_name, db_user,
db_pass ? " password=" : "",
db_pass ? db_pass : "");
conn = PQconnectdb(conninfo);
if (PQstatus(conn) != CONNECTION_OK)
quithere(1, "ERR: Failed to connect to db '%s'", pqerrmsg(conn));
return conn;
}
static int64_t nextid(PGconn *conn, char *idname, int64_t increment,
tv_t *cd, char *by, char *code, char *inet)
{
ExecStatusType rescode;
bool conned = false;
PGresult *res;
char qry[1024];
char *params[5];
int par;
int64_t lastid;
char *field;
bool ok;
int n;
lastid = 0;
snprintf(qry, sizeof(qry), "select lastid from idcontrol "
"where idname='%s' for update",
idname);
if (conn == NULL) {
conn = dbconnect();
conned = true;
}
res = PQexec(conn, qry, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Select", rescode, conn);
goto cleanup;
}
n = PQnfields(res);
if (n != 1) {
LOGERR("%s(): Invalid field count - should be %d, but is %d",
__func__, 1, n);
goto cleanup;
}
n = PQntuples(res);
if (n < 1) {
LOGERR("%s(): No matching idname='%s'", __func__, idname);
goto cleanup;
}
ok = true;
PQ_GET_FLD(res, 0, "lastid", field, ok);
if (!ok)
goto cleanup;
TXT_TO_BIGINT("lastid", field, lastid);
PQclear(res);
lastid += increment;
snprintf(qry, sizeof(qry), "update idcontrol set "
"lastid=$1, modifydate=$2, modifyby=$3, "
"modifycode=$4, modifyinet=$5 "
"where idname='%s'",
idname);
par = 0;
params[par++] = bigint_to_buf(lastid, NULL, 0);
params[par++] = tv_to_buf(cd, NULL, 0);
params[par++] = str_to_buf(by, NULL, 0);
params[par++] = str_to_buf(code, NULL, 0);
params[par++] = str_to_buf(inet, NULL, 0);
PARCHK(par, params);
res = PQexecParams(conn, qry, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Update", rescode, conn);
lastid = 0;
}
for (n = 0; n < par; n++)
free(params[n]);
cleanup:
PQclear(res);
if (conned)
PQfinish(conn);
return lastid;
}
// order by userid asc,workername asc
static cmp_t cmp_workerstatus(K_ITEM *a, K_ITEM *b)
{
WORKERSTATUS *wa, *wb;
DATA_WORKERSTATUS(wa, a);
DATA_WORKERSTATUS(wb, b);
cmp_t c = CMP_BIGINT(wa->userid, wb->userid);
if (c == 0)
c = CMP_STR(wa->workername, wb->workername);
return c;
}
/* TODO: replace a lot of the code for all data types that codes finds,
* each with specific functions for finding, to centralise the finds,
* with passed ctx's */
static K_ITEM *get_workerstatus(int64_t userid, char *workername)
{
WORKERSTATUS workerstatus;
K_TREE_CTX ctx[1];
K_ITEM look, *find;
workerstatus.userid = userid;
STRNCPY(workerstatus.workername, workername);
INIT_WORKERSTATUS(&look);
look.data = (void *)(&workerstatus);
K_RLOCK(workerstatus_free);
find = find_in_ktree(workerstatus_root, &look, cmp_workerstatus, ctx);
K_RUNLOCK(workerstatus_free);
return find;
}
/* Worker loading/creation calls this with create = true
* All others with create = false since the workerstatus should exist
* Failure is a code bug and a reported error, but handled anyway
* This has 2 sets of file/func/line to allow 2 levels of traceback
*/
static K_ITEM *_find_create_workerstatus(int64_t userid, char *workername,
bool create, const char *file2,
const char *func2, const int line2,
WHERE_FFL_ARGS)
{
WORKERSTATUS *row;
K_ITEM *item;
item = get_workerstatus(userid, workername);
if (!item) {
if (!create) {
LOGEMERG("%s(): Missing workerstatus %"PRId64"/%s"
WHERE_FFL WHERE_FFL,
__func__, userid, workername,
file2, func2, line2, WHERE_FFL_PASS);
return NULL;
}
K_WLOCK(workerstatus_free);
item = k_unlink_head(workerstatus_free);
DATA_WORKERSTATUS(row, item);
bzero(row, sizeof(*row));
row->userid = userid;
STRNCPY(row->workername, workername);
workerstatus_root = add_to_ktree(workerstatus_root, item, cmp_workerstatus);
k_add_head(workerstatus_store, item);
K_WUNLOCK(workerstatus_free);
}
return item;
}
#define find_create_workerstatus(_u, _w, _file, _func, _line) \
_find_create_workerstatus(_u, _w, true, _file, _func, _line, WHERE_FFL_HERE)
#define find_workerstatus(_u, _w, _file, _func, _line) \
_find_create_workerstatus(_u, _w, false, _file, _func, _line, WHERE_FFL_HERE)
static cmp_t cmp_userstats_workerstatus(K_ITEM *a, K_ITEM *b);
static cmp_t cmp_sharesummary(K_ITEM *a, K_ITEM *b);
static void zero_on_new_block()
{
WORKERSTATUS *workerstatus;
K_TREE_CTX ctx[1];
K_ITEM *ws_item;
K_WLOCK(workerstatus_free);
pool.diffacc = pool.diffinv = pool.shareacc =
pool.shareinv = pool.best_sdiff = 0;
ws_item = first_in_ktree(workerstatus_root, ctx);
while (ws_item) {
DATA_WORKERSTATUS(workerstatus, ws_item);
workerstatus->diffacc = workerstatus->diffinv =
workerstatus->shareacc = workerstatus->shareinv = 0.0;
ws_item = next_in_ktree(ctx);
}
K_WUNLOCK(workerstatus_free);
}
/* Currently only used at the end of the startup
* Will need to add locking if it's used, later, after startup completes */
static void set_block_share_counters()
{
K_TREE_CTX ctx[1];
K_ITEM *ss_item, ss_look, *ws_item;
WORKERSTATUS *workerstatus;
SHARESUMMARY *sharesummary, looksharesummary;
INIT_SHARESUMMARY(&ss_look);
zero_on_new_block();
ws_item = NULL;
/* From the end backwards so we can skip the workinfoid's we don't
* want by jumping back to just before the current worker when the
* workinfoid goes below the limit */
K_RLOCK(sharesummary_free);
ss_item = last_in_ktree(sharesummary_root, ctx);
while (ss_item) {
DATA_SHARESUMMARY(sharesummary, ss_item);
if (sharesummary->workinfoid < pool.workinfoid) {
// Skip back to the next worker
looksharesummary.userid = sharesummary->userid;
STRNCPY(looksharesummary.workername,
sharesummary->workername);
looksharesummary.workinfoid = -1;
ss_look.data = (void *)(&looksharesummary);
ss_item = find_before_in_ktree(sharesummary_root, &ss_look,
cmp_sharesummary, ctx);
continue;
}
/* Check for user/workername change for new workerstatus
* The tree has user/workername grouped together in order
* so this will only be once per user/workername */
if (!ws_item ||
sharesummary->userid != workerstatus->userid ||
strcmp(sharesummary->workername, workerstatus->workername)) {
/* This is to trigger a console error if it is missing
* since it should always exist
* However, it is simplest to simply create it
* and keep going */
K_RUNLOCK(sharesummary_free);
ws_item = find_workerstatus(sharesummary->userid,
sharesummary->workername,
__FILE__, __func__, __LINE__);
if (!ws_item) {
ws_item = find_create_workerstatus(sharesummary->userid,
sharesummary->workername,
__FILE__, __func__, __LINE__);
}
K_RLOCK(sharesummary_free);
DATA_WORKERSTATUS(workerstatus, ws_item);
}
pool.diffacc += sharesummary->diffacc;
pool.diffinv += sharesummary->diffsta + sharesummary->diffdup +
sharesummary->diffhi + sharesummary->diffrej;
workerstatus->diffacc += sharesummary->diffacc;
workerstatus->diffinv += sharesummary->diffsta + sharesummary->diffdup +
sharesummary->diffhi + sharesummary->diffrej;
workerstatus->shareacc += sharesummary->shareacc;
workerstatus->shareinv += sharesummary->sharesta + sharesummary->sharedup +
sharesummary->sharehi + sharesummary->sharerej;
ss_item = prev_in_ktree(ctx);
}
K_RUNLOCK(sharesummary_free);
}
/* All data is loaded, now update workerstatus fields
TODO: combine set_block_share_counters() with this? */
static void workerstatus_ready()
{
K_TREE_CTX ws_ctx[1], us_ctx[1], ss_ctx[1];
K_ITEM *ws_item, us_look, ss_look, *us_item, *ss_item;
USERSTATS lookuserstats, *userstats;
SHARESUMMARY looksharesummary, *sharesummary;
WORKERSTATUS *workerstatus;
INIT_USERSTATS(&us_look);
INIT_SHARESUMMARY(&ss_look);
ws_item = first_in_ktree(workerstatus_root, ws_ctx);
while (ws_item) {
DATA_WORKERSTATUS(workerstatus, ws_item);
lookuserstats.userid = workerstatus->userid;
STRNCPY(lookuserstats.workername, workerstatus->workername);
lookuserstats.statsdate.tv_sec = date_eot.tv_sec;
lookuserstats.statsdate.tv_usec = date_eot.tv_usec;
us_look.data = (void *)(&lookuserstats);
us_item = find_before_in_ktree(userstats_workerstatus_root, &us_look,
cmp_userstats_workerstatus, us_ctx);
if (us_item) {
DATA_USERSTATS(userstats, us_item);
if (userstats->idle) {
if (tv_newer(&(workerstatus->last_idle),
&(userstats->statsdate))) {
copy_tv(&(workerstatus->last_idle),
&(userstats->statsdate));
}
} else {
if (tv_newer(&(workerstatus->last_stats),
&(userstats->statsdate))) {
copy_tv(&(workerstatus->last_stats),
&(userstats->statsdate));
}
}
}
looksharesummary.userid = workerstatus->userid;
STRNCPY(looksharesummary.workername, workerstatus->workername);
looksharesummary.workinfoid = MAXID;
ss_look.data = (void *)(&looksharesummary);
K_RLOCK(sharesummary_free);
ss_item = find_before_in_ktree(sharesummary_root, &ss_look,
cmp_sharesummary, ss_ctx);
K_RUNLOCK(sharesummary_free);
if (ss_item) {
DATA_SHARESUMMARY(sharesummary, ss_item);
if (tv_newer(&(workerstatus->last_share),
&(sharesummary->lastshare))) {
copy_tv(&(workerstatus->last_share),
&(sharesummary->lastshare));
workerstatus->last_diff =
sharesummary->lastdiffacc;
}
}
ws_item = next_in_ktree(ws_ctx);
}
}
#define workerstatus_update(_auths, _shares, _userstats) \
_workerstatus_update(_auths, _shares, _userstats, WHERE_FFL_HERE)
static void _workerstatus_update(AUTHS *auths, SHARES *shares,
USERSTATS *userstats, WHERE_FFL_ARGS)
{
WORKERSTATUS *row;
K_ITEM *item;
if (auths) {
item = find_workerstatus(auths->userid, auths->workername,
file, func, line);
if (item) {
DATA_WORKERSTATUS(row, item);
if (tv_newer(&(row->last_auth), &(auths->createdate)))
copy_tv(&(row->last_auth), &(auths->createdate));
}
}
if (startup_complete && shares) {
if (shares->errn == SE_NONE) {
pool.diffacc += shares->diff;
pool.shareacc++;
} else {
pool.diffinv += shares->diff;
pool.shareinv++;
}
item = find_workerstatus(shares->userid, shares->workername,
file, func, line);
if (item) {
DATA_WORKERSTATUS(row, item);
if (tv_newer(&(row->last_share), &(shares->createdate))) {
copy_tv(&(row->last_share), &(shares->createdate));
row->last_diff = shares->diff;
}
if (shares->errn == SE_NONE) {
row->diffacc += shares->diff;
row->shareacc++;
} else {
row->diffinv += shares->diff;
row->shareinv++;
}
}
}
if (startup_complete && userstats) {
item = find_workerstatus(userstats->userid, userstats->workername,
file, func, line);
if (item) {
DATA_WORKERSTATUS(row, item);
if (userstats->idle) {
if (tv_newer(&(row->last_idle), &(userstats->statsdate)))
copy_tv(&(row->last_idle), &(userstats->statsdate));
} else {
if (tv_newer(&(row->last_stats), &(userstats->statsdate)))
copy_tv(&(row->last_stats), &(userstats->statsdate));
}
}
}
}
// default tree order by username asc,expirydate desc
static cmp_t cmp_users(K_ITEM *a, K_ITEM *b)
{
USERS *ua, *ub;
DATA_USERS(ua, a);
DATA_USERS(ub, b);
cmp_t c = CMP_STR(ua->username, ub->username);
if (c == 0)
c = CMP_TV(ub->expirydate, ua->expirydate);
return c;
}
// order by userid asc,expirydate desc
static cmp_t cmp_userid(K_ITEM *a, K_ITEM *b)
{
USERS *ua, *ub;
DATA_USERS(ua, a);
DATA_USERS(ub, b);
cmp_t c = CMP_BIGINT(ua->userid, ub->userid);
if (c == 0)
c = CMP_TV(ub->expirydate, ua->expirydate);
return c;
}
// Must be R or W locked before call
static K_ITEM *find_users(char *username)
{
USERS users;
K_TREE_CTX ctx[1];
K_ITEM look;
STRNCPY(users.username, username);
users.expirydate.tv_sec = default_expiry.tv_sec;
users.expirydate.tv_usec = default_expiry.tv_usec;
INIT_USERS(&look);
look.data = (void *)(&users);
return find_in_ktree(users_root, &look, cmp_users, ctx);
}
// Must be R or W locked before call
static K_ITEM *find_userid(int64_t userid)
{
USERS users;
K_TREE_CTX ctx[1];
K_ITEM look;
users.userid = userid;
users.expirydate.tv_sec = default_expiry.tv_sec;
users.expirydate.tv_usec = default_expiry.tv_usec;
INIT_USERS(&look);
look.data = (void *)(&users);
return find_in_ktree(userid_root, &look, cmp_userid, ctx);
}
// TODO: endian?
static void make_salt(USERS *users)
{
long int r1, r2, r3, r4;
r1 = random();
r2 = random();
r3 = random();
r4 = random();
__bin2hex(users->salt, (void *)(&r1), 4);
__bin2hex(users->salt+8, (void *)(&r2), 4);
__bin2hex(users->salt+16, (void *)(&r3), 4);
__bin2hex(users->salt+24, (void *)(&r4), 4);
}
static void password_hash(char *username, char *passwordhash, char *salt, char *result, size_t siz)
{
char tohash[TXT_BIG+1];
char buf[TXT_BIG+1];
size_t len, tot;
char why[1024];
if (siz < SHA256SIZHEX+1) {
snprintf(why, sizeof(why),
"target result too small (%d/%d)",
(int)siz, SHA256SIZHEX+1);
goto hashfail;
}
if (sizeof(buf) < SHA256SIZBIN) {
snprintf(why, sizeof(why),
"temporary target buf too small (%d/%d)",
(int)sizeof(buf), SHA256SIZBIN);
goto hashfail;
}
tot = len = strlen(passwordhash) / 2;
if (len != SHA256SIZBIN) {
snprintf(why, sizeof(why),
"passwordhash wrong size (%d/%d)",
(int)len, SHA256SIZBIN);
goto hashfail;
}
if (len > sizeof(tohash)) {
snprintf(why, sizeof(why),
"temporary tohash too small (%d/%d)",
(int)sizeof(tohash), (int)len);
goto hashfail;
}
hex2bin(tohash, passwordhash, len);
len = strlen(salt) / 2;
if (len != SALTSIZBIN) {
snprintf(why, sizeof(why),
"salt wrong size (%d/%d)",
(int)len, SALTSIZBIN);
goto hashfail;
}
if ((tot + len) > sizeof(tohash)) {
snprintf(why, sizeof(why),
"passwordhash+salt too big (%d/%d)",
(int)(tot + len), (int)sizeof(tohash));
goto hashfail;
}
hex2bin(tohash+tot, salt, len);
tot += len;
sha256((const unsigned char *)tohash, (unsigned int)tot, (unsigned char *)buf);
__bin2hex(result, (void *)buf, SHA256SIZBIN);
return;
hashfail:
LOGERR("%s() Failed to hash '%s' password: %s",
__func__, username, why);
result[0] = '\0';
}
static bool check_hash(USERS *users, char *passwordhash)
{
char hex[SHA256SIZHEX+1];
if (*(users->salt)) {
password_hash(users->username, passwordhash, users->salt, hex, sizeof(hex));
return (strcasecmp(hex, users->passwordhash) == 0);
} else
return (strcasecmp(passwordhash, users->passwordhash) == 0);
}
static bool users_pass_email(PGconn *conn, K_ITEM *u_item, char *oldhash,
char *newhash, char *email, char *by, char *code,
char *inet, tv_t *cd, K_TREE *trf_root)
{
ExecStatusType rescode;
bool conned = false;
K_TREE_CTX ctx[1];
PGresult *res;
K_ITEM *item;
int n;
USERS *row, *users;
char *upd, *ins;
bool ok = false;
char *params[5 + HISTORYDATECOUNT];
bool hash;
int par;
LOGDEBUG("%s(): change", __func__);
if (oldhash != NULL)
hash = true;
else
hash = false;
DATA_USERS(users, u_item);
// i.e. if oldhash == EMPTY, just overwrite with new
if (hash && oldhash != EMPTY && !check_hash(users, oldhash))
return false;
K_WLOCK(users_free);
item = k_unlink_head(users_free);
K_WUNLOCK(users_free);
DATA_USERS(row, item);
memcpy(row, users, sizeof(*row));
// Update one, leave the other
if (hash) {
// New salt each password change
make_salt(row);
password_hash(row->username, newhash, row->salt,
row->passwordhash, sizeof(row->passwordhash));
} else
STRNCPY(row->emailaddress, email);
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);
upd = "update users set expirydate=$1 where userid=$2 and expirydate=$3";
par = 0;
params[par++] = tv_to_buf(cd, NULL, 0);
params[par++] = bigint_to_buf(row->userid, NULL, 0);
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0);
PARCHKVAL(par, 3, params);
if (conn == NULL) {
conn = dbconnect();
conned = true;
}
// Beginning of a write txn
res = PQexec(conn, "Begin", CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Begin", rescode, conn);
goto unparam;
}
PQclear(res);
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Update", rescode, conn);
goto unparam;
}
for (n = 0; n < par; n++)
free(params[n]);
par = 0;
params[par++] = bigint_to_buf(row->userid, NULL, 0);
params[par++] = tv_to_buf(cd, NULL, 0);
// Copy them both in - one will be new and one will be old
params[par++] = str_to_buf(row->emailaddress, NULL, 0);
params[par++] = str_to_buf(row->passwordhash, NULL, 0);
// New salt for each password change (or recopy old)
params[par++] = str_to_buf(row->salt, NULL, 0);
HISTORYDATEPARAMS(params, par, row);
PARCHKVAL(par, 5 + HISTORYDATECOUNT, params); // 10 as per ins
ins = "insert into users "
"(userid,username,status,emailaddress,joineddate,"
"passwordhash,secondaryuserid,salt"
HISTORYDATECONTROL ") select "
"userid,username,status,$3,joineddate,"
"$4,secondaryuserid,$5,"
"$6,$7,$8,$9,$10 from users where "
"userid=$1 and expirydate=$2";
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
PQclear(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
res = PQexec(conn, "Rollback", CKPQ_WRITE);
goto unparam;
}
res = PQexec(conn, "Commit", CKPQ_WRITE);
ok = true;
unparam:
PQclear(res);
if (conned)
PQfinish(conn);
for (n = 0; n < par; n++)
free(params[n]);
K_WLOCK(users_free);
if (!ok)
k_add_head(users_free, item);
else {
users_root = remove_from_ktree(users_root, u_item, cmp_users, ctx);
userid_root = remove_from_ktree(userid_root, u_item, cmp_userid, ctx);
copy_tv(&(users->expirydate), cd);
users_root = add_to_ktree(users_root, u_item, cmp_users);
userid_root = add_to_ktree(userid_root, u_item, cmp_userid);
users_root = add_to_ktree(users_root, item, cmp_users);
userid_root = add_to_ktree(userid_root, item, cmp_userid);
k_add_head(users_store, item);
}
K_WUNLOCK(users_free);
return ok;
}
static K_ITEM *users_add(PGconn *conn, char *username, char *emailaddress,
char *passwordhash, char *by, char *code, char *inet,
tv_t *cd, K_TREE *trf_root)
{
ExecStatusType rescode;
bool conned = false;
PGresult *res;
K_ITEM *item;
int n;
USERS *row;
char *ins;
char tohash[64];
uint64_t hash;
__maybe_unused uint64_t tmp;
bool ok = false;
char *params[8 + HISTORYDATECOUNT];
int par;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(users_free);
item = k_unlink_head(users_free);
K_WUNLOCK(users_free);
DATA_USERS(row, item);
row->userid = nextid(conn, "userid", (int64_t)(666 + (random() % 334)),
cd, by, code, inet);
if (row->userid == 0)
goto unitem;
// TODO: pre-check the username exists? (to save finding out via a DB error)
STRNCPY(row->username, username);
row->status[0] = '\0';
STRNCPY(row->emailaddress, emailaddress);
STRNCPY(row->passwordhash, passwordhash);
snprintf(tohash, sizeof(tohash), "%s&#%s", username, emailaddress);
HASH_BER(tohash, strlen(tohash), 1, hash, tmp);
__bin2hex(row->secondaryuserid, (void *)(&hash), sizeof(hash));
make_salt(row);
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);
// copy createdate
row->joineddate.tv_sec = row->createdate.tv_sec;
row->joineddate.tv_usec = row->createdate.tv_usec;
par = 0;
params[par++] = bigint_to_buf(row->userid, NULL, 0);
params[par++] = str_to_buf(row->username, NULL, 0);
params[par++] = str_to_buf(row->status, NULL, 0);
params[par++] = str_to_buf(row->emailaddress, NULL, 0);
params[par++] = tv_to_buf(&(row->joineddate), NULL, 0);
params[par++] = str_to_buf(row->passwordhash, NULL, 0);
params[par++] = str_to_buf(row->secondaryuserid, NULL, 0);
params[par++] = str_to_buf(row->salt, NULL, 0);
HISTORYDATEPARAMS(params, par, row);
PARCHK(par, params);
ins = "insert into users "
"(userid,username,status,emailaddress,joineddate,passwordhash,"
"secondaryuserid,salt"
HISTORYDATECONTROL ") values (" PQPARAM13 ")";
if (!conn) {
conn = dbconnect();
conned = true;
}
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
goto unparam;
}
ok = true;
unparam:
PQclear(res);
if (conned)
PQfinish(conn);
for (n = 0; n < par; n++)
free(params[n]);
unitem:
K_WLOCK(users_free);
if (!ok)
k_add_head(users_free, item);
else {
users_root = add_to_ktree(users_root, item, cmp_users);
userid_root = add_to_ktree(userid_root, item, cmp_userid);
k_add_head(users_store, item);
}
K_WUNLOCK(users_free);
if (ok)
return item;
else
return NULL;
}
static bool users_fill(PGconn *conn)
{
ExecStatusType rescode;
PGresult *res;
K_ITEM *item;
int n, i;
USERS *row;
char *field;
char *sel;
int fields = 8;
bool ok;
LOGDEBUG("%s(): select", __func__);
sel = "select "
"userid,username,status,emailaddress,joineddate,"
"passwordhash,secondaryuserid,salt"
HISTORYDATECONTROL
" from users";
res = PQexec(conn, sel, CKPQ_READ);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Select", rescode, conn);
PQclear(res);
return false;
}
n = PQnfields(res);
if (n != (fields + HISTORYDATECOUNT)) {
LOGERR("%s(): Invalid field count - should be %d, but is %d",
__func__, fields + HISTORYDATECOUNT, n);
PQclear(res);
return false;
}
n = PQntuples(res);
LOGDEBUG("%s(): tree build count %d", __func__, n);
ok = true;
K_WLOCK(users_free);
for (i = 0; i < n; i++) {
item = k_unlink_head(users_free);
DATA_USERS(row, item);
if (everyone_die) {
ok = false;
break;
}
PQ_GET_FLD(res, i, "userid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("userid", field, row->userid);
PQ_GET_FLD(res, i, "username", field, ok);
if (!ok)
break;
TXT_TO_STR("username", field, row->username);
PQ_GET_FLD(res, i, "status", field, ok);
if (!ok)
break;
TXT_TO_STR("status", field, row->status);
PQ_GET_FLD(res, i, "emailaddress", field, ok);
if (!ok)
break;
TXT_TO_STR("emailaddress", field, row->emailaddress);
PQ_GET_FLD(res, i, "joineddate", field, ok);
if (!ok)
break;
TXT_TO_TV("joineddate", field, row->joineddate);
PQ_GET_FLD(res, i, "passwordhash", field, ok);
if (!ok)
break;
TXT_TO_STR("passwordhash", field, row->passwordhash);
PQ_GET_FLD(res, i, "secondaryuserid", field, ok);
if (!ok)
break;
TXT_TO_STR("secondaryuserid", field, row->secondaryuserid);
PQ_GET_FLD(res, i, "salt", field, ok);
if (!ok)
break;
TXT_TO_STR("salt", field, row->salt);
HISTORYDATEFLDS(res, i, row, ok);
if (!ok)
break;
users_root = add_to_ktree(users_root, item, cmp_users);
userid_root = add_to_ktree(userid_root, item, cmp_userid);
k_add_head(users_store, item);
}
if (!ok)
k_add_head(users_free, item);
K_WUNLOCK(users_free);
PQclear(res);
if (ok) {
LOGDEBUG("%s(): built", __func__);
LOGWARNING("%s(): loaded %d users records", __func__, n);
}
return ok;
}
void users_reload()
{
PGconn *conn = dbconnect();
K_WLOCK(users_free);
users_root = free_ktree(users_root, NULL);
userid_root = free_ktree(userid_root, NULL);
k_list_transfer_to_head(users_store, users_free);
K_WUNLOCK(users_free);
users_fill(conn);
PQfinish(conn);
}
// default tree order by userid asc,attname asc,expirydate desc
static cmp_t cmp_useratts(K_ITEM *a, K_ITEM *b)
{
USERATTS *ua, *ub;
DATA_USERATTS(ua, a);
DATA_USERATTS(ub, b);
cmp_t c = CMP_BIGINT(ua->userid, ub->userid);
if (c == 0) {
c = CMP_STR(ua->attname, ub->attname);
if (c == 0)
c = CMP_TV(ub->expirydate, ua->expirydate);
}
return c;
}
// Must be R or W locked before call
static K_ITEM *find_useratts(int64_t userid, char *attname)
{
USERATTS useratts;
K_TREE_CTX ctx[1];
K_ITEM look;
useratts.userid = userid;
STRNCPY(useratts.attname, attname);
useratts.expirydate.tv_sec = default_expiry.tv_sec;
useratts.expirydate.tv_usec = default_expiry.tv_usec;
INIT_USERATTS(&look);
look.data = (void *)(&useratts);
return find_in_ktree(useratts_root, &look, cmp_useratts, ctx);
}
static bool useratts_item_add(PGconn *conn, K_ITEM *ua_item, tv_t *cd, bool begun)
{
ExecStatusType rescode;
bool conned = false;
K_TREE_CTX ctx[1];
PGresult *res;
K_ITEM *old_item;
USERATTS *old_useratts, *useratts;
char *upd, *ins;
bool ok = false;
char *params[9 + HISTORYDATECOUNT];
int n, par;
LOGDEBUG("%s(): add", __func__);
DATA_USERATTS(useratts, ua_item);
K_RLOCK(useratts_free);
old_item = find_useratts(useratts->userid, useratts->attname);
K_RUNLOCK(useratts_free);
DATA_USERATTS_NULL(old_useratts, old_item);
/* N.B. the values of the old ua_item record, if it exists,
* are completely ignored i.e. you must provide all values required */
if (!conn) {
conn = dbconnect();
conned = true;
}
if (!begun) {
// Beginning of a write txn
res = PQexec(conn, "Begin", CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Begin", rescode, conn);
goto unparam;
}
PQclear(res);
}
if (old_item) {
upd = "update useratts set expirydate=$1 where userid=$2 and "
"attname=$3 and expirydate=$4";
par = 0;
params[par++] = tv_to_buf(cd, NULL, 0);
params[par++] = bigint_to_buf(old_useratts->userid, NULL, 0);
params[par++] = str_to_buf(old_useratts->attname, NULL, 0);
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0);
PARCHKVAL(par, 4, params);
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Update", rescode, conn);
goto unparam;
}
for (n = 0; n < par; n++)
free(params[n]);
}
par = 0;
params[par++] = bigint_to_buf(useratts->userid, NULL, 0);
params[par++] = str_to_buf(useratts->attname, NULL, 0);
params[par++] = str_to_buf(useratts->status, NULL, 0);
params[par++] = str_to_buf(useratts->attstr, NULL, 0);
params[par++] = str_to_buf(useratts->attstr2, NULL, 0);
params[par++] = bigint_to_buf(useratts->attnum, NULL, 0);
params[par++] = bigint_to_buf(useratts->attnum2, NULL, 0);
params[par++] = tv_to_buf(&(useratts->attdate), NULL, 0);
params[par++] = tv_to_buf(&(useratts->attdate2), NULL, 0);
HISTORYDATEPARAMS(params, par, useratts);
PARCHK(par, params);
ins = "insert into useratts "
"(userid,attname,status,attstr,attstr2,attnum,attnum2,"
"attdate,attdate2"
HISTORYDATECONTROL ") values (" PQPARAM14 ")";
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!begun) {
PQclear(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
res = PQexec(conn, "Rollback", CKPQ_WRITE);
goto unparam;
}
res = PQexec(conn, "Commit", CKPQ_WRITE);
ok = true;
} else {
if (PGOK(rescode))
ok = true;
}
unparam:
PQclear(res);
if (conned)
PQfinish(conn);
for (n = 0; n < par; n++)
free(params[n]);
K_WLOCK(useratts_free);
if (ok) {
if (old_item) {
useratts_root = remove_from_ktree(useratts_root, old_item, cmp_useratts, ctx);
copy_tv(&(old_useratts->expirydate), cd);
useratts_root = add_to_ktree(useratts_root, old_item, cmp_useratts);
}
useratts_root = add_to_ktree(useratts_root, ua_item, cmp_useratts);
k_add_head(useratts_store, ua_item);
}
K_WUNLOCK(useratts_free);
return ok;
}
static __maybe_unused K_ITEM *useratts_add(PGconn *conn, char *username, char *attname,
char *status, char *attstr, char *attstr2,
char *attnum, char *attnum2, char *attdate,
char *attdate2, char *by, char *code,
char *inet, tv_t *cd, K_TREE *trf_root,
bool begun)
{
K_ITEM *item, *u_item;
USERATTS *row;
USERS *users;
bool ok = false;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(useratts_free);
item = k_unlink_head(useratts_free);
K_WUNLOCK(useratts_free);
DATA_USERATTS(row, item);
K_RLOCK(users_free);
u_item = find_users(username);
K_RUNLOCK(users_free);
if (!u_item)
goto unitem;
DATA_USERS(users, u_item);
row->userid = users->userid;
STRNCPY(row->attname, attname);
if (status == NULL)
row->status[0] = '\0';
else
STRNCPY(row->status, status);
if (attstr == NULL)
row->attstr[0] = '\0';
else
STRNCPY(row->attstr, attstr);
if (attstr2 == NULL)
row->attstr2[0] = '\0';
else
STRNCPY(row->attstr2, attstr2);
if (attnum == NULL || attnum[0] == '\0')
row->attnum = 0;
else
TXT_TO_BIGINT("attnum", attnum, row->attnum);
if (attnum2 == NULL || attnum2[0] == '\0')
row->attnum2 = 0;
else
TXT_TO_BIGINT("attnum2", attnum2, row->attnum2);
if (attdate == NULL || attdate[0] == '\0')
row->attdate.tv_sec = row->attdate.tv_usec = 0L;
else
TXT_TO_TV("attdate", attdate, row->attdate);
if (attdate2 == NULL || attdate2[0] == '\0')
row->attdate2.tv_sec = row->attdate2.tv_usec = 0L;
else
TXT_TO_TV("attdate2", attdate2, row->attdate2);
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);
ok = useratts_item_add(conn, item, cd, begun);
unitem:
K_WLOCK(useratts_free);
if (!ok)
k_add_head(useratts_free, item);
K_WUNLOCK(useratts_free);
if (ok)
return item;
else
return NULL;
}
static bool useratts_fill(PGconn *conn)
{
ExecStatusType rescode;
PGresult *res;
K_ITEM *item;
int n, i;
USERATTS *row;
char *field;
char *sel;
int fields = 9;
bool ok;
LOGDEBUG("%s(): select", __func__);
sel = "select "
"userid,attname,status,attstr,attstr2,attnum,attnum2"
",attdate,attdate2"
HISTORYDATECONTROL
" from useratts";
res = PQexec(conn, sel, CKPQ_READ);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Select", rescode, conn);
PQclear(res);
return false;
}
n = PQnfields(res);
if (n != (fields + HISTORYDATECOUNT)) {
LOGERR("%s(): Invalid field count - should be %d, but is %d",
__func__, fields + HISTORYDATECOUNT, n);
PQclear(res);
return false;
}
n = PQntuples(res);
LOGDEBUG("%s(): tree build count %d", __func__, n);
ok = true;
K_WLOCK(useratts_free);
for (i = 0; i < n; i++) {
item = k_unlink_head(useratts_free);
DATA_USERATTS(row, item);
if (everyone_die) {
ok = false;
break;
}
PQ_GET_FLD(res, i, "userid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("userid", field, row->userid);
PQ_GET_FLD(res, i, "attname", field, ok);
if (!ok)
break;
TXT_TO_STR("attname", field, row->attname);
PQ_GET_FLD(res, i, "status", field, ok);
if (!ok)
break;
TXT_TO_STR("status", field, row->status);
PQ_GET_FLD(res, i, "attstr", field, ok);
if (!ok)
break;
TXT_TO_STR("attstr", field, row->attstr);
PQ_GET_FLD(res, i, "attstr2", field, ok);
if (!ok)
break;
TXT_TO_STR("attstr2", field, row->attstr2);
PQ_GET_FLD(res, i, "attnum", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("attnum", field, row->attnum);
PQ_GET_FLD(res, i, "attnum2", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("attnum2", field, row->attnum2);
PQ_GET_FLD(res, i, "attdate", field, ok);
if (!ok)
break;
TXT_TO_TV("attdate", field, row->attdate);
PQ_GET_FLD(res, i, "attdate2", field, ok);
if (!ok)
break;
TXT_TO_TV("attdate2", field, row->attdate2);
HISTORYDATEFLDS(res, i, row, ok);
if (!ok)
break;
useratts_root = add_to_ktree(useratts_root, item, cmp_useratts);
k_add_head(useratts_store, item);
}
if (!ok)
k_add_head(useratts_free, item);
K_WUNLOCK(useratts_free);
PQclear(res);
if (ok) {
LOGDEBUG("%s(): built", __func__);
LOGWARNING("%s(): loaded %d useratts records", __func__, n);
}
return ok;
}
void useratts_reload()
{
PGconn *conn = dbconnect();
K_WLOCK(useratts_free);
useratts_root = free_ktree(useratts_root, NULL);
k_list_transfer_to_head(useratts_store, useratts_free);
K_WUNLOCK(useratts_free);
useratts_fill(conn);
PQfinish(conn);
}
// order by userid asc,workername asc,expirydate desc
static cmp_t cmp_workers(K_ITEM *a, K_ITEM *b)
{
WORKERS *wa, *wb;
DATA_WORKERS(wa, a);
DATA_WORKERS(wb, b);
cmp_t c = CMP_BIGINT(wa->userid, wb->userid);
if (c == 0) {
c = CMP_STR(wa->workername, wb->workername);
if (c == 0)
c = CMP_TV(wb->expirydate, wa->expirydate);
}
return c;
}
static K_ITEM *find_workers(int64_t userid, char *workername)
{
WORKERS workers;
K_TREE_CTX ctx[1];
K_ITEM look;
workers.userid = userid;
STRNCPY(workers.workername, workername);
workers.expirydate.tv_sec = default_expiry.tv_sec;
workers.expirydate.tv_usec = default_expiry.tv_usec;
INIT_WORKERS(&look);
look.data = (void *)(&workers);
return find_in_ktree(workers_root, &look, cmp_workers, ctx);
}
static K_ITEM *workers_add(PGconn *conn, int64_t userid, char *workername,
char *difficultydefault, char *idlenotificationenabled,
char *idlenotificationtime, char *by,
char *code, char *inet, tv_t *cd, K_TREE *trf_root)
{
ExecStatusType rescode;
bool conned = false;
PGresult *res;
K_ITEM *item, *ret = NULL;
int n;
WORKERS *row;
char *ins;
char *params[6 + HISTORYDATECOUNT];
int par;
int32_t diffdef;
int32_t nottime;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(workers_free);
item = k_unlink_head(workers_free);
K_WUNLOCK(workers_free);
DATA_WORKERS(row, item);
if (conn == NULL) {
conn = dbconnect();
conned = true;
}
row->workerid = nextid(conn, "workerid", (int64_t)1, cd, by, code, inet);
if (row->workerid == 0)
goto unitem;
row->userid = userid;
STRNCPY(row->workername, workername);
if (difficultydefault && *difficultydefault) {
diffdef = atoi(difficultydefault);
if (diffdef < DIFFICULTYDEFAULT_MIN)
diffdef = DIFFICULTYDEFAULT_MIN;
if (diffdef > DIFFICULTYDEFAULT_MAX)
diffdef = DIFFICULTYDEFAULT_MAX;
row->difficultydefault = diffdef;
} else
row->difficultydefault = DIFFICULTYDEFAULT_DEF;
row->idlenotificationenabled[1] = '\0';
if (idlenotificationenabled && *idlenotificationenabled) {
if (tolower(*idlenotificationenabled) == IDLENOTIFICATIONENABLED[0])
row->idlenotificationenabled[0] = IDLENOTIFICATIONENABLED[0];
else
row->idlenotificationenabled[0] = IDLENOTIFICATIONDISABLED[0];
} else
row->idlenotificationenabled[0] = IDLENOTIFICATIONENABLED_DEF[0];
if (idlenotificationtime && *idlenotificationtime) {
nottime = atoi(idlenotificationtime);
if (nottime < DIFFICULTYDEFAULT_MIN) {
row->idlenotificationenabled[0] = IDLENOTIFICATIONDISABLED[0];
nottime = DIFFICULTYDEFAULT_MIN;
} else if (nottime > IDLENOTIFICATIONTIME_MAX)
nottime = row->idlenotificationtime;
row->idlenotificationtime = nottime;
} else
row->idlenotificationtime = IDLENOTIFICATIONTIME_DEF;
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);
par = 0;
params[par++] = bigint_to_buf(row->workerid, NULL, 0);
params[par++] = bigint_to_buf(row->userid, NULL, 0);
params[par++] = str_to_buf(row->workername, NULL, 0);
params[par++] = int_to_buf(row->difficultydefault, NULL, 0);
params[par++] = str_to_buf(row->idlenotificationenabled, NULL, 0);
params[par++] = int_to_buf(row->idlenotificationtime, NULL, 0);
HISTORYDATEPARAMS(params, par, row);
PARCHK(par, params);
ins = "insert into workers "
"(workerid,userid,workername,difficultydefault,"
"idlenotificationenabled,idlenotificationtime"
HISTORYDATECONTROL ") values (" PQPARAM11 ")";
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
goto unparam;
}
ret = item;
unparam:
PQclear(res);
for (n = 0; n < par; n++)
free(params[n]);
unitem:
if (conned)
PQfinish(conn);
K_WLOCK(workers_free);
if (!ret)
k_add_head(workers_free, item);
else {
workers_root = add_to_ktree(workers_root, item, cmp_workers);
k_add_head(workers_store, item);
// Ensure there is a matching workerstatus
find_create_workerstatus(userid, workername,
__FILE__, __func__, __LINE__);
}
K_WUNLOCK(workers_free);
return ret;
}
static bool workers_update(PGconn *conn, K_ITEM *item, char *difficultydefault,
char *idlenotificationenabled, char *idlenotificationtime,
char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root)
{
ExecStatusType rescode;
bool conned = false;
PGresult *res;
int n;
WORKERS *row;
char *upd, *ins;
bool ok = false;
char *params[6 + HISTORYDATECOUNT];
int par;
int32_t diffdef;
char idlenot;
int32_t nottime;
LOGDEBUG("%s(): update", __func__);
DATA_WORKERS(row, item);
if (difficultydefault && *difficultydefault) {
diffdef = atoi(difficultydefault);
if (diffdef < DIFFICULTYDEFAULT_MIN)
diffdef = row->difficultydefault;
if (diffdef > DIFFICULTYDEFAULT_MAX)
diffdef = row->difficultydefault;
} else
diffdef = row->difficultydefault;
if (idlenotificationenabled && *idlenotificationenabled) {
if (tolower(*idlenotificationenabled) == IDLENOTIFICATIONENABLED[0])
idlenot = IDLENOTIFICATIONENABLED[0];
else
idlenot = IDLENOTIFICATIONDISABLED[0];
} else
idlenot = row->idlenotificationenabled[0];
if (idlenotificationtime && *idlenotificationtime) {
nottime = atoi(idlenotificationtime);
if (nottime < IDLENOTIFICATIONTIME_MIN)
nottime = row->idlenotificationtime;
if (nottime > IDLENOTIFICATIONTIME_MAX)
nottime = row->idlenotificationtime;
} else
nottime = row->idlenotificationtime;
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);
if (diffdef == row->difficultydefault &&
idlenot == row->idlenotificationenabled[0] &&
nottime == row->idlenotificationtime) {
ok = true;
goto early;
} else {
upd = "update workers set expirydate=$1 where workerid=$2 and expirydate=$3";
par = 0;
params[par++] = tv_to_buf(cd, NULL, 0);
params[par++] = bigint_to_buf(row->workerid, NULL, 0);
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0);
PARCHKVAL(par, 3, params);
if (conn == NULL) {
conn = dbconnect();
conned = true;
}
res = PQexec(conn, "Begin", CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Begin", rescode, conn);
goto unparam;
}
PQclear(res);
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
PQclear(res);
if (!PGOK(rescode)) {
PGLOGERR("Update", rescode, conn);
res = PQexec(conn, "Rollback", CKPQ_WRITE);
goto unparam;
}
for (n = 0; n < par; n++)
free(params[n]);
ins = "insert into workers "
"(workerid,userid,workername,difficultydefault,"
"idlenotificationenabled,idlenotificationtime"
HISTORYDATECONTROL ") values (" PQPARAM11 ")";
row->difficultydefault = diffdef;
row->idlenotificationenabled[0] = idlenot;
row->idlenotificationenabled[1] = '\0';
row->idlenotificationtime = nottime;
par = 0;
params[par++] = bigint_to_buf(row->workerid, NULL, 0);
params[par++] = bigint_to_buf(row->userid, NULL, 0);
params[par++] = str_to_buf(row->workername, NULL, 0);
params[par++] = int_to_buf(row->difficultydefault, NULL, 0);
params[par++] = str_to_buf(row->idlenotificationenabled, NULL, 0);
params[par++] = int_to_buf(row->idlenotificationtime, NULL, 0);
HISTORYDATEPARAMS(params, par, row);
PARCHK(par, params);
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
PQclear(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
res = PQexec(conn, "Rollback", CKPQ_WRITE);
goto unparam;
}
res = PQexec(conn, "Commit", CKPQ_WRITE);
}
ok = true;
unparam:
PQclear(res);
if (conned)
PQfinish(conn);
for (n = 0; n < par; n++)
free(params[n]);
early:
return ok;
}
static K_ITEM *new_worker(PGconn *conn, bool update, int64_t userid, char *workername,
char *diffdef, char *idlenotificationenabled,
char *idlenotificationtime, char *by,
char *code, char *inet, tv_t *cd, K_TREE *trf_root)
{
K_ITEM *item;
item = find_workers(userid, workername);
if (item) {
if (!confirm_sharesummary && update) {
workers_update(conn, item, diffdef, idlenotificationenabled,
idlenotificationtime, by, code, inet, cd,
trf_root);
}
} else {
if (confirm_sharesummary) {
// Shouldn't be possible since the sharesummary is already aged
LOGERR("%s() %"PRId64"/%s workername not found during confirm",
__func__, userid, workername);
return NULL;
}
// TODO: limit how many?
item = workers_add(conn, userid, workername, diffdef,
idlenotificationenabled, idlenotificationtime,
by, code, inet, cd, trf_root);
}
return item;
}
static K_ITEM *new_default_worker(PGconn *conn, bool update, int64_t userid, char *workername,
char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root)
{
return new_worker(conn, update, userid, workername, DIFFICULTYDEFAULT_DEF_STR,
IDLENOTIFICATIONENABLED_DEF, IDLENOTIFICATIONTIME_DEF_STR,
by, code, inet, cd, trf_root);
}
/* unused
static K_ITEM *new_worker_find_user(PGconn *conn, bool update, char *username,
char *workername, char *diffdef,
char *idlenotificationenabled,
char *idlenotificationtime,
char *by, char *code, char *inet,
tv_t *cd, K_TREE *trf_root)
{
K_ITEM *item;
USERS *users;
K_RLOCK(users_free);
item = find_users(username);
K_RUNLOCK(users_free);
if (!item)
return NULL;
DATA_USERS(users, item);
return new_worker(conn, update, users->userid, workername, diffdef,
idlenotificationenabled, idlenotificationtime,
by, code, inet, cd, trf_root);
}
*/
static bool workers_fill(PGconn *conn)
{
ExecStatusType rescode;
PGresult *res;
K_ITEM *item;
int n, i;
WORKERS *row;
char *field;
char *sel;
int fields = 6;
bool ok;
LOGDEBUG("%s(): select", __func__);
sel = "select "
"userid,workername,difficultydefault,"
"idlenotificationenabled,idlenotificationtime"
HISTORYDATECONTROL
",workerid from workers";
res = PQexec(conn, sel, CKPQ_READ);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Select", rescode, conn);
PQclear(res);
return false;
}
n = PQnfields(res);
if (n != (fields + HISTORYDATECOUNT)) {
LOGERR("%s(): Invalid field count - should be %d, but is %d",
__func__, fields + HISTORYDATECOUNT, n);
PQclear(res);
return false;
}
n = PQntuples(res);
LOGDEBUG("%s(): tree build count %d", __func__, n);
ok = true;
K_WLOCK(workers_free);
for (i = 0; i < n; i++) {
item = k_unlink_head(workers_free);
DATA_WORKERS(row, item);
if (everyone_die) {
ok = false;
break;
}
PQ_GET_FLD(res, i, "userid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("userid", field, row->userid);
PQ_GET_FLD(res, i, "workername", field, ok);
if (!ok)
break;
TXT_TO_STR("workername", field, row->workername);
PQ_GET_FLD(res, i, "difficultydefault", field, ok);
if (!ok)
break;
TXT_TO_INT("difficultydefault", field, row->difficultydefault);
PQ_GET_FLD(res, i, "idlenotificationenabled", field, ok);
if (!ok)
break;
TXT_TO_STR("idlenotificationenabled", field, row->idlenotificationenabled);
PQ_GET_FLD(res, i, "idlenotificationtime", field, ok);
if (!ok)
break;
TXT_TO_INT("idlenotificationtime", field, row->idlenotificationtime);
HISTORYDATEFLDS(res, i, row, ok);
if (!ok)
break;
PQ_GET_FLD(res, i, "workerid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("workerid", field, row->workerid);
workers_root = add_to_ktree(workers_root, item, cmp_workers);
k_add_head(workers_store, item);
/* Make sure a workerstatus exists for each worker
* This is to ensure that code can use the workerstatus tree
* to reference other tables and not miss workers in the
* other tables */
find_create_workerstatus(row->userid, row->workername,
__FILE__, __func__, __LINE__);
}
if (!ok)
k_add_head(workers_free, item);
K_WUNLOCK(workers_free);
PQclear(res);
if (ok) {
LOGDEBUG("%s(): built", __func__);
LOGWARNING("%s(): loaded %d workers records", __func__, n);
}
return ok;
}
void workers_reload()
{
PGconn *conn = dbconnect();
K_WLOCK(workers_free);
workers_root = free_ktree(workers_root, NULL);
k_list_transfer_to_head(workers_store, workers_free);
K_WUNLOCK(workers_free);
workers_fill(conn);
PQfinish(conn);
}
// order by userid asc,expirydate desc,payaddress asc
static cmp_t cmp_paymentaddresses(K_ITEM *a, K_ITEM *b)
{
PAYMENTADDRESSES *pa, *pb;
DATA_PAYMENTADDRESSES(pa, a);
DATA_PAYMENTADDRESSES(pb, b);
cmp_t c = CMP_BIGINT(pa->userid, pb->userid);
if (c == 0) {
c = CMP_TV(pb->expirydate, pa->expirydate);
if (c == 0)
c = CMP_STR(pa->payaddress, pb->payaddress);
}
return c;
}
static K_ITEM *find_paymentaddresses(int64_t userid)
{
PAYMENTADDRESSES paymentaddresses, *pa;
K_TREE_CTX ctx[1];
K_ITEM look, *item;
paymentaddresses.userid = userid;
paymentaddresses.payaddress[0] = '\0';
paymentaddresses.expirydate.tv_sec = DATE_S_EOT;
INIT_PAYMENTADDRESSES(&look);
look.data = (void *)(&paymentaddresses);
item = find_after_in_ktree(paymentaddresses_root, &look, cmp_paymentaddresses, ctx);
if (item) {
DATA_PAYMENTADDRESSES(pa, item);
if (pa->userid == userid && CURRENT(&(pa->expirydate)))
return item;
else
return NULL;
} else
return NULL;
}
// Whatever the current paymentaddresses are, replace them with this one
static K_ITEM *paymentaddresses_set(PGconn *conn, int64_t userid, char *payaddress,
char *by, char *code, char *inet, tv_t *cd,
K_TREE *trf_root)
{
ExecStatusType rescode;
bool conned = false;
PGresult *res;
K_TREE_CTX ctx[1], ctx2[1];
K_ITEM *item, *old, *this, look;
PAYMENTADDRESSES *row, pa, *thispa;
char *upd, *ins;
bool ok = false;
char *params[4 + HISTORYDATECOUNT];
int par;
int n;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(paymentaddresses_free);
item = k_unlink_head(paymentaddresses_free);
K_WUNLOCK(paymentaddresses_free);
DATA_PAYMENTADDRESSES(row, item);
row->paymentaddressid = nextid(conn, "paymentaddressid", 1,
cd, by, code, inet);
if (row->paymentaddressid == 0)
goto unitem;
row->userid = userid;
STRNCPY(row->payaddress, payaddress);
row->payratio = 1000000;
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);
upd = "update paymentaddresses set expirydate=$1 where userid=$2 and expirydate=$3";
par = 0;
params[par++] = tv_to_buf(cd, NULL, 0);
params[par++] = bigint_to_buf(row->userid, NULL, 0);
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0);
PARCHKVAL(par, 3, params);
if (conn == NULL) {
conn = dbconnect();
conned = true;
}
res = PQexec(conn, "Begin", CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Begin", rescode, conn);
goto unparam;
}
PQclear(res);
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
PQclear(res);
if (!PGOK(rescode)) {
PGLOGERR("Update", rescode, conn);
res = PQexec(conn, "Rollback", CKPQ_WRITE);
goto unparam;
}
for (n = 0; n < par; n++)
free(params[n]);
ins = "insert into paymentaddresses "
"(paymentaddressid,userid,payaddress,payratio"
HISTORYDATECONTROL ") values (" PQPARAM9 ")";
par = 0;
params[par++] = bigint_to_buf(row->paymentaddressid, NULL, 0);
params[par++] = bigint_to_buf(row->userid, NULL, 0);
params[par++] = str_to_buf(row->payaddress, NULL, 0);
params[par++] = int_to_buf(row->payratio, NULL, 0);
HISTORYDATEPARAMS(params, par, row);
PARCHK(par, params);
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
goto unparam;
}
res = PQexec(conn, "Commit", CKPQ_WRITE);
ok = true;
unparam:
PQclear(res);
if (conned)
PQfinish(conn);
for (n = 0; n < par; n++)
free(params[n]);
unitem:
K_WLOCK(paymentaddresses_free);
if (!ok)
k_add_head(paymentaddresses_free, item);
else {
// Remove from ram, old (unneeded) records
pa.userid = userid;
pa.expirydate.tv_sec = 0L;
pa.payaddress[0] = '\0';
INIT_PAYMENTADDRESSES(&look);
look.data = (void *)(&pa);
old = find_after_in_ktree(paymentaddresses_root, &look,
cmp_paymentaddresses, ctx);
while (old) {
this = old;
DATA_PAYMENTADDRESSES(thispa, this);
if (thispa->userid != userid)
break;
old = next_in_ktree(ctx);
paymentaddresses_root = remove_from_ktree(paymentaddresses_root, this,
cmp_paymentaddresses, ctx2);
k_add_head(paymentaddresses_free, this);
}
paymentaddresses_root = add_to_ktree(paymentaddresses_root, item,
cmp_paymentaddresses);
k_add_head(paymentaddresses_store, item);
}
K_WUNLOCK(paymentaddresses_free);
if (ok)
return item;
else
return NULL;
}
static bool paymentaddresses_fill(PGconn *conn)
{
ExecStatusType rescode;
PGresult *res;
K_ITEM *item;
int n, i;
PAYMENTADDRESSES *row;
char *params[1];
int par;
char *field;
char *sel;
int fields = 4;
bool ok;
LOGDEBUG("%s(): select", __func__);
sel = "select "
"paymentaddressid,userid,payaddress,payratio"
HISTORYDATECONTROL
" from paymentaddresses where expirydate=$1";
par = 0;
params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0);
PARCHK(par, params);
res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_READ);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Select", rescode, conn);
PQclear(res);
return false;
}
n = PQnfields(res);
if (n != (fields + HISTORYDATECOUNT)) {
LOGERR("%s(): Invalid field count - should be %d, but is %d",
__func__, fields + HISTORYDATECOUNT, n);
PQclear(res);
return false;
}
n = PQntuples(res);
LOGDEBUG("%s(): tree build count %d", __func__, n);
ok = true;
K_WLOCK(paymentaddresses_free);
for (i = 0; i < n; i++) {
item = k_unlink_head(paymentaddresses_free);
DATA_PAYMENTADDRESSES(row, item);
if (everyone_die) {
ok = false;
break;
}
PQ_GET_FLD(res, i, "paymentaddressid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("paymentaddressid", field, row->paymentaddressid);
PQ_GET_FLD(res, i, "userid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("userid", field, row->userid);
PQ_GET_FLD(res, i, "payaddress", field, ok);
if (!ok)
break;
TXT_TO_STR("payaddress", field, row->payaddress);
PQ_GET_FLD(res, i, "payratio", field, ok);
if (!ok)
break;
TXT_TO_INT("payratio", field, row->payratio);
HISTORYDATEFLDS(res, i, row, ok);
if (!ok)
break;
paymentaddresses_root = add_to_ktree(paymentaddresses_root, item, cmp_paymentaddresses);
k_add_head(paymentaddresses_store, item);
}
if (!ok)
k_add_head(paymentaddresses_free, item);
K_WUNLOCK(paymentaddresses_free);
PQclear(res);
if (ok) {
LOGDEBUG("%s(): built", __func__);
LOGWARNING("%s(): loaded %d paymentaddresses records", __func__, n);
}
return ok;
}
// order by userid asc,paydate asc,payaddress asc,expirydate desc
static cmp_t cmp_payments(K_ITEM *a, K_ITEM *b)
{
PAYMENTS *pa, *pb;
DATA_PAYMENTS(pa, a);
DATA_PAYMENTS(pb, b);
cmp_t c = CMP_BIGINT(pa->userid, pb->userid);
if (c == 0) {
c = CMP_TV(pa->paydate, pb->paydate);
if (c == 0) {
c = CMP_STR(pa->payaddress, pb->payaddress);
if (c == 0)
c = CMP_TV(pb->expirydate, pa->expirydate);
}
}
return c;
}
static bool payments_fill(PGconn *conn)
{
ExecStatusType rescode;
PGresult *res;
K_ITEM *item;
int n, i;
PAYMENTS *row;
char *params[1];
int par;
char *field;
char *sel;
int fields = 8;
bool ok;
LOGDEBUG("%s(): select", __func__);
// TODO: handle selecting a subset, eg 20 per web page (in blocklist also)
sel = "select "
"userid,paydate,payaddress,originaltxn,amount,committxn,commitblockhash"
HISTORYDATECONTROL
",paymentid from payments where expirydate=$1";
par = 0;
params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0);
PARCHK(par, params);
res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_READ);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Select", rescode, conn);
PQclear(res);
return false;
}
n = PQnfields(res);
if (n != (fields + HISTORYDATECOUNT)) {
LOGERR("%s(): Invalid field count - should be %d, but is %d",
__func__, fields + HISTORYDATECOUNT, n);
PQclear(res);
return false;
}
n = PQntuples(res);
LOGDEBUG("%s(): tree build count %d", __func__, n);
ok = true;
K_WLOCK(payments_free);
for (i = 0; i < n; i++) {
item = k_unlink_head(payments_free);
DATA_PAYMENTS(row, item);
if (everyone_die) {
ok = false;
break;
}
PQ_GET_FLD(res, i, "userid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("userid", field, row->userid);
PQ_GET_FLD(res, i, "paydate", field, ok);
if (!ok)
break;
TXT_TO_TV("paydate", field, row->paydate);
PQ_GET_FLD(res, i, "payaddress", field, ok);
if (!ok)
break;
TXT_TO_STR("payaddress", field, row->payaddress);
PQ_GET_FLD(res, i, "originaltxn", field, ok);
if (!ok)
break;
TXT_TO_STR("originaltxn", field, row->originaltxn);
PQ_GET_FLD(res, i, "amount", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("amount", field, row->amount);
PQ_GET_FLD(res, i, "committxn", field, ok);
if (!ok)
break;
TXT_TO_STR("committxn", field, row->committxn);
PQ_GET_FLD(res, i, "commitblockhash", field, ok);
if (!ok)
break;
TXT_TO_STR("commitblockhash", field, row->commitblockhash);
HISTORYDATEFLDS(res, i, row, ok);
if (!ok)
break;
PQ_GET_FLD(res, i, "paymentid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("paymentid", field, row->paymentid);
payments_root = add_to_ktree(payments_root, item, cmp_payments);
k_add_head(payments_store, item);
}
if (!ok)
k_add_head(payments_free, item);
K_WUNLOCK(payments_free);
PQclear(res);
if (ok) {
LOGDEBUG("%s(): built", __func__);
LOGWARNING("%s(): loaded %d payments records", __func__, n);
}
return ok;
}
void payments_reload()
{
PGconn *conn = dbconnect();
K_WLOCK(payments_free);
payments_root = free_ktree(payments_root, NULL);
k_list_transfer_to_head(payments_store, payments_free);
K_WUNLOCK(payments_free);
payments_fill(conn);
PQfinish(conn);
}
// order by workinfoid asc,expirydate asc
static cmp_t cmp_workinfo(K_ITEM *a, K_ITEM *b)
{
WORKINFO *wa, *wb;
DATA_WORKINFO(wa, a);
DATA_WORKINFO(wb, b);
cmp_t c = CMP_BIGINT(wa->workinfoid, wb->workinfoid);
if (c == 0)
c = CMP_TV(wa->expirydate, wb->expirydate);
return c;
}
inline int32_t _coinbase1height(char *coinbase1, WHERE_FFL_ARGS)
{
int32_t height = 0;
uchar *cb1;
int siz;
cb1 = ((uchar *)coinbase1) + 84;
siz = ((hex2bin_tbl[*cb1]) << 4) + (hex2bin_tbl[*(cb1+1)]);
// limit to 4 for int32_t and since ... that should last a while :)
if (siz < 1 || siz > 4) {
LOGERR("%s(): Invalid coinbase1 block height size (%d)"
" require: 1..4 (cb1 %s)" WHERE_FFL,
__func__, siz, coinbase1, WHERE_FFL_PASS);
return height;
}
siz *= 2;
while (siz-- > 0) {
height <<= 4;
height += (int32_t)hex2bin_tbl[*(cb1+(siz^1)+2)];
}
return height;
}
static cmp_t _cmp_height(char *coinbase1a, char *coinbase1b, WHERE_FFL_ARGS)
{
return CMP_INT(_coinbase1height(coinbase1a, WHERE_FFL_PASS),
_coinbase1height(coinbase1b, WHERE_FFL_PASS));
}
// order by height asc,createdate asc
static cmp_t cmp_workinfo_height(K_ITEM *a, K_ITEM *b)
{
WORKINFO *wa, *wb;
DATA_WORKINFO(wa, a);
DATA_WORKINFO(wb, b);
cmp_t c = cmp_height(wa->coinbase1, wb->coinbase1);
if (c == 0)
c = CMP_TV(wa->createdate, wb->createdate);
return c;
}
static K_ITEM *find_workinfo(int64_t workinfoid)
{
WORKINFO workinfo;
K_TREE_CTX ctx[1];
K_ITEM look, *item;
workinfo.workinfoid = workinfoid;
workinfo.expirydate.tv_sec = default_expiry.tv_sec;
workinfo.expirydate.tv_usec = default_expiry.tv_usec;
INIT_WORKINFO(&look);
look.data = (void *)(&workinfo);
K_RLOCK(workinfo_free);
item = find_in_ktree(workinfo_root, &look, cmp_workinfo, ctx);
K_RUNLOCK(workinfo_free);
return item;
}
static int64_t workinfo_add(PGconn *conn, char *workinfoidstr, char *poolinstance,
char *transactiontree, char *merklehash, char *prevhash,
char *coinbase1, char *coinbase2, char *version,
char *bits, char *ntime, char *reward, char *by,
char *code, char *inet, tv_t *cd, bool igndup,
K_TREE *trf_root)
{
ExecStatusType rescode;
bool conned = false;
K_TREE_CTX ctx[1];
PGresult *res;
K_ITEM *item;
char cd_buf[DATE_BUFSIZ];
char ndiffbin[TXT_SML+1];
int n;
int64_t workinfoid = -1;
WORKINFO *row;
char *ins;
char *params[11 + HISTORYDATECOUNT];
int par;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(workinfo_free);
item = k_unlink_head(workinfo_free);
K_WUNLOCK(workinfo_free);
DATA_WORKINFO(row, item);
TXT_TO_BIGINT("workinfoid", workinfoidstr, row->workinfoid);
STRNCPY(row->poolinstance, poolinstance);
row->transactiontree = strdup(transactiontree);
row->merklehash = strdup(merklehash);
STRNCPY(row->prevhash, prevhash);
STRNCPY(row->coinbase1, coinbase1);
STRNCPY(row->coinbase2, coinbase2);
STRNCPY(row->version, version);
STRNCPY(row->bits, bits);
STRNCPY(row->ntime, ntime);
TXT_TO_BIGINT("reward", reward, row->reward);
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);
K_WLOCK(workinfo_free);
if (find_in_ktree(workinfo_root, item, cmp_workinfo, ctx)) {
free(row->transactiontree);
free(row->merklehash);
workinfoid = row->workinfoid;
k_add_head(workinfo_free, item);
K_WUNLOCK(workinfo_free);
if (!igndup) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s(): Duplicate workinfo ignored %s/%s/%s",
__func__, workinfoidstr, poolinstance, cd_buf);
}
return workinfoid;
}
K_WUNLOCK(workinfo_free);
par = 0;
if (!confirm_sharesummary) {
params[par++] = bigint_to_buf(row->workinfoid, NULL, 0);
params[par++] = str_to_buf(row->poolinstance, NULL, 0);
params[par++] = str_to_buf(row->transactiontree, NULL, 0);
params[par++] = str_to_buf(row->merklehash, NULL, 0);
params[par++] = str_to_buf(row->prevhash, NULL, 0);
params[par++] = str_to_buf(row->coinbase1, NULL, 0);
params[par++] = str_to_buf(row->coinbase2, NULL, 0);
params[par++] = str_to_buf(row->version, NULL, 0);
params[par++] = str_to_buf(row->bits, NULL, 0);
params[par++] = str_to_buf(row->ntime, NULL, 0);
params[par++] = bigint_to_buf(row->reward, NULL, 0);
HISTORYDATEPARAMS(params, par, row);
PARCHK(par, params);
ins = "insert into workinfo "
"(workinfoid,poolinstance,transactiontree,merklehash,"
"prevhash,coinbase1,coinbase2,version,bits,ntime,reward"
HISTORYDATECONTROL ") values (" PQPARAM16 ")";
if (!conn) {
conn = dbconnect();
conned = true;
}
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
goto unparam;
}
}
workinfoid = row->workinfoid;
unparam:
if (par) {
PQclear(res);
if (conned)
PQfinish(conn);
for (n = 0; n < par; n++)
free(params[n]);
}
K_WLOCK(workinfo_free);
if (workinfoid == -1) {
free(row->transactiontree);
free(row->merklehash);
k_add_head(workinfo_free, item);
} else {
if (row->transactiontree && *(row->transactiontree)) {
// Not currently needed in RAM
free(row->transactiontree);
row->transactiontree = strdup(EMPTY);
}
hex2bin(ndiffbin, row->bits, 4);
current_ndiff = diff_from_nbits(ndiffbin);
workinfo_root = add_to_ktree(workinfo_root, item, cmp_workinfo);
k_add_head(workinfo_store, item);
// Remember the bc = 'cd' when the height changes
if (workinfo_current) {
WORKINFO *wic;
DATA_WORKINFO(wic, workinfo_current);
if (cmp_height(wic->coinbase1, row->coinbase1) != 0)
copy_tv(&last_bc, cd);
}
workinfo_current = item;
}
K_WUNLOCK(workinfo_free);
return workinfoid;
}
#define sharesummary_update(_conn, _s_row, _e_row, _ss_item, _by, _code, _inet, _cd) \
_sharesummary_update(_conn, _s_row, _e_row, _ss_item, _by, _code, _inet, _cd, \
WHERE_FFL_HERE)
static bool _sharesummary_update(PGconn *conn, SHARES *s_row, SHAREERRORS *e_row, K_ITEM *ss_item,
char *by, char *code, char *inet, tv_t *cd, WHERE_FFL_ARGS);
static cmp_t cmp_sharesummary_workinfoid(K_ITEM *a, K_ITEM *b);
static cmp_t cmp_shares(K_ITEM *a, K_ITEM *b);
static bool workinfo_age(PGconn *conn, int64_t workinfoid, char *poolinstance,
char *by, char *code, char *inet, tv_t *cd,
tv_t *ss_first, tv_t *ss_last, int64_t *ss_count,
int64_t *s_count, int64_t *s_diff)
{
K_ITEM *wi_item, ss_look, *ss_item, s_look, *s_item, *tmp_item;
K_TREE_CTX ss_ctx[1], s_ctx[1], tmp_ctx[1];
char cd_buf[DATE_BUFSIZ];
int64_t ss_tot, ss_already, ss_failed, shares_tot, shares_dumped;
SHARESUMMARY looksharesummary, *sharesummary;
WORKINFO *workinfo;
SHARES lookshares, *shares;
bool ok = false, conned = false, skipupdate;
char error[1024];
LOGDEBUG("%s(): age", __func__);
ss_first->tv_sec = ss_first->tv_usec =
ss_last->tv_sec = ss_last->tv_usec = 0;
*ss_count = *s_count = *s_diff = 0;
wi_item = find_workinfo(workinfoid);
if (!wi_item) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s() %"PRId64"/%s/%ld,%ld %.19s no workinfo! Age discarded!",
__func__, workinfoid, poolinstance,
cd->tv_sec, cd->tv_usec, cd_buf);
goto bye;
}
DATA_WORKINFO(workinfo, wi_item);
if (strcmp(poolinstance, workinfo->poolinstance) != 0) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s() %"PRId64"/%s/%ld,%ld %.19s Poolinstance changed "
"(from %s)! Age discarded!",
__func__, workinfoid, poolinstance,
cd->tv_sec, cd->tv_usec, cd_buf,
workinfo->poolinstance);
goto bye;
}
INIT_SHARESUMMARY(&ss_look);
INIT_SHARES(&s_look);
// Find the first matching sharesummary
looksharesummary.workinfoid = workinfoid;
looksharesummary.userid = -1;
looksharesummary.workername[0] = '\0';
ok = true;
ss_tot = ss_already = ss_failed = shares_tot = shares_dumped = 0;
ss_look.data = (void *)(&looksharesummary);
K_RLOCK(sharesummary_free);
ss_item = find_after_in_ktree(sharesummary_workinfoid_root, &ss_look, cmp_sharesummary_workinfoid, ss_ctx);
K_RUNLOCK(sharesummary_free);
DATA_SHARESUMMARY_NULL(sharesummary, ss_item);
while (ss_item && sharesummary->workinfoid == workinfoid) {
ss_tot++;
error[0] = '\0';
skipupdate = false;
/* Reloading during a confirm will not have any old data
* so finding an aged sharesummary here is an error */
if (reloading) {
if (sharesummary->complete[0] == SUMMARY_COMPLETE) {
ss_already++;
skipupdate = true;
if (confirm_sharesummary) {
LOGERR("%s(): Duplicate %s found during confirm %"PRId64"/%s/%"PRId64,
__func__, __func__,
sharesummary->userid,
sharesummary->workername,
sharesummary->workinfoid);
}
}
}
if (!skipupdate) {
if (conn == NULL && !confirm_sharesummary) {
conn = dbconnect();
conned = true;
}
if (!sharesummary_update(conn, NULL, NULL, ss_item, by, code, inet, cd)) {
ss_failed++;
LOGERR("%s(): Failed to age share summary %"PRId64"/%s/%"PRId64,
__func__, sharesummary->userid,
sharesummary->workername,
sharesummary->workinfoid);
ok = false;
} else {
(*ss_count)++;
*s_count += sharesummary->sharecount;
*s_diff += sharesummary->diffacc;
if (ss_first->tv_sec == 0 ||
!tv_newer(ss_first, &(sharesummary->firstshare)))
copy_tv(ss_first, &(sharesummary->firstshare));
if (tv_newer(ss_last, &(sharesummary->lastshare)))
copy_tv(ss_last, &(sharesummary->lastshare));
}
}
// Discard the shares either way
lookshares.workinfoid = workinfoid;
lookshares.userid = sharesummary->userid;
strcpy(lookshares.workername, sharesummary->workername);
lookshares.createdate.tv_sec = 0;
lookshares.createdate.tv_usec = 0;
s_look.data = (void *)(&lookshares);
K_WLOCK(shares_free);
s_item = find_after_in_ktree(shares_root, &s_look, cmp_shares, s_ctx);
while (s_item) {
DATA_SHARES(shares, s_item);
if (shares->workinfoid != workinfoid ||
shares->userid != lookshares.userid ||
strcmp(shares->workername, lookshares.workername) != 0)
break;
shares_tot++;
tmp_item = next_in_ktree(s_ctx);
shares_root = remove_from_ktree(shares_root, s_item, cmp_shares, tmp_ctx);
k_unlink_item(shares_store, s_item);
if (reloading && skipupdate)
shares_dumped++;
if (reloading && skipupdate && !error[0]) {
snprintf(error, sizeof(error),
"reload found aged shares: %"PRId64"/%"PRId64"/%s",
shares->workinfoid,
shares->userid,
shares->workername);
}
k_add_head(shares_free, s_item);
s_item = tmp_item;
}
K_WUNLOCK(shares_free);
K_RLOCK(sharesummary_free);
ss_item = next_in_ktree(ss_ctx);
K_RUNLOCK(sharesummary_free);
DATA_SHARESUMMARY_NULL(sharesummary, ss_item);
if (error[0])
LOGERR("%s(): %s", __func__, error);
}
if (conned)
PQfinish(conn);
if (ss_already || ss_failed || shares_dumped) {
/* If all were already aged, and no shares
* then we don't want a message */
if (!(ss_already == ss_tot && shares_tot == 0)) {
LOGERR("%s(): Summary aging of %"PRId64"/%s sstotal=%"PRId64
" already=%"PRId64" failed=%"PRId64
", sharestotal=%"PRId64" dumped=%"PRId64,
__func__, workinfoid, poolinstance, ss_tot,
ss_already, ss_failed, shares_tot,
shares_dumped);
}
}
bye:
return ok;
}
static void auto_age_older(PGconn *conn, int64_t workinfoid, char *poolinstance,
char *by, char *code, char *inet, tv_t *cd)
{
static int64_t last_attempted_id = -1;
static int64_t prev_found = 0;
static int repeat;
char min_buf[DATE_BUFSIZ], max_buf[DATE_BUFSIZ];
int64_t ss_count_tot, s_count_tot, s_diff_tot;
int64_t ss_count, s_count, s_diff;
tv_t ss_first_min, ss_last_max;
tv_t ss_first, ss_last;
int32_t wid_count;
SHARESUMMARY looksharesummary, *sharesummary;
K_TREE_CTX ctx[1];
K_ITEM look, *ss_item;
int64_t age_id, do_id, to_id;
bool ok, found;
LOGDEBUG("%s(): workinfoid=%"PRId64" prev=%"PRId64, __func__, workinfoid, prev_found);
age_id = prev_found;
// Find the oldest 'unaged' sharesummary < workinfoid and >= prev_found
looksharesummary.workinfoid = prev_found;
looksharesummary.userid = -1;
looksharesummary.workername[0] = '\0';
INIT_SHARESUMMARY(&look);
look.data = (void *)(&looksharesummary);
K_RLOCK(sharesummary_free);
ss_item = find_after_in_ktree(sharesummary_workinfoid_root, &look,
cmp_sharesummary_workinfoid, ctx);
DATA_SHARESUMMARY_NULL(sharesummary, ss_item);
ss_first_min.tv_sec = ss_first_min.tv_usec =
ss_last_max.tv_sec = ss_last_max.tv_usec = 0;
ss_count_tot = s_count_tot = s_diff_tot = 0;
found = false;
while (ss_item && sharesummary->workinfoid < workinfoid) {
if (sharesummary->complete[0] == SUMMARY_NEW) {
age_id = sharesummary->workinfoid;
prev_found = age_id;
found = true;
break;
}
ss_item = next_in_ktree(ctx);
DATA_SHARESUMMARY_NULL(sharesummary, ss_item);
}
K_RUNLOCK(sharesummary_free);
LOGDEBUG("%s(): age_id=%"PRId64" found=%d", __func__, age_id, found);
// Don't repeat searching old items to avoid accessing their ram
if (!found)
prev_found = workinfoid;
else {
/* Process all the consecutive sharesummaries that's aren't aged
* This way we find each oldest 'batch' of sharesummaries that have
* been missed and can report the range of data that was aged,
* which would normally just be an approx 10min set of workinfoids
* from the last time ckpool stopped
* Each next group of unaged sharesummaries following this, will be
* picked up by each next aging */
wid_count = 0;
do_id = age_id;
to_id = 0;
do {
ok = workinfo_age(conn, do_id, poolinstance,
by, code, inet, cd,
&ss_first, &ss_last,
&ss_count, &s_count, &s_diff);
ss_count_tot += ss_count;
s_count_tot += s_count;
s_diff_tot += s_diff;
if (ss_first_min.tv_sec == 0 || !tv_newer(&ss_first_min, &ss_first))
copy_tv(&ss_first_min, &ss_first);
if (tv_newer(&ss_last_max, &ss_last))
copy_tv(&ss_last_max, &ss_last);
if (!ok)
break;
to_id = do_id;
wid_count++;
K_RLOCK(sharesummary_free);
while (ss_item && sharesummary->workinfoid == to_id) {
ss_item = next_in_ktree(ctx);
DATA_SHARESUMMARY_NULL(sharesummary, ss_item);
}
K_RUNLOCK(sharesummary_free);
if (ss_item) {
do_id = sharesummary->workinfoid;
if (do_id >= workinfoid)
break;
if (sharesummary->complete[0] != SUMMARY_NEW)
break;
}
} while (ss_item);
if (to_id == 0) {
if (last_attempted_id != age_id || ++repeat >= 10) {
// Approx once every 5min since workinfo defaults to ~30s
LOGWARNING("%s() Auto-age failed to age %"PRId64,
__func__, age_id);
last_attempted_id = age_id;
repeat = 0;
}
} else {
char idrange[64];
char sharerange[256];
if (to_id != age_id) {
snprintf(idrange, sizeof(idrange),
"from %"PRId64" to %"PRId64,
age_id, to_id);
} else {
snprintf(idrange, sizeof(idrange),
"%"PRId64, age_id);
}
tv_to_buf(&ss_first_min, min_buf, sizeof(min_buf));
if (tv_equal(&ss_first_min, &ss_last_max)) {
snprintf(sharerange, sizeof(sharerange),
"share date %s", min_buf);
} else {
tv_to_buf(&ss_last_max, max_buf, sizeof(max_buf));
snprintf(sharerange, sizeof(sharerange),
"share dates %s to %s",
min_buf, max_buf);
}
LOGWARNING("%s() Auto-aged %"PRId64"(%"PRId64") "
"share%s %d sharesummar%s %d workinfoid%s "
"%s %s",
__func__,
s_count_tot, s_diff_tot,
(s_count_tot == 1) ? "" : "s",
ss_count_tot,
(ss_count_tot == 1) ? "y" : "ies",
wid_count,
(wid_count == 1) ? "" : "s",
idrange, sharerange);
}
}
}
static bool workinfo_fill(PGconn *conn)
{
ExecStatusType rescode;
PGresult *res;
K_ITEM *item;
int n, i;
WORKINFO *row;
char *params[1];
int par;
char *field;
char *sel;
int fields = 10;
bool ok;
LOGDEBUG("%s(): select", __func__);
// TODO: select the data based on sharesummary since old data isn't needed
// however, the ageing rules for workinfo will decide that also
// keep the last block + current? Rules will depend on payout scheme also
sel = "select "
// "workinfoid,poolinstance,transactiontree,merklehash,prevhash,"
"workinfoid,poolinstance,merklehash,prevhash,"
"coinbase1,coinbase2,version,bits,ntime,reward"
HISTORYDATECONTROL
" from workinfo where expirydate=$1";
par = 0;
params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0);
PARCHK(par, params);
res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_READ);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Select", rescode, conn);
PQclear(res);
return false;
}
n = PQnfields(res);
if (n != (fields + HISTORYDATECOUNT)) {
LOGERR("%s(): Invalid field count - should be %d, but is %d",
__func__, fields + HISTORYDATECOUNT, n);
PQclear(res);
return false;
}
n = PQntuples(res);
LOGDEBUG("%s(): tree build count %d", __func__, n);
ok = true;
K_WLOCK(workinfo_free);
for (i = 0; i < n; i++) {
item = k_unlink_head(workinfo_free);
DATA_WORKINFO(row, item);
if (everyone_die) {
ok = false;
break;
}
PQ_GET_FLD(res, i, "workinfoid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("workinfoid", field, row->workinfoid);
PQ_GET_FLD(res, i, "poolinstance", field, ok);
if (!ok)
break;
TXT_TO_STR("poolinstance", field, row->poolinstance);
/* Not currently needed in RAM
PQ_GET_FLD(res, i, "transactiontree", field, ok);
if (!ok)
break;
TXT_TO_BLOB("transactiontree", field, row->transactiontree);
*/
row->transactiontree = strdup(EMPTY);
PQ_GET_FLD(res, i, "merklehash", field, ok);
if (!ok)
break;
TXT_TO_BLOB("merklehash", field, row->merklehash);
PQ_GET_FLD(res, i, "prevhash", field, ok);
if (!ok)
break;
TXT_TO_STR("prevhash", field, row->prevhash);
PQ_GET_FLD(res, i, "coinbase1", field, ok);
if (!ok)
break;
TXT_TO_STR("coinbase1", field, row->coinbase1);
PQ_GET_FLD(res, i, "coinbase2", field, ok);
if (!ok)
break;
TXT_TO_STR("coinbase2", field, row->coinbase2);
PQ_GET_FLD(res, i, "version", field, ok);
if (!ok)
break;
TXT_TO_STR("version", field, row->version);
PQ_GET_FLD(res, i, "bits", field, ok);
if (!ok)
break;
TXT_TO_STR("bits", field, row->bits);
PQ_GET_FLD(res, i, "ntime", field, ok);
if (!ok)
break;
TXT_TO_STR("ntime", field, row->ntime);
PQ_GET_FLD(res, i, "reward", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("reward", field, row->reward);
HISTORYDATEFLDS(res, i, row, ok);
if (!ok)
break;
workinfo_root = add_to_ktree(workinfo_root, item, cmp_workinfo);
if (!confirm_sharesummary)
workinfo_height_root = add_to_ktree(workinfo_height_root, item, cmp_workinfo_height);
k_add_head(workinfo_store, item);
if (tv_newer(&(dbstatus.newest_createdate_workinfo), &(row->createdate)))
copy_tv(&(dbstatus.newest_createdate_workinfo), &(row->createdate));
tick();
}
if (!ok)
k_add_head(workinfo_free, item);
K_WUNLOCK(workinfo_free);
PQclear(res);
if (ok) {
LOGDEBUG("%s(): built", __func__);
LOGWARNING("%s(): loaded %d workinfo records", __func__, n);
}
return ok;
}
void workinfo_reload()
{
// TODO: ??? a bad idea?
/*
PGconn *conn = dbconnect();
K_WLOCK(workinfo_free);
workinfo_root = free_ktree(workinfo_root, ???); free transactiontree and merklehash
k_list_transfer_to_head(workinfo_store, workinfo_free);
K_WUNLOCK(workinfo_free);
workinfo_fill(conn);
PQfinish(conn);
*/
}
// order by workinfoid asc,userid asc,workername asc,createdate asc,nonce asc,expirydate desc
static cmp_t cmp_shares(K_ITEM *a, K_ITEM *b)
{
SHARES *sa, *sb;
DATA_SHARES(sa, a);
DATA_SHARES(sb, b);
cmp_t c = CMP_BIGINT(sa->workinfoid, sb->workinfoid);
if (c == 0) {
c = CMP_BIGINT(sa->userid, sb->userid);
if (c == 0) {
c = CMP_STR(sa->workername, sb->workername);
if (c == 0) {
c = CMP_TV(sa->createdate, sb->createdate);
if (c == 0) {
c = CMP_STR(sa->nonce, sb->nonce);
if (c == 0) {
c = CMP_TV(sb->expirydate,
sa->expirydate);
}
}
}
}
}
return c;
}
static void zero_sharesummary(SHARESUMMARY *row, tv_t *cd, double diff)
{
row->diffacc = row->diffsta = row->diffdup = row->diffhi =
row->diffrej = row->shareacc = row->sharesta = row->sharedup =
row->sharehi = row->sharerej = 0.0;
row->sharecount = row->errorcount = row->countlastupdate = 0;
row->reset = false;
row->firstshare.tv_sec = cd->tv_sec;
row->firstshare.tv_usec = cd->tv_usec;
row->lastshare.tv_sec = row->firstshare.tv_sec;
row->lastshare.tv_usec = row->firstshare.tv_usec;
row->lastdiffacc = diff;
row->complete[0] = SUMMARY_NEW;
row->complete[1] = '\0';
}
static K_ITEM *find_sharesummary(int64_t userid, char *workername, int64_t workinfoid);
// Memory (and log file) only
static bool shares_add(PGconn *conn, char *workinfoid, char *username, char *workername,
char *clientid, char *errn, char *enonce1, char *nonce2,
char *nonce, char *diff, char *sdiff, char *secondaryuserid,
char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root)
{
K_ITEM *s_item, *u_item, *wi_item, *w_item, *ss_item;
char cd_buf[DATE_BUFSIZ];
SHARESUMMARY *sharesummary;
SHARES *shares;
USERS *users;
bool ok = false;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(shares_free);
s_item = k_unlink_head(shares_free);
K_WUNLOCK(shares_free);
DATA_SHARES(shares, s_item);
K_RLOCK(users_free);
u_item = find_users(username);
K_RUNLOCK(users_free);
if (!u_item) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s() %s/%ld,%ld %.19s no user! Share discarded!",
__func__, username,
cd->tv_sec, cd->tv_usec, cd_buf);
goto unitem;
}
DATA_USERS(users, u_item);
shares->userid = users->userid;
TXT_TO_BIGINT("workinfoid", workinfoid, shares->workinfoid);
STRNCPY(shares->workername, workername);
TXT_TO_INT("clientid", clientid, shares->clientid);
TXT_TO_INT("errn", errn, shares->errn);
STRNCPY(shares->enonce1, enonce1);
STRNCPY(shares->nonce2, nonce2);
STRNCPY(shares->nonce, nonce);
TXT_TO_DOUBLE("diff", diff, shares->diff);
TXT_TO_DOUBLE("sdiff", sdiff, shares->sdiff);
STRNCPY(shares->secondaryuserid, secondaryuserid);
if (!(*secondaryuserid)) {
STRNCPY(shares->secondaryuserid, users->secondaryuserid);
if (!tv_newer(&missing_secuser_min, cd) ||
!tv_newer(cd, &missing_secuser_max)) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s() %s/%ld,%ld %.19s missing secondaryuserid! "
"Share corrected",
__func__, username,
cd->tv_sec, cd->tv_usec, cd_buf);
}
}
HISTORYDATEINIT(shares, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, shares);
wi_item = find_workinfo(shares->workinfoid);
if (!wi_item) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s() %"PRId64"/%s/%ld,%ld %.19s no workinfo! Share discarded!",
__func__, shares->workinfoid, workername,
cd->tv_sec, cd->tv_usec, cd_buf);
goto unitem;
}
w_item = new_default_worker(conn, false, shares->userid, shares->workername,
by, code, inet, cd, trf_root);
if (!w_item)
goto unitem;
if (reloading && !confirm_sharesummary) {
ss_item = find_sharesummary(shares->userid, shares->workername, shares->workinfoid);
if (ss_item) {
DATA_SHARESUMMARY(sharesummary, ss_item);
if (sharesummary->complete[0] != SUMMARY_NEW) {
K_WLOCK(shares_free);
k_add_head(shares_free, s_item);
K_WUNLOCK(shares_free);
return true;
}
if (!sharesummary->reset) {
zero_sharesummary(sharesummary, cd, shares->diff);
sharesummary->reset = true;
}
}
}
if (!confirm_sharesummary)
workerstatus_update(NULL, shares, NULL);
sharesummary_update(conn, shares, NULL, NULL, by, code, inet, cd);
ok = true;
unitem:
K_WLOCK(shares_free);
if (!ok)
k_add_head(shares_free, s_item);
else {
shares_root = add_to_ktree(shares_root, s_item, cmp_shares);
k_add_head(shares_store, s_item);
}
K_WUNLOCK(shares_free);
return ok;
}
static bool shares_fill()
{
return true;
}
// order by workinfoid asc,userid asc,createdate asc,nonce asc,expirydate desc
static cmp_t cmp_shareerrors(K_ITEM *a, K_ITEM *b)
{
SHAREERRORS *sa, *sb;
DATA_SHAREERRORS(sa, a);
DATA_SHAREERRORS(sb, b);
cmp_t c = CMP_BIGINT(sa->workinfoid, sb->workinfoid);
if (c == 0) {
c = CMP_BIGINT(sa->userid, sb->userid);
if (c == 0) {
c = CMP_TV(sa->createdate, sb->createdate);
if (c == 0)
c = CMP_TV(sb->expirydate, sa->expirydate);
}
}
return c;
}
// Memory (and log file) only
// TODO: handle shareerrors that appear after a workinfoid is aged or doesn't exist?
static bool shareerrors_add(PGconn *conn, char *workinfoid, char *username,
char *workername, char *clientid, char *errn,
char *error, char *secondaryuserid, char *by,
char *code, char *inet, tv_t *cd, K_TREE *trf_root)
{
K_ITEM *s_item, *u_item, *wi_item, *w_item, *ss_item;
char cd_buf[DATE_BUFSIZ];
SHARESUMMARY *sharesummary;
SHAREERRORS *shareerrors;
USERS *users;
bool ok = false;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(shareerrors_free);
s_item = k_unlink_head(shareerrors_free);
K_WUNLOCK(shareerrors_free);
DATA_SHAREERRORS(shareerrors, s_item);
K_RLOCK(users_free);
u_item = find_users(username);
K_RUNLOCK(users_free);
if (!u_item) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s() %s/%ld,%ld %.19s no user! Shareerror discarded!",
__func__, username,
cd->tv_sec, cd->tv_usec, cd_buf);
goto unitem;
}
DATA_USERS(users, u_item);
shareerrors->userid = users->userid;
TXT_TO_BIGINT("workinfoid", workinfoid, shareerrors->workinfoid);
STRNCPY(shareerrors->workername, workername);
TXT_TO_INT("clientid", clientid, shareerrors->clientid);
TXT_TO_INT("errn", errn, shareerrors->errn);
STRNCPY(shareerrors->error, error);
STRNCPY(shareerrors->secondaryuserid, secondaryuserid);
if (!(*secondaryuserid)) {
STRNCPY(shareerrors->secondaryuserid, users->secondaryuserid);
if (!tv_newer(&missing_secuser_min, cd) ||
!tv_newer(cd, &missing_secuser_max)) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s() %s/%ld,%ld %.19s missing secondaryuserid! "
"Sharerror corrected",
__func__, username,
cd->tv_sec, cd->tv_usec, cd_buf);
}
}
HISTORYDATEINIT(shareerrors, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, shareerrors);
wi_item = find_workinfo(shareerrors->workinfoid);
if (!wi_item) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s() %"PRId64"/%s/%ld,%ld %.19s no workinfo! Shareerror discarded!",
__func__, shareerrors->workinfoid, workername,
cd->tv_sec, cd->tv_usec, cd_buf);
goto unitem;
}
w_item = new_default_worker(NULL, false, shareerrors->userid, shareerrors->workername,
by, code, inet, cd, trf_root);
if (!w_item)
goto unitem;
if (reloading && !confirm_sharesummary) {
ss_item = find_sharesummary(shareerrors->userid, shareerrors->workername, shareerrors->workinfoid);
if (ss_item) {
DATA_SHARESUMMARY(sharesummary, ss_item);
if (sharesummary->complete[0] != SUMMARY_NEW) {
K_WLOCK(shareerrors_free);
k_add_head(shareerrors_free, s_item);
K_WUNLOCK(shareerrors_free);
return true;
}
if (!sharesummary->reset) {
zero_sharesummary(sharesummary, cd, 0.0);
sharesummary->reset = true;
}
}
}
sharesummary_update(conn, NULL, shareerrors, NULL, by, code, inet, cd);
ok = true;
unitem:
K_WLOCK(shareerrors_free);
if (!ok)
k_add_head(shareerrors_free, s_item);
else {
shareerrors_root = add_to_ktree(shareerrors_root, s_item, cmp_shareerrors);
k_add_head(shareerrors_store, s_item);
}
K_WUNLOCK(shareerrors_free);
return ok;
}
static bool shareerrors_fill()
{
return true;
}
static void dsp_sharesummary(K_ITEM *item, FILE *stream)
{
char createdate_buf[DATE_BUFSIZ];
SHARESUMMARY *s;
if (!item)
fprintf(stream, "%s() called with (null) item\n", __func__);
else {
DATA_SHARESUMMARY(s, item);
tv_to_buf(&(s->createdate), createdate_buf, sizeof(createdate_buf));
fprintf(stream, " uid=%"PRId64" wn='%s' wid=%"PRId64" "
"da=%f ds=%f ss=%f c='%s' cd=%s\n",
s->userid, s->workername, s->workinfoid,
s->diffacc, s->diffsta, s->sharesta,
s->complete, createdate_buf);
}
}
// default tree order by userid asc,workername asc,workinfoid asc for reporting
static cmp_t cmp_sharesummary(K_ITEM *a, K_ITEM *b)
{
SHARESUMMARY *sa, *sb;
DATA_SHARESUMMARY(sa, a);
DATA_SHARESUMMARY(sb, b);
cmp_t c = CMP_BIGINT(sa->userid, sb->userid);
if (c == 0) {
c = CMP_STR(sa->workername, sb->workername);
if (c == 0)
c = CMP_BIGINT(sa->workinfoid, sb->workinfoid);
}
return c;
}
// order by workinfoid asc,userid asc,workername asc for flagging complete
static cmp_t cmp_sharesummary_workinfoid(K_ITEM *a, K_ITEM *b)
{
SHARESUMMARY *sa, *sb;
DATA_SHARESUMMARY(sa, a);
DATA_SHARESUMMARY(sb, b);
cmp_t c = CMP_BIGINT(sa->workinfoid, sb->workinfoid);
if (c == 0) {
c = CMP_BIGINT(sa->userid, sb->userid);
if (c == 0)
c = CMP_STR(sa->workername, sb->workername);
}
return c;
}
static K_ITEM *find_sharesummary(int64_t userid, char *workername, int64_t workinfoid)
{
SHARESUMMARY sharesummary;
K_TREE_CTX ctx[1];
K_ITEM look;
sharesummary.userid = userid;
STRNCPY(sharesummary.workername, workername);
sharesummary.workinfoid = workinfoid;
INIT_SHARESUMMARY(&look);
look.data = (void *)(&sharesummary);
return find_in_ktree(sharesummary_root, &look, cmp_sharesummary, ctx);
}
static bool _sharesummary_update(PGconn *conn, SHARES *s_row, SHAREERRORS *e_row, K_ITEM *ss_item,
char *by, char *code, char *inet, tv_t *cd, WHERE_FFL_ARGS)
{
ExecStatusType rescode;
PGresult *res = NULL;
SHARESUMMARY *row;
K_ITEM *item;
char *ins, *upd;
bool ok = false, new;
char *params[19 + MODIFYDATECOUNT];
int n, par = 0;
int64_t userid, workinfoid;
char *workername;
tv_t *sharecreatedate;
bool must_update = false, conned = false;
double diff = 0;
LOGDEBUG("%s(): update", __func__);
if (ss_item) {
if (s_row || e_row) {
quithere(1, "ERR: only one of s_row, e_row and "
"ss_item allowed" WHERE_FFL,
WHERE_FFL_PASS);
}
new = false;
item = ss_item;
DATA_SHARESUMMARY(row, item);
must_update = true;
row->complete[0] = SUMMARY_COMPLETE;
row->complete[1] = '\0';
} else {
if (s_row) {
if (e_row) {
quithere(1, "ERR: only one of s_row, e_row "
"(and ss_item) allowed" WHERE_FFL,
WHERE_FFL_PASS);
}
userid = s_row->userid;
workername = s_row->workername;
workinfoid = s_row->workinfoid;
diff = s_row->diff;
sharecreatedate = &(s_row->createdate);
} else {
if (!e_row) {
quithere(1, "ERR: all s_row, e_row and "
"ss_item are NULL" WHERE_FFL,
WHERE_FFL_PASS);
}
userid = e_row->userid;
workername = e_row->workername;
workinfoid = e_row->workinfoid;
sharecreatedate = &(e_row->createdate);
}
K_RLOCK(sharesummary_free);
item = find_sharesummary(userid, workername, workinfoid);
K_RUNLOCK(sharesummary_free);
if (item) {
new = false;
DATA_SHARESUMMARY(row, item);
} else {
new = true;
K_WLOCK(sharesummary_free);
item = k_unlink_head(sharesummary_free);
K_WUNLOCK(sharesummary_free);
DATA_SHARESUMMARY(row, item);
row->userid = userid;
STRNCPY(row->workername, workername);
row->workinfoid = workinfoid;
zero_sharesummary(row, sharecreatedate, diff);
row->inserted = false;
row->saveaged = false;
}
if (e_row)
row->errorcount += 1;
else {
row->sharecount += 1;
switch (s_row->errn) {
case SE_NONE:
row->diffacc += s_row->diff;
row->shareacc++;
break;
case SE_STALE:
row->diffsta += s_row->diff;
row->sharesta++;
break;
case SE_DUPE:
row->diffdup += s_row->diff;
row->sharedup++;
break;
case SE_HIGH_DIFF:
row->diffhi += s_row->diff;
row->sharehi++;
break;
default:
row->diffrej += s_row->diff;
row->sharerej++;
break;
}
}
if (!new) {
double td;
td = tvdiff(sharecreatedate, &(row->firstshare));
// don't LOGERR '=' in case shares come from ckpool with the same timestamp
if (td < 0.0) {
char *tmp1, *tmp2;
LOGERR("%s(): %s createdate (%s) is < summary firstshare (%s)",
__func__, s_row ? "shares" : "shareerrors",
(tmp1 = ctv_to_buf(sharecreatedate, NULL, 0)),
(tmp2 = ctv_to_buf(&(row->firstshare), NULL, 0)));
free(tmp2);
free(tmp1);
row->firstshare.tv_sec = sharecreatedate->tv_sec;
row->firstshare.tv_usec = sharecreatedate->tv_usec;
// Don't change lastdiffacc
}
td = tvdiff(sharecreatedate, &(row->lastshare));
// don't LOGERR '=' in case shares come from ckpool with the same timestamp
if (td >= 0.0) {
row->lastshare.tv_sec = sharecreatedate->tv_sec;
row->lastshare.tv_usec = sharecreatedate->tv_usec;
row->lastdiffacc = diff;
} else {
char *tmp1, *tmp2;
LOGERR("%s(): %s createdate (%s) is < summary lastshare (%s)",
__func__, s_row ? "shares" : "shareerrors",
(tmp1 = ctv_to_buf(sharecreatedate, NULL, 0)),
(tmp2 = ctv_to_buf(&(row->lastshare), NULL, 0)));
free(tmp2);
free(tmp1);
}
if (row->complete[0] != SUMMARY_NEW) {
LOGDEBUG("%s(): updating sharesummary not '%c' %"PRId64"/%s/%"PRId64"/%s",
__func__, SUMMARY_NEW, row->userid, row->workername,
row->workinfoid, row->complete);
}
}
}
// During startup, don't save 'new' sharesummaries, to reduce DB I/O
if (!startup_complete && row->complete[0] == SUMMARY_NEW)
goto startupskip;
if (conn == NULL && !confirm_sharesummary) {
conn = dbconnect();
conned = true;
}
if (new || !(row->inserted)) {
MODIFYDATEINIT(row, cd, by, code, inet);
par = 0;
if (!confirm_sharesummary) {
params[par++] = bigint_to_buf(row->userid, NULL, 0);
params[par++] = str_to_buf(row->workername, NULL, 0);
params[par++] = bigint_to_buf(row->workinfoid, NULL, 0);
params[par++] = double_to_buf(row->diffacc, NULL, 0);
params[par++] = double_to_buf(row->diffsta, NULL, 0);
params[par++] = double_to_buf(row->diffdup, NULL, 0);
params[par++] = double_to_buf(row->diffhi, NULL, 0);
params[par++] = double_to_buf(row->diffrej, NULL, 0);
params[par++] = double_to_buf(row->shareacc, NULL, 0);
params[par++] = double_to_buf(row->sharesta, NULL, 0);
params[par++] = double_to_buf(row->sharedup, NULL, 0);
params[par++] = double_to_buf(row->sharehi, NULL, 0);
params[par++] = double_to_buf(row->sharerej, NULL, 0);
params[par++] = bigint_to_buf(row->sharecount, NULL, 0);
params[par++] = bigint_to_buf(row->errorcount, NULL, 0);
params[par++] = tv_to_buf(&(row->firstshare), NULL, 0);
params[par++] = tv_to_buf(&(row->lastshare), NULL, 0);
params[par++] = double_to_buf(row->lastdiffacc, NULL, 0);
params[par++] = str_to_buf(row->complete, NULL, 0);
MODIFYDATEPARAMS(params, par, row);
PARCHK(par, params);
ins = "insert into sharesummary "
"(userid,workername,workinfoid,diffacc,diffsta,diffdup,diffhi,"
"diffrej,shareacc,sharesta,sharedup,sharehi,sharerej,"
"sharecount,errorcount,firstshare,lastshare,"
"lastdiffacc,complete"
MODIFYDATECONTROL ") values (" PQPARAM27 ")";
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
goto unparam;
}
}
row->countlastupdate = row->sharecount + row->errorcount;
row->inserted = true;
if (row->complete[0] == SUMMARY_COMPLETE)
row->saveaged = true;
} else {
bool stats_update = false;
MODIFYUPDATE(row, cd, by, code, inet);
if ((row->countlastupdate + SHARESUMMARY_UPDATE_EVERY) <
(row->sharecount + row->errorcount))
stats_update = true;
if (must_update && row->countlastupdate < (row->sharecount + row->errorcount))
stats_update = true;
if (stats_update) {
par = 0;
if (!confirm_sharesummary) {
params[par++] = bigint_to_buf(row->userid, NULL, 0);
params[par++] = str_to_buf(row->workername, NULL, 0);
params[par++] = bigint_to_buf(row->workinfoid, NULL, 0);
params[par++] = double_to_buf(row->diffacc, NULL, 0);
params[par++] = double_to_buf(row->diffsta, NULL, 0);
params[par++] = double_to_buf(row->diffdup, NULL, 0);
params[par++] = double_to_buf(row->diffhi, NULL, 0);
params[par++] = double_to_buf(row->diffrej, NULL, 0);
params[par++] = double_to_buf(row->shareacc, NULL, 0);
params[par++] = double_to_buf(row->sharesta, NULL, 0);
params[par++] = double_to_buf(row->sharedup, NULL, 0);
params[par++] = double_to_buf(row->sharehi, NULL, 0);
params[par++] = double_to_buf(row->sharerej, NULL, 0);
params[par++] = tv_to_buf(&(row->firstshare), NULL, 0);
params[par++] = tv_to_buf(&(row->lastshare), NULL, 0);
params[par++] = bigint_to_buf(row->sharecount, NULL, 0);
params[par++] = bigint_to_buf(row->errorcount, NULL, 0);
params[par++] = double_to_buf(row->lastdiffacc, NULL, 0);
params[par++] = str_to_buf(row->complete, NULL, 0);
MODIFYUPDATEPARAMS(params, par, row);
PARCHKVAL(par, 23, params);
upd = "update sharesummary "
"set diffacc=$4,diffsta=$5,diffdup=$6,diffhi=$7,diffrej=$8,"
"shareacc=$9,sharesta=$10,sharedup=$11,sharehi=$12,"
"sharerej=$13,firstshare=$14,lastshare=$15,"
"sharecount=$16,errorcount=$17,lastdiffacc=$18,complete=$19"
",modifydate=$20,modifyby=$21,modifycode=$22,modifyinet=$23 "
"where userid=$1 and workername=$2 and workinfoid=$3";
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Update", rescode, conn);
goto unparam;
}
}
row->countlastupdate = row->sharecount + row->errorcount;
if (row->complete[0] == SUMMARY_COMPLETE)
row->saveaged = true;
} else {
if (!must_update) {
ok = true;
goto late;
} else {
par = 0;
if (!confirm_sharesummary) {
params[par++] = bigint_to_buf(row->userid, NULL, 0);
params[par++] = str_to_buf(row->workername, NULL, 0);
params[par++] = bigint_to_buf(row->workinfoid, NULL, 0);
params[par++] = str_to_buf(row->complete, NULL, 0);
MODIFYUPDATEPARAMS(params, par, row);
PARCHKVAL(par, 8, params);
upd = "update sharesummary "
"set complete=$4,modifydate=$5,modifyby=$6,modifycode=$7,modifyinet=$8 "
"where userid=$1 and workername=$2 and workinfoid=$3";
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("MustUpdate", rescode, conn);
goto unparam;
}
}
row->countlastupdate = row->sharecount + row->errorcount;
if (row->complete[0] == SUMMARY_COMPLETE)
row->saveaged = true;
}
}
}
startupskip:
ok = true;
unparam:
if (par) {
PQclear(res);
for (n = 0; n < par; n++)
free(params[n]);
}
late:
if (conned)
PQfinish(conn);
// We keep the new item no matter what 'ok' is, since it will be inserted later
K_WLOCK(sharesummary_free);
if (new) {
sharesummary_root = add_to_ktree(sharesummary_root, item, cmp_sharesummary);
sharesummary_workinfoid_root = add_to_ktree(sharesummary_workinfoid_root,
item,
cmp_sharesummary_workinfoid);
k_add_head(sharesummary_store, item);
}
K_WUNLOCK(sharesummary_free);
return ok;
}
static bool sharesummary_fill(PGconn *conn)
{
ExecStatusType rescode;
PGresult *res;
K_ITEM *item;
int n, i;
SHARESUMMARY *row;
char *field;
char *sel;
int fields = 19;
bool ok;
LOGDEBUG("%s(): select", __func__);
// TODO: limit how far back
sel = "select "
"userid,workername,workinfoid,diffacc,diffsta,diffdup,diffhi,"
"diffrej,shareacc,sharesta,sharedup,sharehi,sharerej,"
"sharecount,errorcount,firstshare,lastshare,"
"lastdiffacc,complete"
MODIFYDATECONTROL
" from sharesummary";
res = PQexec(conn, sel, CKPQ_READ);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Select", rescode, conn);
PQclear(res);
return false;
}
n = PQnfields(res);
if (n != (fields + MODIFYDATECOUNT)) {
LOGERR("%s(): Invalid field count - should be %d, but is %d",
__func__, fields + MODIFYDATECOUNT, n);
PQclear(res);
return false;
}
n = PQntuples(res);
LOGDEBUG("%s(): tree build count %d", __func__, n);
ok = true;
for (i = 0; i < n; i++) {
item = k_unlink_head(sharesummary_free);
DATA_SHARESUMMARY(row, item);
if (everyone_die) {
ok = false;
break;
}
row->inserted = true;
PQ_GET_FLD(res, i, "userid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("userid", field, row->userid);
PQ_GET_FLD(res, i, "workername", field, ok);
if (!ok)
break;
TXT_TO_STR("workername", field, row->workername);
PQ_GET_FLD(res, i, "workinfoid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("workinfoid", field, row->workinfoid);
PQ_GET_FLD(res, i, "diffacc", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("diffacc", field, row->diffacc);
PQ_GET_FLD(res, i, "diffsta", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("diffsta", field, row->diffsta);
PQ_GET_FLD(res, i, "diffdup", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("diffdup", field, row->diffdup);
PQ_GET_FLD(res, i, "diffhi", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("diffhi", field, row->diffhi);
PQ_GET_FLD(res, i, "diffrej", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("diffrej", field, row->diffrej);
PQ_GET_FLD(res, i, "shareacc", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("shareacc", field, row->shareacc);
PQ_GET_FLD(res, i, "sharesta", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("sharesta", field, row->sharesta);
PQ_GET_FLD(res, i, "sharedup", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("sharedup", field, row->sharedup);
PQ_GET_FLD(res, i, "sharehi", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("sharehi", field, row->sharehi);
PQ_GET_FLD(res, i, "sharerej", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("sharerej", field, row->sharerej);
PQ_GET_FLD(res, i, "sharecount", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("sharecount", field, row->sharecount);
PQ_GET_FLD(res, i, "errorcount", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("errorcount", field, row->errorcount);
row->countlastupdate = row->sharecount + row->errorcount;
PQ_GET_FLD(res, i, "firstshare", field, ok);
if (!ok)
break;
TXT_TO_TV("firstshare", field, row->firstshare);
PQ_GET_FLD(res, i, "lastshare", field, ok);
if (!ok)
break;
TXT_TO_TV("lastshare", field, row->lastshare);
PQ_GET_FLD(res, i, "lastdiffacc", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("lastdiffacc", field, row->lastdiffacc);
PQ_GET_FLD(res, i, "complete", field, ok);
if (!ok)
break;
TXT_TO_STR("complete", field, row->complete);
MODIFYDATEFLDS(res, i, row, ok);
if (!ok)
break;
sharesummary_root = add_to_ktree(sharesummary_root, item, cmp_sharesummary);
sharesummary_workinfoid_root = add_to_ktree(sharesummary_workinfoid_root, item, cmp_sharesummary_workinfoid);
k_add_head(sharesummary_store, item);
// A share summary is currently only shares in a single workinfo, at all 3 levels n,a,y
if (tolower(row->complete[0]) == SUMMARY_NEW) {
if (dbstatus.oldest_sharesummary_firstshare_n.tv_sec == 0 ||
!tv_newer(&(dbstatus.oldest_sharesummary_firstshare_n), &(row->firstshare))) {
copy_tv(&(dbstatus.oldest_sharesummary_firstshare_n), &(row->firstshare));
dbstatus.oldest_workinfoid_n = row->workinfoid;
}
} else {
if (tv_newer(&(dbstatus.newest_sharesummary_firstshare_ay), &(row->firstshare)))
copy_tv(&(dbstatus.newest_sharesummary_firstshare_ay), &(row->firstshare));
if (tolower(row->complete[0]) == SUMMARY_COMPLETE) {
if (dbstatus.oldest_sharesummary_firstshare_a.tv_sec == 0 ||
!tv_newer(&(dbstatus.oldest_sharesummary_firstshare_a), &(row->firstshare))) {
copy_tv(&(dbstatus.oldest_sharesummary_firstshare_a), &(row->firstshare));
dbstatus.oldest_workinfoid_a = row->workinfoid;
}
if (tv_newer(&(dbstatus.newest_sharesummary_firstshare_a), &(row->firstshare))) {
copy_tv(&(dbstatus.newest_sharesummary_firstshare_a), &(row->firstshare));
dbstatus.newest_workinfoid_a = row->workinfoid;
}
} else /* SUMMARY_CONFIRM */ {
if (tv_newer(&(dbstatus.newest_sharesummary_firstshare_y), &(row->firstshare))) {
copy_tv(&(dbstatus.newest_sharesummary_firstshare_y), &(row->firstshare));
dbstatus.newest_workinfoid_y = row->workinfoid;
}
}
}
tick();
}
if (!ok)
k_add_head(sharesummary_free, item);
PQclear(res);
if (ok) {
LOGDEBUG("%s(): built", __func__);
LOGWARNING("%s(): loaded %d sharesummary records", __func__, n);
}
return ok;
}
void sharesummary_reload()
{
PGconn *conn = dbconnect();
sharesummary_root = free_ktree(sharesummary_root, NULL);
sharesummary_workinfoid_root = free_ktree(sharesummary_workinfoid_root, NULL);
k_list_transfer_to_head(sharesummary_store, sharesummary_free);
sharesummary_fill(conn);
PQfinish(conn);
}
// TODO: do this better ... :)
static void dsp_hash(char *hash, char *buf, size_t siz)
{
char *ptr;
ptr = hash + strlen(hash) - (siz - 1) - 8;
if (ptr < hash)
ptr = hash;
STRNCPYSIZ(buf, ptr, siz);
}
static void dsp_blocks(K_ITEM *item, FILE *stream)
{
char createdate_buf[DATE_BUFSIZ], expirydate_buf[DATE_BUFSIZ];
BLOCKS *b = NULL;
char hash_dsp[16+1];
if (!item)
fprintf(stream, "%s() called with (null) item\n", __func__);
else {
DATA_BLOCKS(b, item);
dsp_hash(b->blockhash, hash_dsp, sizeof(hash_dsp));
tv_to_buf(&(b->createdate), createdate_buf, sizeof(createdate_buf));
tv_to_buf(&(b->expirydate), expirydate_buf, sizeof(expirydate_buf));
fprintf(stream, " hi=%d hash='%.16s' conf=%s uid=%"PRId64
" w='%s' sconf=%s cd=%s ed=%s\n",
b->height, hash_dsp, b->confirmed, b->userid,
b->workername, b->statsconfirmed,
createdate_buf, expirydate_buf);
}
}
// order by height asc,blockhash asc,expirydate desc
static cmp_t cmp_blocks(K_ITEM *a, K_ITEM *b)
{
BLOCKS *ba, *bb;
DATA_BLOCKS(ba, a);
DATA_BLOCKS(bb, b);
cmp_t c = CMP_INT(ba->height, bb->height);
if (c == 0) {
c = CMP_STR(ba->blockhash, bb->blockhash);
if (c == 0)
c = CMP_TV(bb->expirydate, ba->expirydate);
}
return c;
}
/* TODO: and make sure all block searches use these
* or add new ones as required here */
// Must be R or W locked before call - gets current status (default_expiry)
static K_ITEM *find_blocks(int32_t height, char *blockhash)
{
BLOCKS blocks;
K_TREE_CTX ctx[1];
K_ITEM look;
blocks.height = height;
STRNCPY(blocks.blockhash, blockhash);
blocks.expirydate.tv_sec = default_expiry.tv_sec;
blocks.expirydate.tv_usec = default_expiry.tv_usec;
INIT_BLOCKS(&look);
look.data = (void *)(&blocks);
return find_in_ktree(blocks_root, &look, cmp_blocks, ctx);
}
// Must be R or W locked before call
static K_ITEM *find_prev_blocks(int32_t height)
{
BLOCKS lookblocks, *blocks;
K_TREE_CTX ctx[1];
K_ITEM look, *b_item;
/* TODO: For self orphaned (if that ever happens)
* this will find based on blockhash order if it has two,
* not NEW, blocks, which might not find the right one */
lookblocks.height = height;
lookblocks.blockhash[0] = '\0';
lookblocks.expirydate.tv_sec = 0L;
lookblocks.expirydate.tv_usec = 0L;
INIT_BLOCKS(&look);
look.data = (void *)(&lookblocks);
b_item = find_before_in_ktree(blocks_root, &look, cmp_blocks, ctx);
while (b_item) {
DATA_BLOCKS(blocks, b_item);
if (blocks->confirmed[0] != BLOCKS_NEW &&
CURRENT(&(blocks->expirydate)))
return b_item;
b_item = prev_in_ktree(ctx);
}
return NULL;
}
static const char *blocks_confirmed(char *confirmed)
{
switch (confirmed[0]) {
case BLOCKS_NEW:
return blocks_new;
case BLOCKS_CONFIRM:
return blocks_confirm;
case BLOCKS_42:
return blocks_42;
case BLOCKS_ORPHAN:
return blocks_orphan;
}
return blocks_unknown;
}
static bool blocks_stats(PGconn *conn, int32_t height, char *blockhash,
double diffacc, double diffinv, double shareacc,
double shareinv, int64_t elapsed,
char *by, char *code, char *inet, tv_t *cd)
{
ExecStatusType rescode;
bool conned = false;
PGresult *res = NULL;
K_TREE_CTX ctx[1];
K_ITEM *b_item, *old_b_item;
BLOCKS *row, *oldblocks;
char hash_dsp[16+1];
char *upd, *ins;
char *params[8 + HISTORYDATECOUNT];
bool ok = false, update_old = false;
int par = 0;
int n;
LOGDEBUG("%s(): confirm", __func__);
dsp_hash(blockhash, hash_dsp, sizeof(hash_dsp));
K_RLOCK(blocks_free);
old_b_item = find_blocks(height, blockhash);
K_RUNLOCK(blocks_free);
if (!old_b_item) {
LOGERR("%s(): Non-existent Block: %d/...%s",
__func__, height, hash_dsp);
return false;
}
DATA_BLOCKS(oldblocks, old_b_item);
K_WLOCK(blocks_free);
b_item = k_unlink_head(blocks_free);
K_WUNLOCK(blocks_free);
DATA_BLOCKS(row, b_item);
memcpy(row, oldblocks, sizeof(*row));
row->diffacc = diffacc;
row->diffinv = diffinv;
row->shareacc = shareacc;
row->shareinv = shareinv;
row->elapsed = elapsed;
row->statsconfirmed[0] = BLOCKS_STATSCONFIRMED;
row->statsconfirmed[1] = '\0';
HISTORYDATEINIT(row, cd, by, code, inet);
upd = "update blocks set expirydate=$1 where blockhash=$2 and expirydate=$3";
par = 0;
params[par++] = tv_to_buf(cd, NULL, 0);
params[par++] = str_to_buf(row->blockhash, NULL, 0);
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0);
PARCHKVAL(par, 3, params);
if (conn == NULL) {
conn = dbconnect();
conned = true;
}
res = PQexec(conn, "Begin", CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Begin", rescode, conn);
goto unparam;
}
PQclear(res);
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
PQclear(res);
if (!PGOK(rescode)) {
PGLOGERR("Update", rescode, conn);
res = PQexec(conn, "Rollback", CKPQ_WRITE);
goto unparam;
}
update_old = true;
for (n = 0; n < par; n++)
free(params[n]);
par = 0;
params[par++] = str_to_buf(row->blockhash, NULL, 0);
params[par++] = tv_to_buf(cd, NULL, 0);
params[par++] = double_to_buf(row->diffacc, NULL, 0);
params[par++] = double_to_buf(row->diffinv, NULL, 0);
params[par++] = double_to_buf(row->shareacc, NULL, 0);
params[par++] = double_to_buf(row->shareinv, NULL, 0);
params[par++] = bigint_to_buf(row->elapsed, NULL, 0);
params[par++] = str_to_buf(row->statsconfirmed, NULL, 0);
HISTORYDATEPARAMS(params, par, row);
PARCHKVAL(par, 8 + HISTORYDATECOUNT, params); // 13 as per ins
ins = "insert into blocks "
"(height,blockhash,workinfoid,userid,workername,"
"clientid,enonce1,nonce2,nonce,reward,confirmed,"
"diffacc,diffinv,shareacc,shareinv,elapsed,"
"statsconfirmed"
HISTORYDATECONTROL ") select "
"height,blockhash,workinfoid,userid,workername,"
"clientid,enonce1,nonce2,nonce,reward,confirmed,"
"$3,$4,$5,$6,$7,$8,"
"$9,$10,$11,$12,$13 from blocks where "
"blockhash=$1 and expirydate=$2";
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
PQclear(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
res = PQexec(conn, "Rollback", CKPQ_WRITE);
goto unparam;
}
res = PQexec(conn, "Commit", CKPQ_WRITE);
ok = true;
unparam:
PQclear(res);
for (n = 0; n < par; n++)
free(params[n]);
if (conned)
PQfinish(conn);
K_WLOCK(blocks_free);
if (!ok)
k_add_head(blocks_free, b_item);
else {
if (update_old) {
blocks_root = remove_from_ktree(blocks_root, old_b_item, cmp_blocks, ctx);
copy_tv(&(oldblocks->expirydate), cd);
blocks_root = add_to_ktree(blocks_root, old_b_item, cmp_blocks);
}
blocks_root = add_to_ktree(blocks_root, b_item, cmp_blocks);
k_add_head(blocks_store, b_item);
}
K_WUNLOCK(blocks_free);
return ok;
}
static bool blocks_add(PGconn *conn, char *height, char *blockhash,
char *confirmed, char *workinfoid, char *username,
char *workername, char *clientid, char *enonce1,
char *nonce2, char *nonce, char *reward,
char *by, char *code, char *inet, tv_t *cd,
bool igndup, char *id, K_TREE *trf_root)
{
ExecStatusType rescode;
bool conned = false;
PGresult *res = NULL;
K_TREE_CTX ctx[1];
K_ITEM *b_item, *u_item, *old_b_item;
char cd_buf[DATE_BUFSIZ];
char hash_dsp[16+1];
BLOCKS *row, *oldblocks;
USERS *users;
char *upd, *ins;
char *params[17 + HISTORYDATECOUNT];
bool ok = false, update_old = false;
int par = 0;
char want = '?';
int n;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(blocks_free);
b_item = k_unlink_head(blocks_free);
K_WUNLOCK(blocks_free);
DATA_BLOCKS(row, b_item);
TXT_TO_INT("height", height, row->height);
STRNCPY(row->blockhash, blockhash);
dsp_hash(blockhash, hash_dsp, sizeof(hash_dsp));
K_RLOCK(blocks_free);
old_b_item = find_blocks(row->height, blockhash);
K_RUNLOCK(blocks_free);
DATA_BLOCKS_NULL(oldblocks, old_b_item);
switch (confirmed[0]) {
case BLOCKS_NEW:
// None should exist - so must be a duplicate
if (old_b_item) {
K_WLOCK(blocks_free);
k_add_head(blocks_free, b_item);
K_WUNLOCK(blocks_free);
if (!igndup) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s(): Duplicate (%s) blocks ignored, Status: "
"%s, Block: %s/...%s/%s",
__func__,
blocks_confirmed(oldblocks->confirmed),
blocks_confirmed(confirmed),
height, hash_dsp, cd_buf);
}
return true;
}
K_RLOCK(users_free);
u_item = find_users(username);
K_RUNLOCK(users_free);
if (!u_item)
row->userid = KANO;
else {
DATA_USERS(users, u_item);
row->userid = users->userid;
}
STRNCPY(row->confirmed, confirmed);
TXT_TO_BIGINT("workinfoid", workinfoid, row->workinfoid);
STRNCPY(row->workername, workername);
TXT_TO_INT("clientid", clientid, row->clientid);
STRNCPY(row->enonce1, enonce1);
STRNCPY(row->nonce2, nonce2);
STRNCPY(row->nonce, nonce);
TXT_TO_BIGINT("reward", reward, row->reward);
// Specify them
row->diffacc = 0;
row->diffinv = 0;
row->shareacc = 0;
row->shareinv = 0;
row->elapsed = 0;
STRNCPY(row->statsconfirmed, BLOCKS_STATSPENDING_STR);
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);
par = 0;
params[par++] = int_to_buf(row->height, NULL, 0);
params[par++] = str_to_buf(row->blockhash, NULL, 0);
params[par++] = bigint_to_buf(row->workinfoid, NULL, 0);
params[par++] = bigint_to_buf(row->userid, NULL, 0);
params[par++] = str_to_buf(row->workername, NULL, 0);
params[par++] = int_to_buf(row->clientid, NULL, 0);
params[par++] = str_to_buf(row->enonce1, NULL, 0);
params[par++] = str_to_buf(row->nonce2, NULL, 0);
params[par++] = str_to_buf(row->nonce, NULL, 0);
params[par++] = bigint_to_buf(row->reward, NULL, 0);
params[par++] = str_to_buf(row->confirmed, NULL, 0);
params[par++] = double_to_buf(row->diffacc, NULL, 0);
params[par++] = double_to_buf(row->diffinv, NULL, 0);
params[par++] = double_to_buf(row->shareacc, NULL, 0);
params[par++] = double_to_buf(row->shareinv, NULL, 0);
params[par++] = bigint_to_buf(row->elapsed, NULL, 0);
params[par++] = str_to_buf(row->statsconfirmed, NULL, 0);
HISTORYDATEPARAMS(params, par, row);
PARCHK(par, params);
ins = "insert into blocks "
"(height,blockhash,workinfoid,userid,workername,"
"clientid,enonce1,nonce2,nonce,reward,confirmed,"
"diffacc,diffinv,shareacc,shareinv,elapsed,"
"statsconfirmed"
HISTORYDATECONTROL ") values (" PQPARAM22 ")";
if (conn == NULL) {
conn = dbconnect();
conned = true;
}
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
goto unparam;
}
break;
case BLOCKS_ORPHAN:
case BLOCKS_42:
// These shouldn't be possible until startup completes
if (!startup_complete) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s(): Status: %s invalid during startup. "
"Ignored: Block: %s/...%s/%s",
__func__,
blocks_confirmed(confirmed),
height, hash_dsp, cd_buf);
goto flail;
}
want = BLOCKS_CONFIRM;
case BLOCKS_CONFIRM:
if (!old_b_item) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s(): Can't %s a non-existent Block: %s/...%s/%s",
__func__, blocks_confirmed(confirmed),
height, hash_dsp, cd_buf);
goto flail;
}
if (confirmed[0] == BLOCKS_CONFIRM)
want = BLOCKS_NEW;
if (oldblocks->confirmed[0] != want) {
// No mismatch messages during startup
if (startup_complete) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s(): New Status: %s requires Status: %c. "
"Ignored: Status: %s, Block: %s/...%s/%s",
__func__,
blocks_confirmed(confirmed), want,
blocks_confirmed(oldblocks->confirmed),
height, hash_dsp, cd_buf);
}
goto flail;
}
upd = "update blocks set expirydate=$1 where blockhash=$2 and expirydate=$3";
par = 0;
params[par++] = tv_to_buf(cd, NULL, 0);
params[par++] = str_to_buf(row->blockhash, NULL, 0);
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0);
PARCHKVAL(par, 3, params);
if (conn == NULL) {
conn = dbconnect();
conned = true;
}
// New is mostly a copy of the old
memcpy(row, oldblocks, sizeof(*row));
STRNCPY(row->confirmed, confirmed);
if (confirmed[0] == BLOCKS_CONFIRM) {
row->diffacc = pool.diffacc;
row->diffinv = pool.diffinv;
row->shareacc = pool.shareacc;
row->shareinv = pool.shareinv;
}
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);
res = PQexec(conn, "Begin", CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Begin", rescode, conn);
goto unparam;
}
PQclear(res);
res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
PQclear(res);
if (!PGOK(rescode)) {
PGLOGERR("Update", rescode, conn);
res = PQexec(conn, "Rollback", CKPQ_WRITE);
goto unparam;
}
for (n = 0; n < par; n++)
free(params[n]);
par = 0;
params[par++] = str_to_buf(row->blockhash, NULL, 0);
params[par++] = tv_to_buf(cd, NULL, 0);
params[par++] = str_to_buf(row->confirmed, NULL, 0);
if (confirmed[0] == BLOCKS_CONFIRM) {
params[par++] = double_to_buf(row->diffacc, NULL, 0);
params[par++] = double_to_buf(row->diffinv, NULL, 0);
params[par++] = double_to_buf(row->shareacc, NULL, 0);
params[par++] = double_to_buf(row->shareinv, NULL, 0);
HISTORYDATEPARAMS(params, par, row);
PARCHKVAL(par, 7 + HISTORYDATECOUNT, params); // 12 as per ins
ins = "insert into blocks "
"(height,blockhash,workinfoid,userid,workername,"
"clientid,enonce1,nonce2,nonce,reward,confirmed,"
"diffacc,diffinv,shareacc,shareinv,elapsed,"
"statsconfirmed"
HISTORYDATECONTROL ") select "
"height,blockhash,workinfoid,userid,workername,"
"clientid,enonce1,nonce2,nonce,reward,"
"$3,$4,$5,$6,$7,elapsed,statsconfirmed,"
"$8,$9,$10,$11,$12 from blocks where "
"blockhash=$1 and expirydate=$2";
} else {
HISTORYDATEPARAMS(params, par, row);
PARCHKVAL(par, 3 + HISTORYDATECOUNT, params); // 8 as per ins
ins = "insert into blocks "
"(height,blockhash,workinfoid,userid,workername,"
"clientid,enonce1,nonce2,nonce,reward,confirmed,"
"diffacc,diffinv,shareacc,shareinv,elapsed,"
"statsconfirmed"
HISTORYDATECONTROL ") select "
"height,blockhash,workinfoid,userid,workername,"
"clientid,enonce1,nonce2,nonce,reward,"
"$3,diffacc,diffinv,shareacc,shareinv,elapsed,"
"statsconfirmed,"
"$4,$5,$6,$7,$8 from blocks where "
"blockhash=$1 and expirydate=$2";
}
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
PQclear(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
res = PQexec(conn, "Rollback", CKPQ_WRITE);
goto unparam;
}
update_old = true;
res = PQexec(conn, "Commit", CKPQ_WRITE);
break;
default:
LOGERR("%s(): %s.failed.invalid confirm='%s'",
__func__, id, confirmed);
goto flail;
}
ok = true;
unparam:
PQclear(res);
for (n = 0; n < par; n++)
free(params[n]);
flail:
if (conned)
PQfinish(conn);
K_WLOCK(blocks_free);
if (!ok)
k_add_head(blocks_free, b_item);
else {
if (update_old) {
blocks_root = remove_from_ktree(blocks_root, old_b_item, cmp_blocks, ctx);
copy_tv(&(oldblocks->expirydate), cd);
blocks_root = add_to_ktree(blocks_root, old_b_item, cmp_blocks);
}
blocks_root = add_to_ktree(blocks_root, b_item, cmp_blocks);
k_add_head(blocks_store, b_item);
}
K_WUNLOCK(blocks_free);
if (ok) {
char pct[16] = "?";
char est[16] = "";
K_ITEM *w_item;
char tmp[256];
bool blk;
switch (confirmed[0]) {
case BLOCKS_NEW:
blk = true;
tv_to_buf(&(row->createdate), cd_buf, sizeof(cd_buf));
snprintf(tmp, sizeof(tmp), " UTC:%s", cd_buf);
break;
case BLOCKS_CONFIRM:
blk = true;
w_item = find_workinfo(row->workinfoid);
if (w_item) {
char wdiffbin[TXT_SML+1];
double wdiff;
WORKINFO *workinfo;
DATA_WORKINFO(workinfo, w_item);
hex2bin(wdiffbin, workinfo->bits, 4);
wdiff = diff_from_nbits(wdiffbin);
if (wdiff > 0.0) {
snprintf(pct, sizeof(pct), "%.2f",
100.0 * pool.diffacc / wdiff);
}
}
if (pool.diffacc >= 1000.0) {
suffix_string(pool.diffacc, est, sizeof(est)-1, 0);
strcat(est, " ");
}
tv_to_buf(&(row->createdate), cd_buf, sizeof(cd_buf));
snprintf(tmp, sizeof(tmp),
" Reward: %f, Worker: %s, ShareEst: %.1f %s%s%% UTC:%s",
BTC_TO_D(row->reward),
row->workername,
pool.diffacc, est, pct, cd_buf);
if (pool.workinfoid < row->workinfoid) {
pool.workinfoid = row->workinfoid;
zero_on_new_block();
}
break;
case BLOCKS_ORPHAN:
case BLOCKS_42:
default:
blk = false;
tmp[0] = '\0';
break;
}
LOGWARNING("%s(): %sStatus: %s, Block: %s/...%s%s",
__func__, blk ? "BLOCK! " : "",
blocks_confirmed(confirmed),
height, hash_dsp, tmp);
}
return ok;
}
static bool blocks_fill(PGconn *conn)
{
ExecStatusType rescode;
PGresult *res;
K_ITEM *item;
int n, i;
BLOCKS *row;
char *field;
char *sel;
int fields = 17;
bool ok;
LOGDEBUG("%s(): select", __func__);
sel = "select "
"height,blockhash,workinfoid,userid,workername,"
"clientid,enonce1,nonce2,nonce,reward,confirmed,"
"diffacc,diffinv,shareacc,shareinv,elapsed,statsconfirmed"
HISTORYDATECONTROL
" from blocks";
res = PQexec(conn, sel, CKPQ_READ);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Select", rescode, conn);
PQclear(res);
return false;
}
n = PQnfields(res);
if (n != (fields + HISTORYDATECOUNT)) {
LOGERR("%s(): Invalid field count - should be %d, but is %d",
__func__, fields + HISTORYDATECOUNT, n);
PQclear(res);
return false;
}
n = PQntuples(res);
LOGDEBUG("%s(): tree build count %d", __func__, n);
ok = true;
for (i = 0; i < n; i++) {
item = k_unlink_head(blocks_free);
DATA_BLOCKS(row, item);
if (everyone_die) {
ok = false;
break;
}
PQ_GET_FLD(res, i, "height", field, ok);
if (!ok)
break;
TXT_TO_INT("height", field, row->height);
PQ_GET_FLD(res, i, "blockhash", field, ok);
if (!ok)
break;
TXT_TO_STR("blockhash", field, row->blockhash);
PQ_GET_FLD(res, i, "workinfoid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("workinfoid", field, row->workinfoid);
PQ_GET_FLD(res, i, "userid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("userid", field, row->userid);
PQ_GET_FLD(res, i, "workername", field, ok);
if (!ok)
break;
TXT_TO_STR("workername", field, row->workername);
PQ_GET_FLD(res, i, "clientid", field, ok);
if (!ok)
break;
TXT_TO_INT("clientid", field, row->clientid);
PQ_GET_FLD(res, i, "enonce1", field, ok);
if (!ok)
break;
TXT_TO_STR("enonce1", field, row->enonce1);
PQ_GET_FLD(res, i, "nonce2", field, ok);
if (!ok)
break;
TXT_TO_STR("nonce2", field, row->nonce2);
PQ_GET_FLD(res, i, "nonce", field, ok);
if (!ok)
break;
TXT_TO_STR("nonce", field, row->nonce);
PQ_GET_FLD(res, i, "reward", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("reward", field, row->reward);
PQ_GET_FLD(res, i, "confirmed", field, ok);
if (!ok)
break;
TXT_TO_STR("confirmed", field, row->confirmed);
PQ_GET_FLD(res, i, "diffacc", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("diffacc", field, row->diffacc);
PQ_GET_FLD(res, i, "diffinv", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("diffinv", field, row->diffinv);
PQ_GET_FLD(res, i, "shareacc", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("shareacc", field, row->shareacc);
PQ_GET_FLD(res, i, "shareinv", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("shareinv", field, row->shareinv);
PQ_GET_FLD(res, i, "elapsed", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("elapsed", field, row->elapsed);
PQ_GET_FLD(res, i, "statsconfirmed", field, ok);
if (!ok)
break;
TXT_TO_STR("statsconfirmed", field, row->statsconfirmed);
HISTORYDATEFLDS(res, i, row, ok);
if (!ok)
break;
blocks_root = add_to_ktree(blocks_root, item, cmp_blocks);
k_add_head(blocks_store, item);
if (tv_newer(&(dbstatus.newest_createdate_blocks), &(row->createdate)))
copy_tv(&(dbstatus.newest_createdate_blocks), &(row->createdate));
if (pool.workinfoid < row->workinfoid)
pool.workinfoid = row->workinfoid;
}
if (!ok)
k_add_head(blocks_free, item);
PQclear(res);
if (ok) {
LOGDEBUG("%s(): built", __func__);
LOGWARNING("%s(): loaded %d blocks records", __func__, n);
}
return ok;
}
void blocks_reload()
{
PGconn *conn = dbconnect();
blocks_root = free_ktree(blocks_root, NULL);
k_list_transfer_to_head(blocks_store, blocks_free);
blocks_fill(conn);
PQfinish(conn);
}
/* order by height asc,userid asc,expirydate asc
* i.e. only one payout amount per block per user */
static cmp_t cmp_miningpayouts(K_ITEM *a, K_ITEM *b)
{
MININGPAYOUTS *ma, *mb;
DATA_MININGPAYOUTS(ma, a);
DATA_MININGPAYOUTS(mb, b);
cmp_t c = CMP_INT(ma->height, mb->height);
if (c == 0) {
c = CMP_BIGINT(ma->userid, mb->userid);
if (c == 0)
c = CMP_TV(ma->expirydate, mb->expirydate);
}
return c;
}
__maybe_unused static bool miningpayouts_add(PGconn *conn, char *username, char *height,
char *blockhash, char *amount, char *by,
char *code, char *inet, tv_t *cd, K_TREE *trf_root)
{
ExecStatusType rescode;
bool conned = false;
PGresult *res;
K_ITEM *m_item, *u_item;
bool ok = false;
int n;
MININGPAYOUTS *row;
USERS *users;
char *ins;
char *params[5 + HISTORYDATECOUNT];
int par;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(miningpayouts_free);
m_item = k_unlink_head(miningpayouts_free);
K_WUNLOCK(miningpayouts_free);
DATA_MININGPAYOUTS(row, m_item);
if (conn == NULL) {
conn = dbconnect();
conned = true;
}
row->miningpayoutid = nextid(conn, "miningpayoutid", (int64_t)1, cd, by, code, inet);
if (row->miningpayoutid == 0)
goto unitem;
K_RLOCK(users_free);
u_item = find_users(username);
K_RUNLOCK(users_free);
if (!u_item)
goto unitem;
DATA_USERS(users, u_item);
row->userid = users->userid;
TXT_TO_INT("height", height, row->height);
STRNCPY(row->blockhash, blockhash);
TXT_TO_BIGINT("amount", amount, row->amount);
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);
par = 0;
params[par++] = bigint_to_buf(row->miningpayoutid, NULL, 0);
params[par++] = bigint_to_buf(row->userid, NULL, 0);
params[par++] = int_to_buf(row->height, NULL, 0);
params[par++] = str_to_buf(row->blockhash, NULL, 0);
params[par++] = bigint_to_buf(row->amount, NULL, 0);
HISTORYDATEPARAMS(params, par, row);
PARCHK(par, params);
ins = "insert into miningpayouts "
"(miningpayoutid,userid,height,blockhash,amount"
HISTORYDATECONTROL ") values (" PQPARAM10 ")";
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
goto unparam;
}
ok = true;
unparam:
PQclear(res);
for (n = 0; n < par; n++)
free(params[n]);
unitem:
if (conned)
PQfinish(conn);
K_WLOCK(miningpayouts_free);
if (!ok)
k_add_head(miningpayouts_free, m_item);
else {
miningpayouts_root = add_to_ktree(miningpayouts_root, m_item, cmp_miningpayouts);
k_add_head(miningpayouts_store, m_item);
}
K_WUNLOCK(miningpayouts_free);
return ok;
}
static bool miningpayouts_fill(PGconn *conn)
{
ExecStatusType rescode;
PGresult *res;
K_ITEM *item;
int n, i;
MININGPAYOUTS *row;
char *params[1];
int par;
char *field;
char *sel;
int fields = 5;
bool ok;
LOGDEBUG("%s(): select", __func__);
sel = "select "
"miningpayoutid,userid,height,blockhash,amount"
HISTORYDATECONTROL
" from miningpayouts where expirydate=$1";
par = 0;
params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0);
PARCHK(par, params);
res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_READ);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Select", rescode, conn);
PQclear(res);
return false;
}
n = PQnfields(res);
if (n != (fields + HISTORYDATECOUNT)) {
LOGERR("%s(): Invalid field count - should be %d, but is %d",
__func__, fields + HISTORYDATECOUNT, n);
PQclear(res);
return false;
}
n = PQntuples(res);
LOGDEBUG("%s(): tree build count %d", __func__, n);
ok = true;
K_WLOCK(miningpayouts_free);
for (i = 0; i < n; i++) {
item = k_unlink_head(miningpayouts_free);
DATA_MININGPAYOUTS(row, item);
if (everyone_die) {
ok = false;
break;
}
PQ_GET_FLD(res, i, "miningpayoutid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("miningpayoutid", field, row->miningpayoutid);
PQ_GET_FLD(res, i, "userid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("userid", field, row->userid);
PQ_GET_FLD(res, i, "height", field, ok);
if (!ok)
break;
TXT_TO_INT("height", field, row->height);
PQ_GET_FLD(res, i, "blockhash", field, ok);
if (!ok)
break;
TXT_TO_STR("blockhash", field, row->blockhash);
PQ_GET_FLD(res, i, "amount", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("amount", field, row->amount);
HISTORYDATEFLDS(res, i, row, ok);
if (!ok)
break;
miningpayouts_root = add_to_ktree(miningpayouts_root, item, cmp_miningpayouts);
k_add_head(miningpayouts_store, item);
tick();
}
if (!ok)
k_add_head(miningpayouts_free, item);
K_WUNLOCK(miningpayouts_free);
PQclear(res);
if (ok) {
LOGDEBUG("%s(): built", __func__);
LOGWARNING("%s(): loaded %d miningpayout records", __func__, n);
}
return ok;
}
void miningpayouts_reload()
{
PGconn *conn = dbconnect();
K_WLOCK(miningpayouts_free);
miningpayouts_root = free_ktree(miningpayouts_root, NULL);
k_list_transfer_to_head(miningpayouts_store, miningpayouts_free);
K_WUNLOCK(miningpayouts_free);
miningpayouts_fill(conn);
PQfinish(conn);
}
// order by userid asc,createdate asc,authid asc,expirydate desc
static cmp_t cmp_auths(K_ITEM *a, K_ITEM *b)
{
AUTHS *aa, *ab;
DATA_AUTHS(aa, a);
DATA_AUTHS(ab, b);
cmp_t c = CMP_BIGINT(aa->userid, ab->userid);
if (c == 0) {
c = CMP_TV(aa->createdate, ab->createdate);
if (c == 0) {
c = CMP_BIGINT(aa->authid, ab->authid);
if (c == 0)
c = CMP_TV(ab->expirydate, aa->expirydate);
}
}
return c;
}
static char *auths_add(PGconn *conn, char *poolinstance, char *username,
char *workername, char *clientid, char *enonce1,
char *useragent, char *preauth, char *by, char *code,
char *inet, tv_t *cd, bool igndup, K_TREE *trf_root,
bool addressuser)
{
ExecStatusType rescode;
bool conned = false;
PGresult *res;
K_TREE_CTX ctx[1];
K_ITEM *a_item, *u_item;
char cd_buf[DATE_BUFSIZ];
int n;
USERS *users;
AUTHS *row;
char *ins;
char *secuserid = NULL;
char *params[8 + HISTORYDATECOUNT];
int par;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(auths_free);
a_item = k_unlink_head(auths_free);
K_WUNLOCK(auths_free);
DATA_AUTHS(row, a_item);
K_RLOCK(users_free);
u_item = find_users(username);
K_RUNLOCK(users_free);
if (!u_item) {
if (addressuser) {
if (conn == NULL) {
conn = dbconnect();
conned = true;
}
u_item = users_add(conn, username, EMPTY, EMPTY,
by, code, inet, cd, trf_root);
}
if (!u_item)
goto unitem;
}
DATA_USERS(users, u_item);
STRNCPY(row->poolinstance, poolinstance);
row->userid = users->userid;
// since update=false, a dup will be ok and do nothing when igndup=true
new_worker(conn, false, row->userid, workername, DIFFICULTYDEFAULT_DEF_STR,
IDLENOTIFICATIONENABLED_DEF, IDLENOTIFICATIONTIME_DEF_STR,
by, code, inet, cd, trf_root);
STRNCPY(row->workername, workername);
TXT_TO_INT("clientid", clientid, row->clientid);
STRNCPY(row->enonce1, enonce1);
STRNCPY(row->useragent, useragent);
STRNCPY(row->preauth, preauth);
HISTORYDATEINIT(row, cd, by, code, inet);
HISTORYDATETRANSFER(trf_root, row);
K_WLOCK(auths_free);
if (find_in_ktree(auths_root, a_item, cmp_auths, ctx)) {
k_add_head(auths_free, a_item);
K_WUNLOCK(auths_free);
if (conned)
PQfinish(conn);
if (!igndup) {
tv_to_buf(cd, cd_buf, sizeof(cd_buf));
LOGERR("%s(): Duplicate auths ignored %s/%s/%s",
__func__, poolinstance, workername, cd_buf);
}
return users->secondaryuserid;
}
K_WUNLOCK(auths_free);
// Update even if DB fails
workerstatus_update(row, NULL, NULL);
if (conn == NULL) {
conn = dbconnect();
conned = true;
}
row->authid = nextid(conn, "authid", (int64_t)1, cd, by, code, inet);
if (row->authid == 0)
goto unitem;
par = 0;
params[par++] = bigint_to_buf(row->authid, NULL, 0);
params[par++] = str_to_buf(row->poolinstance, NULL, 0);
params[par++] = bigint_to_buf(row->userid, NULL, 0);
params[par++] = str_to_buf(row->workername, NULL, 0);
params[par++] = int_to_buf(row->clientid, NULL, 0);
params[par++] = str_to_buf(row->enonce1, NULL, 0);
params[par++] = str_to_buf(row->useragent, NULL, 0);
params[par++] = str_to_buf(row->preauth, NULL, 0);
HISTORYDATEPARAMS(params, par, row);
PARCHK(par, params);
ins = "insert into auths "
"(authid,poolinstance,userid,workername,clientid,enonce1,useragent,preauth"
HISTORYDATECONTROL ") values (" PQPARAM13 ")";
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
goto unparam;
}
secuserid = users->secondaryuserid;
unparam:
PQclear(res);
for (n = 0; n < par; n++)
free(params[n]);
unitem:
if (conned)
PQfinish(conn);
K_WLOCK(auths_free);
if (!secuserid)
k_add_head(auths_free, a_item);
else {
auths_root = add_to_ktree(auths_root, a_item, cmp_auths);
k_add_head(auths_store, a_item);
}
K_WUNLOCK(auths_free);
return secuserid;
}
static bool auths_fill(PGconn *conn)
{
ExecStatusType rescode;
PGresult *res;
K_ITEM *item;
int n, i;
AUTHS *row;
char *params[1];
int par;
char *field;
char *sel;
int fields = 7;
bool ok;
LOGDEBUG("%s(): select", __func__);
// TODO: add/update a (single) fake auth every ~10min or 10min after the last one?
sel = "select "
"authid,userid,workername,clientid,enonce1,useragent,preauth"
HISTORYDATECONTROL
" from auths where expirydate=$1";
par = 0;
params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0);
PARCHK(par, params);
res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_READ);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Select", rescode, conn);
PQclear(res);
return false;
}
n = PQnfields(res);
if (n != (fields + HISTORYDATECOUNT)) {
LOGERR("%s(): Invalid field count - should be %d, but is %d",
__func__, fields + HISTORYDATECOUNT, n);
PQclear(res);
return false;
}
n = PQntuples(res);
LOGDEBUG("%s(): tree build count %d", __func__, n);
ok = true;
K_WLOCK(auths_free);
for (i = 0; i < n; i++) {
item = k_unlink_head(auths_free);
DATA_AUTHS(row, item);
if (everyone_die) {
ok = false;
break;
}
PQ_GET_FLD(res, i, "authid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("authid", field, row->authid);
PQ_GET_FLD(res, i, "userid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("userid", field, row->userid);
PQ_GET_FLD(res, i, "workername", field, ok);
if (!ok)
break;
TXT_TO_STR("workername", field, row->workername);
PQ_GET_FLD(res, i, "clientid", field, ok);
if (!ok)
break;
TXT_TO_INT("clientid", field, row->clientid);
PQ_GET_FLD(res, i, "enonce1", field, ok);
if (!ok)
break;
TXT_TO_STR("enonce1", field, row->enonce1);
PQ_GET_FLD(res, i, "useragent", field, ok);
if (!ok)
break;
TXT_TO_STR("useragent", field, row->useragent);
PQ_GET_FLD(res, i, "preauth", field, ok);
if (!ok)
break;
TXT_TO_STR("preauth", field, row->preauth);
HISTORYDATEFLDS(res, i, row, ok);
if (!ok)
break;
auths_root = add_to_ktree(auths_root, item, cmp_auths);
k_add_head(auths_store, item);
workerstatus_update(row, NULL, NULL);
if (tv_newer(&(dbstatus.newest_createdate_auths), &(row->createdate)))
copy_tv(&(dbstatus.newest_createdate_auths), &(row->createdate));
tick();
}
if (!ok)
k_add_head(auths_free, item);
K_WUNLOCK(auths_free);
PQclear(res);
if (ok) {
LOGDEBUG("%s(): built", __func__);
LOGWARNING("%s(): loaded %d auth records", __func__, n);
}
return ok;
}
void auths_reload()
{
PGconn *conn = dbconnect();
K_WLOCK(auths_free);
auths_root = free_ktree(auths_root, NULL);
k_list_transfer_to_head(auths_store, auths_free);
K_WUNLOCK(auths_free);
auths_fill(conn);
PQfinish(conn);
}
// order by poolinstance asc,createdate asc
static cmp_t cmp_poolstats(K_ITEM *a, K_ITEM *b)
{
POOLSTATS *pa, *pb;
DATA_POOLSTATS(pa, a);
DATA_POOLSTATS(pb, b);
cmp_t c = CMP_STR(pa->poolinstance, pb->poolinstance);
if (c == 0)
c = CMP_TV(pa->createdate, pb->createdate);
return c;
}
static bool poolstats_add(PGconn *conn, bool store, char *poolinstance,
char *elapsed, char *users, char *workers,
char *hashrate, char *hashrate5m,
char *hashrate1hr, char *hashrate24hr,
char *by, char *code, char *inet, tv_t *cd,
bool igndup, K_TREE *trf_root)
{
ExecStatusType rescode;
bool conned = false;
PGresult *res;
K_TREE_CTX ctx[1];
K_ITEM *p_item;
int n;
POOLSTATS *row;
char *ins;
char *params[8 + SIMPLEDATECOUNT];
int par;
bool ok = false;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(poolstats_free);
p_item = k_unlink_head(poolstats_free);
K_WUNLOCK(poolstats_free);
DATA_POOLSTATS(row, p_item);
row->stored = false;
STRNCPY(row->poolinstance, poolinstance);
TXT_TO_BIGINT("elapsed", elapsed, row->elapsed);
TXT_TO_INT("users", users, row->users);
TXT_TO_INT("workers", workers, row->workers);
TXT_TO_DOUBLE("hashrate", hashrate, row->hashrate);
TXT_TO_DOUBLE("hashrate5m", hashrate5m, row->hashrate5m);
TXT_TO_DOUBLE("hashrate1hr", hashrate1hr, row->hashrate1hr);
TXT_TO_DOUBLE("hashrate24hr", hashrate24hr, row->hashrate24hr);
SIMPLEDATEINIT(row, cd, by, code, inet);
SIMPLEDATETRANSFER(trf_root, row);
if (igndup && find_in_ktree(poolstats_root, p_item, cmp_poolstats, ctx)) {
K_WLOCK(poolstats_free);
k_add_head(poolstats_free, p_item);
K_WUNLOCK(poolstats_free);
return true;
}
par = 0;
if (store) {
params[par++] = str_to_buf(row->poolinstance, NULL, 0);
params[par++] = bigint_to_buf(row->elapsed, NULL, 0);
params[par++] = int_to_buf(row->users, NULL, 0);
params[par++] = int_to_buf(row->workers, NULL, 0);
params[par++] = bigint_to_buf(row->hashrate, NULL, 0);
params[par++] = bigint_to_buf(row->hashrate5m, NULL, 0);
params[par++] = bigint_to_buf(row->hashrate1hr, NULL, 0);
params[par++] = bigint_to_buf(row->hashrate24hr, NULL, 0);
SIMPLEDATEPARAMS(params, par, row);
PARCHK(par, params);
ins = "insert into poolstats "
"(poolinstance,elapsed,users,workers,hashrate,"
"hashrate5m,hashrate1hr,hashrate24hr"
SIMPLEDATECONTROL ") values (" PQPARAM12 ")";
if (!conn) {
conn = dbconnect();
conned = true;
}
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
goto unparam;
}
row->stored = true;
}
ok = true;
unparam:
if (store) {
PQclear(res);
if (conned)
PQfinish(conn);
for (n = 0; n < par; n++)
free(params[n]);
}
K_WLOCK(poolstats_free);
if (!ok)
k_add_head(poolstats_free, p_item);
else {
poolstats_root = add_to_ktree(poolstats_root, p_item, cmp_poolstats);
k_add_head(poolstats_store, p_item);
}
K_WUNLOCK(poolstats_free);
return ok;
}
// TODO: data selection - only require ?
static bool poolstats_fill(PGconn *conn)
{
ExecStatusType rescode;
PGresult *res;
K_ITEM *item;
int n, i;
POOLSTATS *row;
char *field;
char *sel;
int fields = 8;
bool ok;
LOGDEBUG("%s(): select", __func__);
sel = "select "
"poolinstance,elapsed,users,workers,hashrate,hashrate5m,"
"hashrate1hr,hashrate24hr"
SIMPLEDATECONTROL
" from poolstats";
res = PQexec(conn, sel, CKPQ_READ);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Select", rescode, conn);
PQclear(res);
return false;
}
n = PQnfields(res);
if (n != (fields + SIMPLEDATECOUNT)) {
LOGERR("%s(): Invalid field count - should be %d, but is %d",
__func__, fields + SIMPLEDATECOUNT, n);
PQclear(res);
return false;
}
n = PQntuples(res);
LOGDEBUG("%s(): tree build count %d", __func__, n);
ok = true;
K_WLOCK(poolstats_free);
for (i = 0; i < n; i++) {
item = k_unlink_head(poolstats_free);
DATA_POOLSTATS(row, item);
if (everyone_die) {
ok = false;
break;
}
row->stored = true;
PQ_GET_FLD(res, i, "poolinstance", field, ok);
if (!ok)
break;
TXT_TO_STR("poolinstance", field, row->poolinstance);
PQ_GET_FLD(res, i, "elapsed", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("elapsed", field, row->elapsed);
PQ_GET_FLD(res, i, "users", field, ok);
if (!ok)
break;
TXT_TO_INT("users", field, row->users);
PQ_GET_FLD(res, i, "workers", field, ok);
if (!ok)
break;
TXT_TO_INT("workers", field, row->workers);
PQ_GET_FLD(res, i, "hashrate", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("hashrate", field, row->hashrate);
PQ_GET_FLD(res, i, "hashrate5m", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("hashrate5m", field, row->hashrate5m);
PQ_GET_FLD(res, i, "hashrate1hr", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("hashrate1hr", field, row->hashrate1hr);
PQ_GET_FLD(res, i, "hashrate24hr", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("hashrate24hr", field, row->hashrate24hr);
SIMPLEDATEFLDS(res, i, row, ok);
if (!ok)
break;
poolstats_root = add_to_ktree(poolstats_root, item, cmp_poolstats);
k_add_head(poolstats_store, item);
if (tv_newer(&(dbstatus.newest_createdate_poolstats), &(row->createdate)))
copy_tv(&(dbstatus.newest_createdate_poolstats), &(row->createdate));
tick();
}
if (!ok)
k_add_head(poolstats_free, item);
K_WUNLOCK(poolstats_free);
PQclear(res);
if (ok) {
LOGDEBUG("%s(): built", __func__);
LOGWARNING("%s(): loaded %d poolstats records", __func__, n);
}
return ok;
}
void poolstats_reload()
{
PGconn *conn = dbconnect();
K_WLOCK(poolstats_free);
poolstats_root = free_ktree(poolstats_root, NULL);
k_list_transfer_to_head(poolstats_store, poolstats_free);
K_WUNLOCK(poolstats_free);
poolstats_fill(conn);
PQfinish(conn);
}
static void dsp_userstats(K_ITEM *item, FILE *stream)
{
char statsdate_buf[DATE_BUFSIZ], createdate_buf[DATE_BUFSIZ];
USERSTATS *u = NULL;
if (!item)
fprintf(stream, "%s() called with (null) item\n", __func__);
else {
DATA_USERSTATS(u, item);
tv_to_buf(&(u->statsdate), statsdate_buf, sizeof(statsdate_buf));
tv_to_buf(&(u->createdate), createdate_buf, sizeof(createdate_buf));
fprintf(stream, " pi='%s' uid=%"PRId64" w='%s' e=%"PRId64" Hs=%f "
"Hs5m=%f Hs1hr=%f Hs24hr=%f sl=%s sc=%d sd=%s cd=%s\n",
u->poolinstance, u->userid, u->workername,
u->elapsed, u->hashrate, u->hashrate5m,
u->hashrate1hr, u->hashrate24hr, u->summarylevel,
u->summarycount, statsdate_buf, createdate_buf);
}
}
/* order by userid asc,statsdate asc,poolinstance asc,workername asc
as per required for userstats homepage summarisation */
static cmp_t cmp_userstats(K_ITEM *a, K_ITEM *b)
{
USERSTATS *ua, *ub;
DATA_USERSTATS(ua, a);
DATA_USERSTATS(ub, b);
cmp_t c = CMP_BIGINT(ua->userid, ub->userid);
if (c == 0) {
c = CMP_TV(ua->statsdate, ub->statsdate);
if (c == 0) {
c = CMP_STR(ua->poolinstance, ub->poolinstance);
if (c == 0)
c = CMP_STR(ua->workername, ub->workername);
}
}
return c;
}
/* order by userid asc,workername asc
temporary tree for summing userstats when sending user homepage info */
static cmp_t cmp_userstats_workername(K_ITEM *a, K_ITEM *b)
{
USERSTATS *ua, *ub;
DATA_USERSTATS(ua, a);
DATA_USERSTATS(ub, b);
cmp_t c = CMP_BIGINT(ua->userid, ub->userid);
if (c == 0)
c = CMP_STR(ua->workername, ub->workername);
return c;
}
/* order by statsdate,userid asc,statsdate asc,workername asc,poolinstance asc
as per required for DB summarisation */
static cmp_t cmp_userstats_statsdate(K_ITEM *a, K_ITEM *b)
{
USERSTATS *ua, *ub;
DATA_USERSTATS(ua, a);
DATA_USERSTATS(ub, b);
cmp_t c = CMP_TV(ua->statsdate, ub->statsdate);
if (c == 0) {
c = CMP_BIGINT(ua->userid, ub->userid);
if (c == 0) {
c = CMP_STR(ua->workername, ub->workername);
if (c == 0)
c = CMP_STR(ua->poolinstance, ub->poolinstance);
}
}
return c;
}
/* order by userid asc,workername asc,statsdate asc,poolinstance asc
built during data load to update workerstatus at the end of the load
and used during reload to discard stats already in the DB */
static cmp_t cmp_userstats_workerstatus(K_ITEM *a, K_ITEM *b)
{
USERSTATS *ua, *ub;
DATA_USERSTATS(ua, a);
DATA_USERSTATS(ub, b);
cmp_t c = CMP_BIGINT(ua->userid, ub->userid);
if (c == 0) {
c = CMP_STR(ua->workername, ub->workername);
if (c == 0) {
c = CMP_TV(ua->statsdate, ub->statsdate);
if (c == 0)
c = CMP_STR(ua->poolinstance, ub->poolinstance);
}
}
return c;
}
static bool userstats_add_db(PGconn *conn, USERSTATS *row)
{
ExecStatusType rescode;
bool conned = false;
PGresult *res;
char *ins;
bool ok = false;
char *params[10 + SIMPLEDATECOUNT];
int par;
int n;
LOGDEBUG("%s(): store", __func__);
par = 0;
params[par++] = bigint_to_buf(row->userid, NULL, 0);
params[par++] = str_to_buf(row->workername, NULL, 0);
params[par++] = bigint_to_buf(row->elapsed, NULL, 0);
params[par++] = double_to_buf(row->hashrate, NULL, 0);
params[par++] = double_to_buf(row->hashrate5m, NULL, 0);
params[par++] = double_to_buf(row->hashrate1hr, NULL, 0);
params[par++] = double_to_buf(row->hashrate24hr, NULL, 0);
params[par++] = str_to_buf(row->summarylevel, NULL, 0);
params[par++] = int_to_buf(row->summarycount, NULL, 0);
params[par++] = tv_to_buf(&(row->statsdate), NULL, 0);
SIMPLEDATEPARAMS(params, par, row);
PARCHK(par, params);
ins = "insert into userstats "
"(userid,workername,elapsed,hashrate,hashrate5m,hashrate1hr,"
"hashrate24hr,summarylevel,summarycount,statsdate"
SIMPLEDATECONTROL ") values (" PQPARAM14 ")";
if (conn == NULL) {
conn = dbconnect();
conned = true;
}
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
goto unparam;
}
ok = true;
unparam:
PQclear(res);
if (conned)
PQfinish(conn);
for (n = 0; n < par; n++)
free(params[n]);
return ok;
}
static bool userstats_add(char *poolinstance, char *elapsed, char *username,
char *workername, char *hashrate, char *hashrate5m,
char *hashrate1hr, char *hashrate24hr, bool idle,
bool eos, char *by, char *code, char *inet, tv_t *cd,
K_TREE *trf_root)
{
K_ITEM *us_item, *u_item, *us_match, *us_next, look;
tv_t eosdate;
USERSTATS *row, cmp, *match, *next;
USERS *users;
K_TREE_CTX ctx[1];
LOGDEBUG("%s(): add", __func__);
K_WLOCK(userstats_free);
us_item = k_unlink_head(userstats_free);
K_WUNLOCK(userstats_free);
DATA_USERSTATS(row, us_item);
STRNCPY(row->poolinstance, poolinstance);
TXT_TO_BIGINT("elapsed", elapsed, row->elapsed);
K_RLOCK(users_free);
u_item = find_users(username);
K_RUNLOCK(users_free);
if (!u_item)
return false;
DATA_USERS(users, u_item);
row->userid = users->userid;
TXT_TO_STR("workername", workername, row->workername);
TXT_TO_DOUBLE("hashrate", hashrate, row->hashrate);
TXT_TO_DOUBLE("hashrate5m", hashrate5m, row->hashrate5m);
TXT_TO_DOUBLE("hashrate1hr", hashrate1hr, row->hashrate1hr);
TXT_TO_DOUBLE("hashrate24hr", hashrate24hr, row->hashrate24hr);
row->idle = idle;
row->summarylevel[0] = SUMMARY_NONE;
row->summarylevel[1] = '\0';
row->summarycount = 1;
SIMPLEDATEINIT(row, cd, by, code, inet);
SIMPLEDATETRANSFER(trf_root, row);
copy_tv(&(row->statsdate), &(row->createdate));
if (eos) {
// Save it for end processing
eosdate.tv_sec = row->createdate.tv_sec;
eosdate.tv_usec = row->createdate.tv_usec;
}
// confirm_summaries() doesn't call this
if (reloading) {
memcpy(&cmp, row, sizeof(cmp));
INIT_USERSTATS(&look);
look.data = (void *)(&cmp);
// Just zero it to ensure the DB record is after it, not equal to it
cmp.statsdate.tv_usec = 0;
/* If there is a matching user+worker DB record summarising this row,
* or a matching user+worker DB record next after this row, discard it */
us_match = find_after_in_ktree(userstats_workerstatus_root, &look,
cmp_userstats_workerstatus, ctx);
DATA_USERSTATS_NULL(match, us_match);
if (us_match &&
match->userid == row->userid &&
strcmp(match->workername, row->workername) == 0 &&
match->summarylevel[0] != SUMMARY_NONE) {
K_WLOCK(userstats_free);
k_add_head(userstats_free, us_item);
K_WUNLOCK(userstats_free);
/* If this was an eos record and eos_store has data,
* it means we need to process the eos_store */
if (eos && userstats_eos_store->count > 0)
goto advancetogo;
return true;
}
}
workerstatus_update(NULL, NULL, row);
/* group at full key: userid,createdate,poolinstance,workername
i.e. ignore instance and group together down at workername */
us_match = userstats_eos_store->head;
while (us_match && cmp_userstats(us_item, us_match) != 0.0)
us_match = us_match->next;
if (us_match) {
DATA_USERSTATS(match, us_match);
match->hashrate += row->hashrate;
match->hashrate5m += row->hashrate5m;
match->hashrate1hr += row->hashrate1hr;
match->hashrate24hr += row->hashrate24hr;
// Minimum elapsed of the data set
if (match->elapsed > row->elapsed)
match->elapsed = row->elapsed;
// Unused
K_WLOCK(userstats_free);
k_add_head(userstats_free, us_item);
K_WUNLOCK(userstats_free);
} else {
// New worker
K_WLOCK(userstats_free);
k_add_head(userstats_eos_store, us_item);
K_WUNLOCK(userstats_free);
}
if (eos) {
advancetogo:
K_WLOCK(userstats_free);
us_next = userstats_eos_store->head;
while (us_next) {
DATA_USERSTATS(next, us_next);
if (tvdiff(&(next->createdate), &eosdate) != 0.0) {
char date_buf[DATE_BUFSIZ];
LOGERR("userstats != eos '%s' discarded: %s/%"PRId64"/%s",
tv_to_buf(&eosdate, date_buf, DATE_BUFSIZ),
next->poolinstance,
next->userid,
next->workername);
us_next = us_next->next;
} else {
us_match = us_next;
us_next = us_match->next;
k_unlink_item(userstats_eos_store, us_match);
userstats_root = add_to_ktree(userstats_root, us_match,
cmp_userstats);
userstats_statsdate_root = add_to_ktree(userstats_statsdate_root, us_match,
cmp_userstats_statsdate);
if (!startup_complete) {
userstats_workerstatus_root = add_to_ktree(userstats_workerstatus_root, us_match,
cmp_userstats_workerstatus);
}
k_add_head(userstats_store, us_match);
}
}
// Discard them
if (userstats_eos_store->count > 0)
k_list_transfer_to_head(userstats_eos_store, userstats_free);
K_WUNLOCK(userstats_free);
}
return true;
}
static bool userstats_starttimeband(USERSTATS *row, tv_t *statsdate)
{
char buf[DATE_BUFSIZ+1];
copy_tv(statsdate, &(row->statsdate));
// Start of this timeband
switch (row->summarylevel[0]) {
case SUMMARY_DB:
statsdate->tv_sec -= statsdate->tv_sec % USERSTATS_DB_S;
statsdate->tv_usec = 0;
break;
case SUMMARY_FULL:
statsdate->tv_sec -= statsdate->tv_sec % USERSTATS_DB_DS;
statsdate->tv_usec = 0;
break;
default:
tv_to_buf(statsdate, buf, sizeof(buf));
// Bad userstats are not fatal
LOGERR("Unknown userstats summarylevel 0x%02x '%c' "
"userid %"PRId64" workername %s statsdate %s",
row->summarylevel[0], row->summarylevel[0],
row->userid, row->workername, buf);
return false;
}
return true;
}
// TODO: data selection - only require ?
static bool userstats_fill(PGconn *conn)
{
ExecStatusType rescode;
PGresult *res;
K_ITEM *item;
int n, i;
USERSTATS *row;
tv_t statsdate;
char *field;
char *sel;
int fields = 10;
bool ok;
LOGDEBUG("%s(): select", __func__);
sel = "select "
"userid,workername,elapsed,hashrate,hashrate5m,hashrate1hr,"
"hashrate24hr,summarylevel,summarycount,statsdate"
SIMPLEDATECONTROL
" from userstats";
res = PQexec(conn, sel, CKPQ_READ);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Select", rescode, conn);
PQclear(res);
return false;
}
n = PQnfields(res);
if (n != (fields + SIMPLEDATECOUNT)) {
LOGERR("%s(): Invalid field count - should be %d, but is %d",
__func__, fields + SIMPLEDATECOUNT, n);
PQclear(res);
return false;
}
n = PQntuples(res);
LOGDEBUG("%s(): tree build count %d", __func__, n);
ok = true;
K_WLOCK(userstats_free);
for (i = 0; i < n; i++) {
item = k_unlink_head(userstats_free);
DATA_USERSTATS(row, item);
if (everyone_die) {
ok = false;
break;
}
// Not a DB field
row->poolinstance[0] = '\0';
PQ_GET_FLD(res, i, "userid", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("userid", field, row->userid);
PQ_GET_FLD(res, i, "workername", field, ok);
if (!ok)
break;
TXT_TO_STR("workername", field, row->workername);
PQ_GET_FLD(res, i, "elapsed", field, ok);
if (!ok)
break;
TXT_TO_BIGINT("elapsed", field, row->elapsed);
PQ_GET_FLD(res, i, "hashrate", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("hashrate", field, row->hashrate);
PQ_GET_FLD(res, i, "hashrate5m", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("hashrate5m", field, row->hashrate5m);
PQ_GET_FLD(res, i, "hashrate1hr", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("hashrate1hr", field, row->hashrate1hr);
PQ_GET_FLD(res, i, "hashrate24hr", field, ok);
if (!ok)
break;
TXT_TO_DOUBLE("hashrate24hr", field, row->hashrate24hr);
PQ_GET_FLD(res, i, "summarylevel", field, ok);
if (!ok)
break;
TXT_TO_STR("summarylevel", field, row->summarylevel);
PQ_GET_FLD(res, i, "summarycount", field, ok);
if (!ok)
break;
TXT_TO_INT("summarycount", field, row->summarycount);
PQ_GET_FLD(res, i, "statsdate", field, ok);
if (!ok)
break;
TXT_TO_TV("statsdate", field, row->statsdate);
// From DB - 1hr means it must have been idle > 10m
if (row->hashrate5m == 0.0 && row->hashrate1hr == 0.0)
row->idle = true;
else
row->idle = false;
SIMPLEDATEFLDS(res, i, row, ok);
if (!ok)
break;
userstats_root = add_to_ktree(userstats_root, item, cmp_userstats);
userstats_statsdate_root = add_to_ktree(userstats_statsdate_root, item,
cmp_userstats_statsdate);
userstats_workerstatus_root = add_to_ktree(userstats_workerstatus_root, item,
cmp_userstats_workerstatus);
k_add_head(userstats_store, item);
workerstatus_update(NULL, NULL, row);
if (userstats_starttimeband(row, &statsdate)) {
if (tv_newer(&(dbstatus.newest_starttimeband_userstats), &statsdate))
copy_tv(&(dbstatus.newest_starttimeband_userstats), &statsdate);
}
tick();
}
if (!ok)
k_add_head(userstats_free, item);
K_WUNLOCK(userstats_free);
PQclear(res);
if (ok) {
LOGDEBUG("%s(): built", __func__);
LOGWARNING("%s(): loaded %d userstats records", __func__, n);
}
return ok;
}
void userstats_reload()
{
PGconn *conn = dbconnect();
K_WLOCK(userstats_free);
userstats_root = free_ktree(userstats_root, NULL);
userstats_statsdate_root = free_ktree(userstats_statsdate_root, NULL);
k_list_transfer_to_head(userstats_store, userstats_free);
K_WUNLOCK(userstats_free);
userstats_fill(conn);
PQfinish(conn);
}
static bool check_db_version(PGconn *conn)
{
ExecStatusType rescode;
PGresult *res;
char *field;
char *sel;
int fields = 2;
bool ok;
int n;
LOGDEBUG("%s(): select", __func__);
sel = "select * from version;";
res = PQexec(conn, sel, CKPQ_READ);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGEMERG("Select", rescode, conn);
PQclear(res);
return false;
}
n = PQnfields(res);
if (n != fields) {
LOGEMERG("%s(): Invalid field count - should be %d, but is %d",
__func__, fields, n);
PQclear(res);
return false;
}
n = PQntuples(res);
if (n != 1) {
LOGEMERG("%s(): Invalid record count - should be %d, but is %d",
__func__, 1, n);
PQclear(res);
return false;
}
ok = true;
PQ_GET_FLD(res, 0, "vlock", field, ok);
if (!ok) {
LOGEMERG("%s(): Missing field vlock", __func__);
PQclear(res);
return false;
}
if (strcmp(field, DB_VLOCK)) {
LOGEMERG("%s(): incorrect vlock '%s' - should be '%s'",
__func__, field, DB_VLOCK);
PQclear(res);
return false;
}
ok = true;
PQ_GET_FLD(res, 0, "version", field, ok);
if (!ok) {
LOGEMERG("%s(): Missing field version", __func__);
PQclear(res);
return false;
}
if (strcmp(field, DB_VERSION)) {
LOGEMERG("%s(): incorrect version '%s' - should be '%s'",
__func__, field, DB_VERSION);
PQclear(res);
return false;
}
PQclear(res);
LOGWARNING("%s(): DB version (%s) correct (CKDB V%s)",
__func__, DB_VERSION, CKDB_VERSION);
return true;
}
/* Load tables required to support auths,adduser,chkpass and newid
* N.B. idcontrol is DB internal so is always ready
*/
static bool getdata1()
{
PGconn *conn = dbconnect();
bool ok = true;
if (!(ok = check_db_version(conn)))
goto matane;
if (!(ok = users_fill(conn)))
goto matane;
if (!(ok = workers_fill(conn)))
goto matane;
if (!confirm_sharesummary)
ok = auths_fill(conn);
matane:
PQfinish(conn);
return ok;
}
static bool getdata2()
{
PGconn *conn = dbconnect();
bool ok = true;
if (!(ok = blocks_fill(conn)) || everyone_die)
goto sukamudai;
if (!confirm_sharesummary) {
if (!(ok = paymentaddresses_fill(conn)) || everyone_die)
goto sukamudai;
if (!(ok = payments_fill(conn)) || everyone_die)
goto sukamudai;
}
if (!(ok = workinfo_fill(conn)) || everyone_die)
goto sukamudai;
if (!(ok = shares_fill()) || everyone_die)
goto sukamudai;
if (!(ok = shareerrors_fill()) || everyone_die)
goto sukamudai;
if (!(ok = sharesummary_fill(conn)) || everyone_die)
goto sukamudai;
if (!confirm_sharesummary) {
if (!(ok = useratts_fill(conn)) || everyone_die)
goto sukamudai;
if (!(ok = poolstats_fill(conn)) || everyone_die)
goto sukamudai;
ok = userstats_fill(conn);
}
sukamudai:
PQfinish(conn);
return ok;
}
static bool reload_from(tv_t *start);
static bool reload()
{
char buf[DATE_BUFSIZ+1];
char *filename;
tv_t start;
char *reason;
FILE *fp;
tv_to_buf(&(dbstatus.oldest_sharesummary_firstshare_n), buf, sizeof(buf));
LOGWARNING("%s(): %s oldest DB incomplete sharesummary", __func__, buf);
tv_to_buf(&(dbstatus.newest_sharesummary_firstshare_ay), buf, sizeof(buf));
LOGWARNING("%s(): %s newest DB complete sharesummary", __func__, buf);
tv_to_buf(&(dbstatus.newest_createdate_workinfo), buf, sizeof(buf));
LOGWARNING("%s(): %s newest DB workinfo", __func__, buf);
tv_to_buf(&(dbstatus.newest_createdate_auths), buf, sizeof(buf));
LOGWARNING("%s(): %s newest DB auths", __func__, buf);
tv_to_buf(&(dbstatus.newest_createdate_poolstats), buf, sizeof(buf));
LOGWARNING("%s(): %s newest DB poolstats", __func__, buf);
tv_to_buf(&(dbstatus.newest_starttimeband_userstats), buf, sizeof(buf));
LOGWARNING("%s(): %s newest DB userstats start timeband", __func__, buf);
tv_to_buf(&(dbstatus.newest_createdate_blocks), buf, sizeof(buf));
LOGWARNING("%s(): %s newest DB blocks (ignored)", __func__, buf);
if (dbstatus.oldest_sharesummary_firstshare_n.tv_sec)
copy_tv(&(dbstatus.sharesummary_firstshare), &(dbstatus.oldest_sharesummary_firstshare_n));
else
copy_tv(&(dbstatus.sharesummary_firstshare), &(dbstatus.newest_sharesummary_firstshare_ay));
copy_tv(&start, &(dbstatus.sharesummary_firstshare));
reason = "sharesummary";
if (!tv_newer(&start, &(dbstatus.newest_createdate_workinfo))) {
copy_tv(&start, &(dbstatus.newest_createdate_workinfo));
reason = "workinfo";
}
if (!tv_newer(&start, &(dbstatus.newest_createdate_auths))) {
copy_tv(&start, &(dbstatus.newest_createdate_auths));
reason = "auths";
}
if (!tv_newer(&start, &(dbstatus.newest_createdate_poolstats))) {
copy_tv(&start, &(dbstatus.newest_createdate_poolstats));
reason = "poolstats";
}
if (!tv_newer(&start, &(dbstatus.newest_starttimeband_userstats))) {
copy_tv(&start, &(dbstatus.newest_starttimeband_userstats));
reason = "userstats";
}
tv_to_buf(&start, buf, sizeof(buf));
LOGWARNING("%s() restart timestamp %s for %s", __func__, buf, reason);
if (start.tv_sec < DATE_BEGIN) {
start.tv_sec = DATE_BEGIN;
start.tv_usec = 0L;
filename = rotating_filename(restorefrom, start.tv_sec);
fp = fopen(filename, "re");
if (fp)
fclose(fp);
else {
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
int fd = open(filename, O_CREAT|O_RDONLY, mode);
if (fd == -1) {
int ern = errno;
quithere(1, "Couldn't create '%s' (%d) %s",
filename, ern, strerror(ern));
}
close(fd);
}
free(filename);
}
return reload_from(&start);
}
/* TODO:
static PGconn *dbquit(PGconn *conn)
{
if (conn != NULL)
PQfinish(conn);
return NULL;
}
*/
/* Open the file in path, check if there is a pid in there that still exists
* and if not, write the pid into that file. */
static bool write_pid(ckpool_t *ckp, const char *path, pid_t pid)
{
struct stat statbuf;
FILE *fp;
int ret;
if (!stat(path, &statbuf)) {
int oldpid;
LOGWARNING("File %s exists", path);
fp = fopen(path, "re");
if (!fp) {
LOGEMERG("Failed to open file %s", path);
return false;
}
ret = fscanf(fp, "%d", &oldpid);
fclose(fp);
if (ret == 1 && !(kill(oldpid, 0))) {
if (!ckp->killold) {
LOGEMERG("Process %s pid %d still exists, start ckpool with -k if you wish to kill it",
path, oldpid);
return false;
}
if (kill(oldpid, 9)) {
LOGEMERG("Unable to kill old process %s pid %d", path, oldpid);
return false;
}
LOGWARNING("Killing off old process %s pid %d", path, oldpid);
}
}
fp = fopen(path, "we");
if (!fp) {
LOGERR("Failed to open file %s", path);
return false;
}
fprintf(fp, "%d", pid);
fclose(fp);
return true;
}
static void create_process_unixsock(proc_instance_t *pi)
{
unixsock_t *us = &pi->us;
us->path = strdup(pi->ckp->socket_dir);
realloc_strcat(&us->path, pi->sockname);
LOGDEBUG("Opening %s", us->path);
us->sockd = open_unix_server(us->path);
if (unlikely(us->sockd < 0))
quit(1, "Failed to open %s socket", pi->sockname);
}
static void write_namepid(proc_instance_t *pi)
{
char s[256];
pi->pid = getpid();
sprintf(s, "%s%s.pid", pi->ckp->socket_dir, pi->processname);
if (!write_pid(pi->ckp, s, pi->pid))
quit(1, "Failed to write %s pid %d", pi->processname, pi->pid);
}
static void rm_namepid(proc_instance_t *pi)
{
char s[256];
sprintf(s, "%s%s.pid", pi->ckp->socket_dir, pi->processname);
unlink(s);
}
static void clean_up(ckpool_t *ckp)
{
rm_namepid(&ckp->main);
dealloc(ckp->socket_dir);
fclose(ckp->logfp);
}
static void alloc_storage()
{
workqueue_free = k_new_list("WorkQueue", sizeof(WORKQUEUE),
ALLOC_WORKQUEUE, LIMIT_WORKQUEUE, true);
workqueue_store = k_new_store(workqueue_free);
transfer_free = k_new_list(Transfer, sizeof(TRANSFER),
ALLOC_TRANSFER, LIMIT_TRANSFER, true);
transfer_free->dsp_func = dsp_transfer;
users_free = k_new_list("Users", sizeof(USERS),
ALLOC_USERS, LIMIT_USERS, true);
users_store = k_new_store(users_free);
users_root = new_ktree();
userid_root = new_ktree();
useratts_free = k_new_list("Useratts", sizeof(USERATTS),
ALLOC_USERATTS, LIMIT_USERATTS, true);
useratts_store = k_new_store(useratts_free);
useratts_root = new_ktree();
workers_free = k_new_list("Workers", sizeof(WORKERS),
ALLOC_WORKERS, LIMIT_WORKERS, true);
workers_store = k_new_store(workers_free);
workers_root = new_ktree();
paymentaddresses_free = k_new_list("PaymentAddresses",
sizeof(PAYMENTADDRESSES),
ALLOC_PAYMENTADDRESSES,
LIMIT_PAYMENTADDRESSES, true);
paymentaddresses_store = k_new_store(paymentaddresses_free);
paymentaddresses_root = new_ktree();
payments_free = k_new_list("Payments", sizeof(PAYMENTS),
ALLOC_PAYMENTS, LIMIT_PAYMENTS, true);
payments_store = k_new_store(payments_free);
payments_root = new_ktree();
idcontrol_free = k_new_list("IDControl", sizeof(IDCONTROL),
ALLOC_IDCONTROL, LIMIT_IDCONTROL, true);
idcontrol_store = k_new_store(idcontrol_free);
workinfo_free = k_new_list("WorkInfo", sizeof(WORKINFO),
ALLOC_WORKINFO, LIMIT_WORKINFO, true);
workinfo_store = k_new_store(workinfo_free);
workinfo_root = new_ktree();
if (!confirm_sharesummary)
workinfo_height_root = new_ktree();
shares_free = k_new_list("Shares", sizeof(SHARES),
ALLOC_SHARES, LIMIT_SHARES, true);
shares_store = k_new_store(shares_free);
shares_root = new_ktree();
shareerrors_free = k_new_list("ShareErrors", sizeof(SHAREERRORS),
ALLOC_SHAREERRORS, LIMIT_SHAREERRORS, true);
shareerrors_store = k_new_store(shareerrors_free);
shareerrors_root = new_ktree();
sharesummary_free = k_new_list("ShareSummary", sizeof(SHARESUMMARY),
ALLOC_SHARESUMMARY, LIMIT_SHARESUMMARY, true);
sharesummary_store = k_new_store(sharesummary_free);
sharesummary_root = new_ktree();
sharesummary_workinfoid_root = new_ktree();
sharesummary_free->dsp_func = dsp_sharesummary;
blocks_free = k_new_list("Blocks", sizeof(BLOCKS),
ALLOC_BLOCKS, LIMIT_BLOCKS, true);
blocks_store = k_new_store(blocks_free);
blocks_root = new_ktree();
blocks_free->dsp_func = dsp_blocks;
miningpayouts_free = k_new_list("MiningPayouts", sizeof(MININGPAYOUTS),
ALLOC_MININGPAYOUTS, LIMIT_MININGPAYOUTS, true);
miningpayouts_store = k_new_store(miningpayouts_free);
miningpayouts_root = new_ktree();
auths_free = k_new_list("Auths", sizeof(AUTHS),
ALLOC_AUTHS, LIMIT_AUTHS, true);
auths_store = k_new_store(auths_free);
auths_root = new_ktree();
poolstats_free = k_new_list("PoolStats", sizeof(POOLSTATS),
ALLOC_POOLSTATS, LIMIT_POOLSTATS, true);
poolstats_store = k_new_store(poolstats_free);
poolstats_root = new_ktree();
userstats_free = k_new_list("UserStats", sizeof(USERSTATS),
ALLOC_USERSTATS, LIMIT_USERSTATS, true);
userstats_store = k_new_store(userstats_free);
userstats_eos_store = k_new_store(userstats_free);
userstats_summ = k_new_store(userstats_free);
userstats_root = new_ktree();
userstats_statsdate_root = new_ktree();
userstats_workerstatus_root = new_ktree();
userstats_free->dsp_func = dsp_userstats;
workerstatus_free = k_new_list("WorkerStatus", sizeof(WORKERSTATUS),
ALLOC_WORKERSTATUS, LIMIT_WORKERSTATUS, true);
workerstatus_store = k_new_store(workerstatus_free);
workerstatus_root = new_ktree();
}
static bool setup_data()
{
K_TREE_CTX ctx[1];
K_ITEM look, *found;
WORKINFO wi, *wic, *wif;
cklock_init(&fpm_lock);
cksem_init(&socketer_sem);
mutex_init(&wq_waitlock);
cond_init(&wq_waitcond);
alloc_storage();
if (!getdata1() || everyone_die)
return false;
db_auths_complete = true;
cksem_post(&socketer_sem);
if (!getdata2() || everyone_die)
return false;
db_load_complete = true;
if (!reload() || everyone_die)
return false;
set_block_share_counters();
if (everyone_die)
return false;
workerstatus_ready();
userstats_workerstatus_root = free_ktree(userstats_workerstatus_root, NULL);
workinfo_current = last_in_ktree(workinfo_height_root, ctx);
if (workinfo_current) {
DATA_WORKINFO(wic, workinfo_current);
STRNCPY(wi.coinbase1, wic->coinbase1);
wi.createdate.tv_sec = 0L;
wi.createdate.tv_usec = 0L;
INIT_WORKINFO(&look);
look.data = (void *)(&wi);
// Find the first workinfo for this height
found = find_after_in_ktree(workinfo_height_root, &look, cmp_workinfo_height, ctx);
if (found) {
DATA_WORKINFO(wif, found);
copy_tv(&last_bc, &(wif->createdate));
}
// No longer needed
workinfo_height_root = free_ktree(workinfo_height_root, NULL);
}
return true;
}
static char *cmd_adduser(PGconn *conn, char *cmd, char *id, tv_t *now, char *by,
char *code, char *inet, __maybe_unused tv_t *notcd,
K_TREE *trf_root)
{
char reply[1024] = "";
size_t siz = sizeof(reply);
K_ITEM *i_username, *i_emailaddress, *i_passwordhash, *u_item;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz);
if (!i_username)
return strdup(reply);
i_emailaddress = require_name(trf_root, "emailaddress", 7, (char *)mailpatt, reply, siz);
if (!i_emailaddress)
return strdup(reply);
i_passwordhash = require_name(trf_root, "passwordhash", 64, (char *)hashpatt, reply, siz);
if (!i_passwordhash)
return strdup(reply);
u_item = users_add(conn, transfer_data(i_username),
transfer_data(i_emailaddress),
transfer_data(i_passwordhash),
by, code, inet, now, trf_root);
if (!u_item) {
LOGERR("%s() %s.failed.DBE", __func__, id);
return strdup("failed.DBE");
}
LOGDEBUG("%s.ok.added %s", id, transfer_data(i_username));
snprintf(reply, siz, "ok.added %s", transfer_data(i_username));
return strdup(reply);
}
static char *cmd_newpass(__maybe_unused PGconn *conn, char *cmd, char *id,
tv_t *now, char *by, char *code, char *inet,
__maybe_unused tv_t *cd, K_TREE *trf_root)
{
K_ITEM *i_username, *i_oldhash, *i_newhash, *u_item;
char reply[1024] = "";
size_t siz = sizeof(reply);
bool ok = false;
char *oldhash;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz);
if (!i_username)
return strdup(reply);
i_oldhash = optional_name(trf_root, "oldhash", 64, (char *)hashpatt);
if (i_oldhash)
oldhash = transfer_data(i_oldhash);
else
oldhash = EMPTY;
i_newhash = require_name(trf_root, "newhash", 64, (char *)hashpatt, reply, siz);
if (!i_newhash)
return strdup(reply);
K_RLOCK(users_free);
u_item = find_users(transfer_data(i_username));
K_RUNLOCK(users_free);
if (u_item) {
ok = users_pass_email(NULL, u_item,
oldhash,
transfer_data(i_newhash),
NULL,
by, code, inet, now, trf_root);
}
if (!ok) {
LOGERR("%s.failed.%s", id, transfer_data(i_username));
return strdup("failed.");
}
LOGDEBUG("%s.ok.%s", id, transfer_data(i_username));
return strdup("ok.");
}
static char *cmd_chkpass(__maybe_unused PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *now, __maybe_unused char *by,
__maybe_unused char *code, __maybe_unused char *inet,
__maybe_unused tv_t *notcd, K_TREE *trf_root)
{
K_ITEM *i_username, *i_passwordhash, *u_item;
char reply[1024] = "";
size_t siz = sizeof(reply);
USERS *users;
bool ok;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz);
if (!i_username)
return strdup(reply);
i_passwordhash = require_name(trf_root, "passwordhash", 64, (char *)hashpatt, reply, siz);
if (!i_passwordhash)
return strdup(reply);
K_RLOCK(users_free);
u_item = find_users(transfer_data(i_username));
K_RUNLOCK(users_free);
if (!u_item)
ok = false;
else {
DATA_USERS(users, u_item);
ok = check_hash(users, transfer_data(i_passwordhash));
}
if (!ok) {
LOGERR("%s.failed.%s", id, transfer_data(i_username));
return strdup("failed.");
}
LOGDEBUG("%s.ok.%s", id, transfer_data(i_username));
return strdup("ok.");
}
static char *cmd_userset(PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *now, __maybe_unused char *by,
__maybe_unused char *code, __maybe_unused char *inet,
__maybe_unused tv_t *notcd, K_TREE *trf_root)
{
K_ITEM *i_username, *i_passwordhash, *i_address, *i_email, *u_item, *pa_item;
char *email, *address;
char reply[1024] = "";
size_t siz = sizeof(reply);
char tmp[1024];
PAYMENTADDRESSES *paymentaddresses;
USERS *users;
char *reason = NULL;
char *answer = NULL;
size_t len, off;
bool ok;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz);
if (!i_username) {
// For web this message is detailed enough
reason = "System error";
goto struckout;
}
K_RLOCK(users_free);
u_item = find_users(transfer_data(i_username));
K_RUNLOCK(users_free);
if (!u_item) {
reason = "Unknown user";
goto struckout;
} else {
DATA_USERS(users, u_item);
i_passwordhash = optional_name(trf_root, "passwordhash",
64, (char *)hashpatt);
if (!i_passwordhash) {
APPEND_REALLOC_INIT(answer, off, len);
snprintf(tmp, sizeof(tmp), "email=%s%c",
users->emailaddress, FLDSEP);
APPEND_REALLOC(answer, off, len, tmp);
K_RLOCK(paymentaddresses_free);
pa_item = find_paymentaddresses(users->userid);
K_RUNLOCK(paymentaddresses_free);
if (pa_item) {
DATA_PAYMENTADDRESSES(paymentaddresses, pa_item);
snprintf(tmp, sizeof(tmp), "addr=%s",
paymentaddresses->payaddress);
APPEND_REALLOC(answer, off, len, tmp);
} else {
snprintf(tmp, sizeof(tmp), "addr=");
APPEND_REALLOC(answer, off, len, tmp);
}
} else {
if (!check_hash(users, transfer_data(i_passwordhash))) {
reason = "Incorrect password";
goto struckout;
}
i_email = optional_name(trf_root, "email", 1, (char *)mailpatt);
if (i_email)
email = transfer_data(i_email);
else
email = NULL;
i_address = optional_name(trf_root, "address", 1, NULL);
if (i_address)
address = transfer_data(i_address);
else
address = NULL;
if ((email == NULL || *email == '\0') &&
(address == NULL || *address == '\0')) {
reason = "Missing/Invalid value";
goto struckout;
}
// if (address && *address)
// TODO: validate it
if (email && *email) {
ok = users_pass_email(conn, u_item, NULL,
NULL, email,
by, code, inet,
now, trf_root);
if (!ok) {
reason = "email error";
goto struckout;
}
}
if (address && *address) {
ok = paymentaddresses_set(conn, users->userid,
address, by,
code, inet,
now, trf_root);
if (!ok) {
reason = "address error";
goto struckout;
}
}
answer = strdup("updated");
}
}
struckout:
if (reason) {
snprintf(reply, siz, "ERR.%s", reason);
LOGERR("%s.%s.%s", cmd, id, reply);
return strdup(reply);
}
snprintf(reply, siz, "ok.%s", answer);
LOGDEBUG("%s.%s", id, answer);
free(answer);
return strdup(reply);
}
static char *cmd_poolstats_do(PGconn *conn, char *cmd, char *id, char *by,
char *code, char *inet, tv_t *cd, bool igndup,
K_TREE *trf_root)
{
char reply[1024] = "";
size_t siz = sizeof(reply);
K_TREE_CTX ctx[1];
bool store;
// log to logfile
K_ITEM *i_poolinstance, *i_elapsed, *i_users, *i_workers;
K_ITEM *i_hashrate, *i_hashrate5m, *i_hashrate1hr, *i_hashrate24hr;
K_ITEM look, *ps;
POOLSTATS row, *poolstats;
bool ok = false;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz);
if (!i_poolinstance)
return strdup(reply);
i_elapsed = optional_name(trf_root, "elapsed", 1, NULL);
if (!i_elapsed)
i_elapsed = &poolstats_elapsed;
i_users = require_name(trf_root, "users", 1, NULL, reply, siz);
if (!i_users)
return strdup(reply);
i_workers = require_name(trf_root, "workers", 1, NULL, reply, siz);
if (!i_workers)
return strdup(reply);
i_hashrate = require_name(trf_root, "hashrate", 1, NULL, reply, siz);
if (!i_hashrate)
return strdup(reply);
i_hashrate5m = require_name(trf_root, "hashrate5m", 1, NULL, reply, siz);
if (!i_hashrate5m)
return strdup(reply);
i_hashrate1hr = require_name(trf_root, "hashrate1hr", 1, NULL, reply, siz);
if (!i_hashrate1hr)
return strdup(reply);
i_hashrate24hr = require_name(trf_root, "hashrate24hr", 1, NULL, reply, siz);
if (!i_hashrate24hr)
return strdup(reply);
STRNCPY(row.poolinstance, transfer_data(i_poolinstance));
row.createdate.tv_sec = date_eot.tv_sec;
row.createdate.tv_usec = date_eot.tv_usec;
INIT_POOLSTATS(&look);
look.data = (void *)(&row);
ps = find_before_in_ktree(poolstats_root, &look, cmp_poolstats, ctx);
if (!ps)
store = true;
else {
DATA_POOLSTATS(poolstats, ps);
// Find last stored matching the poolinstance and less than STATS_PER old
while (ps && !poolstats->stored &&
strcmp(row.poolinstance, poolstats->poolinstance) == 0 &&
tvdiff(cd, &(poolstats->createdate)) < STATS_PER) {
ps = prev_in_ktree(ctx);
DATA_POOLSTATS_NULL(poolstats, ps);
}
if (!ps || !poolstats->stored ||
strcmp(row.poolinstance, poolstats->poolinstance) != 0 ||
tvdiff(cd, &(poolstats->createdate)) >= STATS_PER)
store = true;
else
store = false;
}
ok = poolstats_add(conn, store, transfer_data(i_poolinstance),
transfer_data(i_elapsed),
transfer_data(i_users),
transfer_data(i_workers),
transfer_data(i_hashrate),
transfer_data(i_hashrate5m),
transfer_data(i_hashrate1hr),
transfer_data(i_hashrate24hr),
by, code, inet, cd, igndup, trf_root);
if (!ok) {
LOGERR("%s() %s.failed.DBE", __func__, id);
return strdup("failed.DBE");
}
LOGDEBUG("%s.ok.", id);
snprintf(reply, siz, "ok.");
return strdup(reply);
}
static char *cmd_poolstats(PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *notnow, char *by,
char *code, char *inet, tv_t *cd,
K_TREE *trf_root)
{
bool igndup = false;
// confirm_summaries() doesn't call this
if (reloading) {
if (tv_equal(cd, &(dbstatus.newest_createdate_blocks)))
igndup = true;
else if (tv_newer(cd, &(dbstatus.newest_createdate_blocks)))
return NULL;
}
return cmd_poolstats_do(conn, cmd, id, by, code, inet, cd, igndup, trf_root);
}
static char *cmd_userstats(__maybe_unused PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *notnow, char *by, char *code,
char *inet, tv_t *cd, K_TREE *trf_root)
{
char reply[1024] = "";
size_t siz = sizeof(reply);
// log to logfile
K_ITEM *i_poolinstance, *i_elapsed, *i_username, *i_workername;
K_ITEM *i_hashrate, *i_hashrate5m, *i_hashrate1hr, *i_hashrate24hr;
K_ITEM *i_eos, *i_idle;
bool ok = false, idle, eos;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz);
if (!i_poolinstance)
return strdup(reply);
i_elapsed = optional_name(trf_root, "elapsed", 1, NULL);
if (!i_elapsed)
i_elapsed = &userstats_elapsed;
i_username = require_name(trf_root, "username", 1, NULL, reply, siz);
if (!i_username)
return strdup(reply);
i_workername = optional_name(trf_root, "workername", 1, NULL);
if (!i_workername)
i_workername = &userstats_workername;
i_hashrate = require_name(trf_root, "hashrate", 1, NULL, reply, siz);
if (!i_hashrate)
return strdup(reply);
i_hashrate5m = require_name(trf_root, "hashrate5m", 1, NULL, reply, siz);
if (!i_hashrate5m)
return strdup(reply);
i_hashrate1hr = require_name(trf_root, "hashrate1hr", 1, NULL, reply, siz);
if (!i_hashrate1hr)
return strdup(reply);
i_hashrate24hr = require_name(trf_root, "hashrate24hr", 1, NULL, reply, siz);
if (!i_hashrate24hr)
return strdup(reply);
i_idle = optional_name(trf_root, "idle", 1, NULL);
if (!i_idle)
i_idle = &userstats_idle;
idle = (strcasecmp(transfer_data(i_idle), TRUE_STR) == 0);
i_eos = optional_name(trf_root, "eos", 1, NULL);
if (!i_eos)
i_eos = &userstats_eos;
eos = (strcasecmp(transfer_data(i_eos), TRUE_STR) == 0);
ok = userstats_add(transfer_data(i_poolinstance),
transfer_data(i_elapsed),
transfer_data(i_username),
transfer_data(i_workername),
transfer_data(i_hashrate),
transfer_data(i_hashrate5m),
transfer_data(i_hashrate1hr),
transfer_data(i_hashrate24hr),
idle, eos, by, code, inet, cd, trf_root);
if (!ok) {
LOGERR("%s() %s.failed.DATA", __func__, id);
return strdup("failed.DATA");
}
LOGDEBUG("%s.ok.", id);
snprintf(reply, siz, "ok.");
return strdup(reply);
}
static char *cmd_blocklist(__maybe_unused PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *now, __maybe_unused char *by,
__maybe_unused char *code, __maybe_unused char *inet,
__maybe_unused tv_t *notcd,
__maybe_unused K_TREE *trf_root)
{
K_TREE_CTX ctx[1];
K_ITEM *b_item, *w_item;
BLOCKS *blocks;
char reply[1024] = "";
char tmp[1024];
char *buf;
size_t len, off;
int32_t height = -1;
tv_t first_cd = {0,0};
int rows;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
APPEND_REALLOC_INIT(buf, off, len);
APPEND_REALLOC(buf, off, len, "ok.");
rows = 0;
K_RLOCK(blocks_free);
b_item = last_in_ktree(blocks_root, ctx);
while (b_item && rows < 42) {
DATA_BLOCKS(blocks, b_item);
if (height != blocks->height) {
height = blocks->height;
copy_tv(&first_cd, &(blocks->createdate));
}
if (CURRENT(&(blocks->expirydate))) {
int_to_buf(blocks->height, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "height:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
str_to_buf(blocks->blockhash, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "blockhash:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
str_to_buf(blocks->nonce, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "nonce:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
bigint_to_buf(blocks->reward, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "reward:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
str_to_buf(blocks->workername, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "workername:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp),
"firstcreatedate:%d=%ld%c", rows,
first_cd.tv_sec, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp),
"createdate:%d=%ld%c", rows,
blocks->createdate.tv_sec, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp),
"status:%d=%s%c", rows,
blocks_confirmed(blocks->confirmed), FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(blocks->diffacc, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "diffacc:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(blocks->diffinv, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "diffinv:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(blocks->shareacc, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "shareacc:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(blocks->shareinv, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "shareinv:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
bigint_to_buf(blocks->elapsed, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "elapsed:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
w_item = find_workinfo(blocks->workinfoid);
if (w_item) {
char wdiffbin[TXT_SML+1];
double wdiff;
WORKINFO *workinfo;
DATA_WORKINFO(workinfo, w_item);
hex2bin(wdiffbin, workinfo->bits, 4);
wdiff = diff_from_nbits(wdiffbin);
snprintf(tmp, sizeof(tmp),
"netdiff:%d=%.1f%c",
rows, wdiff, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
} else {
snprintf(tmp, sizeof(tmp),
"netdiff:%d=?%c", rows, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
}
rows++;
}
b_item = prev_in_ktree(ctx);
}
K_RUNLOCK(blocks_free);
snprintf(tmp, sizeof(tmp),
"rows=%d%cflds=%s%c",
rows, FLDSEP,
"height,blockhash,nonce,reward,workername,firstcreatedate,"
"createdate,status,diffacc,diffinv,shareacc,shareinv,elapsed,"
"netdiff", FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Blocks", FLDSEP, "");
APPEND_REALLOC(buf, off, len, tmp);
LOGDEBUG("%s.ok.%d_blocks", id, rows);
return buf;
}
static char *cmd_blockstatus(__maybe_unused PGconn *conn, char *cmd, char *id,
tv_t *now, char *by, char *code, char *inet,
__maybe_unused tv_t *cd, K_TREE *trf_root)
{
K_ITEM *i_height, *i_blockhash, *i_action;
char reply[1024] = "";
size_t siz = sizeof(reply);
K_ITEM *b_item;
BLOCKS *blocks;
int32_t height;
char *action;
bool ok = false;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_height = require_name(trf_root, "height", 1, NULL, reply, siz);
if (!i_height)
return strdup(reply);
TXT_TO_INT("height", transfer_data(i_height), height);
i_blockhash = require_name(trf_root, "blockhash", 1, NULL, reply, siz);
if (!i_blockhash)
return strdup(reply);
i_action = require_name(trf_root, "action", 1, NULL, reply, siz);
if (!i_action)
return strdup(reply);
action = transfer_data(i_action);
K_RLOCK(blocks_free);
b_item = find_blocks(height, transfer_data(i_blockhash));
K_RUNLOCK(blocks_free);
if (!b_item) {
snprintf(reply, siz, "ERR.unknown block");
LOGERR("%s.%s", id, reply);
return strdup(reply);
}
DATA_BLOCKS(blocks, b_item);
if (strcasecmp(action, "orphan") == 0) {
switch (blocks->confirmed[0]) {
case BLOCKS_NEW:
case BLOCKS_CONFIRM:
ok = blocks_add(conn, transfer_data(i_height),
blocks->blockhash,
BLOCKS_ORPHAN_STR,
EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY,
by, code, inet, now, false, id,
trf_root);
if (!ok) {
snprintf(reply, siz,
"DBE.action '%s'",
action);
LOGERR("%s.%s", id, reply);
return strdup(reply);
}
// TODO: reset the share counter?
break;
default:
snprintf(reply, siz,
"ERR.invalid action '%.*s%s' for block state '%s'",
CMD_SIZ, action,
(strlen(action) > CMD_SIZ) ? "..." : "",
blocks_confirmed(blocks->confirmed));
LOGERR("%s.%s", id, reply);
return strdup(reply);
}
} else {
snprintf(reply, siz, "ERR.unknown action '%s'",
transfer_data(i_action));
LOGERR("%s.%s", id, reply);
return strdup(reply);
}
snprintf(reply, siz, "ok.%s %d", transfer_data(i_action), height);
LOGDEBUG("%s.%s", id, reply);
return strdup(reply);
}
static char *cmd_newid(PGconn *conn, char *cmd, char *id, tv_t *now, char *by,
char *code, char *inet, __maybe_unused tv_t *cd,
K_TREE *trf_root)
{
char reply[1024] = "";
size_t siz = sizeof(reply);
K_ITEM *i_idname, *i_idvalue, *look;
IDCONTROL *row;
char *params[2 + MODIFYDATECOUNT];
int par;
bool ok = false;
ExecStatusType rescode;
bool conned = false;
PGresult *res;
char *ins;
int n;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_idname = require_name(trf_root, "idname", 3, (char *)idpatt, reply, siz);
if (!i_idname)
return strdup(reply);
i_idvalue = require_name(trf_root, "idvalue", 1, (char *)intpatt, reply, siz);
if (!i_idvalue)
return strdup(reply);
K_WLOCK(idcontrol_free);
look = k_unlink_head(idcontrol_free);
K_WUNLOCK(idcontrol_free);
DATA_IDCONTROL(row, look);
STRNCPY(row->idname, transfer_data(i_idname));
TXT_TO_BIGINT("idvalue", transfer_data(i_idvalue), row->lastid);
MODIFYDATEINIT(row, now, by, code, inet);
par = 0;
params[par++] = str_to_buf(row->idname, NULL, 0);
params[par++] = bigint_to_buf(row->lastid, NULL, 0);
MODIFYDATEPARAMS(params, par, row);
PARCHK(par, params);
ins = "insert into idcontrol "
"(idname,lastid" MODIFYDATECONTROL ") values (" PQPARAM10 ")";
if (!conn) {
conn = dbconnect();
conned = true;
}
res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE);
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Insert", rescode, conn);
goto foil;
}
ok = true;
foil:
PQclear(res);
if (conned)
PQfinish(conn);
for (n = 0; n < par; n++)
free(params[n]);
K_WLOCK(idcontrol_free);
k_add_head(idcontrol_free, look);
K_WUNLOCK(idcontrol_free);
if (!ok) {
LOGERR("%s() %s.failed.DBE", __func__, id);
return strdup("failed.DBE");
}
LOGDEBUG("%s.ok.added %s %"PRId64, id, transfer_data(i_idname), row->lastid);
snprintf(reply, siz, "ok.added %s %"PRId64,
transfer_data(i_idname), row->lastid);
return strdup(reply);
}
static char *cmd_payments(__maybe_unused PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *now, __maybe_unused char *by,
__maybe_unused char *code, __maybe_unused char *inet,
__maybe_unused tv_t *notcd,
__maybe_unused K_TREE *trf_root)
{
K_ITEM *i_username, look, *u_item, *p_item;
K_TREE_CTX ctx[1];
PAYMENTS lookpayments, *payments;
USERS *users;
char reply[1024] = "";
char tmp[1024];
size_t siz = sizeof(reply);
char *buf;
size_t len, off;
int rows;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz);
if (!i_username)
return strdup(reply);
K_RLOCK(users_free);
u_item = find_users(transfer_data(i_username));
K_RUNLOCK(users_free);
if (!u_item)
return strdup("bad");
DATA_USERS(users, u_item);
lookpayments.userid = users->userid;
lookpayments.paydate.tv_sec = 0;
lookpayments.paydate.tv_usec = 0;
INIT_PAYMENTS(&look);
look.data = (void *)(&lookpayments);
p_item = find_after_in_ktree(payments_root, &look, cmp_payments, ctx);
DATA_PAYMENTS_NULL(payments, p_item);
APPEND_REALLOC_INIT(buf, off, len);
APPEND_REALLOC(buf, off, len, "ok.");
rows = 0;
while (p_item && payments->userid == users->userid) {
tv_to_buf(&(payments->paydate), reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "paydate:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
str_to_buf(payments->payaddress, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "payaddress:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
bigint_to_buf(payments->amount, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "amount:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
rows++;
p_item = next_in_ktree(ctx);
DATA_PAYMENTS_NULL(payments, p_item);
}
snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c",
rows, FLDSEP,
"paydate,payaddress,amount", FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Payments", FLDSEP, "");
APPEND_REALLOC(buf, off, len, tmp);
LOGDEBUG("%s.ok.%s", id, transfer_data(i_username));
return buf;
}
static char *cmd_workers(__maybe_unused PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *now, __maybe_unused char *by,
__maybe_unused char *code, __maybe_unused char *inet,
__maybe_unused tv_t *notcd, K_TREE *trf_root)
{
K_ITEM *i_username, *i_stats, w_look, *u_item, *w_item, us_look, *us_item, *ws_item;
K_TREE_CTX w_ctx[1], us_ctx[1];
WORKERS lookworkers, *workers;
WORKERSTATUS *workerstatus;
USERSTATS lookuserstats, *userstats;
USERS *users;
char reply[1024] = "";
char tmp[1024];
size_t siz = sizeof(reply);
char *buf;
size_t len, off;
bool stats;
int rows;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz);
if (!i_username)
return strdup(reply);
K_RLOCK(users_free);
u_item = find_users(transfer_data(i_username));
K_RUNLOCK(users_free);
if (!u_item)
return strdup("bad");
DATA_USERS(users, u_item);
i_stats = optional_name(trf_root, "stats", 1, NULL);
if (!i_stats)
stats = false;
else
stats = (strcasecmp(transfer_data(i_stats), TRUE_STR) == 0);
INIT_WORKERS(&w_look);
INIT_USERSTATS(&us_look);
lookworkers.userid = users->userid;
lookworkers.workername[0] = '\0';
lookworkers.expirydate.tv_sec = 0;
lookworkers.expirydate.tv_usec = 0;
w_look.data = (void *)(&lookworkers);
w_item = find_after_in_ktree(workers_root, &w_look, cmp_workers, w_ctx);
DATA_WORKERS_NULL(workers, w_item);
APPEND_REALLOC_INIT(buf, off, len);
APPEND_REALLOC(buf, off, len, "ok.");
rows = 0;
while (w_item && workers->userid == users->userid) {
if (CURRENT(&(workers->expirydate))) {
str_to_buf(workers->workername, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "workername:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
int_to_buf(workers->difficultydefault, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "difficultydefault:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
str_to_buf(workers->idlenotificationenabled, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "idlenotificationenabled:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
int_to_buf(workers->idlenotificationtime, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "idlenotificationtime:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
if (stats) {
K_TREE *userstats_workername_root = new_ktree();
K_TREE_CTX usw_ctx[1];
double w_hashrate5m, w_hashrate1hr;
int64_t w_elapsed;
tv_t w_lastshare;
double w_lastdiff, w_diffacc, w_diffinv;
double w_shareacc, w_shareinv;
w_hashrate5m = w_hashrate1hr = 0.0;
w_elapsed = -1;
w_lastshare.tv_sec = 0;
w_lastdiff = w_diffacc = w_diffinv =
w_shareacc = w_shareinv = 0;
ws_item = find_workerstatus(users->userid, workers->workername,
__FILE__, __func__, __LINE__);
if (ws_item) {
DATA_WORKERSTATUS(workerstatus, ws_item);
w_lastshare.tv_sec = workerstatus->last_share.tv_sec;
w_lastdiff = workerstatus->last_diff;
w_diffacc = workerstatus->diffacc;
w_diffinv = workerstatus->diffinv;
w_shareacc = workerstatus->shareacc;
w_shareinv = workerstatus->shareinv;
}
// find last stored userid record
lookuserstats.userid = users->userid;
lookuserstats.statsdate.tv_sec = date_eot.tv_sec;
lookuserstats.statsdate.tv_usec = date_eot.tv_usec;
// find/cmp doesn't get to here
lookuserstats.poolinstance[0] = '\0';
lookuserstats.workername[0] = '\0';
us_look.data = (void *)(&lookuserstats);
K_RLOCK(userstats_free);
us_item = find_before_in_ktree(userstats_root, &us_look, cmp_userstats, us_ctx);
DATA_USERSTATS_NULL(userstats, us_item);
while (us_item && userstats->userid == lookuserstats.userid) {
if (strcmp(userstats->workername, workers->workername) == 0) {
if (tvdiff(now, &(userstats->statsdate)) < USERSTATS_PER_S) {
// TODO: add together the latest per pool instance (this is the latest per worker)
if (!find_in_ktree(userstats_workername_root, us_item, cmp_userstats_workername, usw_ctx)) {
w_hashrate5m += userstats->hashrate5m;
w_hashrate1hr += userstats->hashrate1hr;
if (w_elapsed == -1 || w_elapsed > userstats->elapsed)
w_elapsed = userstats->elapsed;
userstats_workername_root = add_to_ktree(userstats_workername_root,
us_item,
cmp_userstats_workername);
}
} else
break;
}
us_item = prev_in_ktree(us_ctx);
DATA_USERSTATS_NULL(userstats, us_item);
}
double_to_buf(w_hashrate5m, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "w_hashrate5m:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(w_hashrate1hr, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "w_hashrate1hr:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
bigint_to_buf(w_elapsed, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "w_elapsed:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
int_to_buf((int)(w_lastshare.tv_sec), reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "w_lastshare:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(w_lastdiff, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "w_lastdiff:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(w_diffacc, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "w_diffacc:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(w_diffinv, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "w_diffinv:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(w_shareacc, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "w_shareacc:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(w_shareinv, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "w_shareinv:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
userstats_workername_root = free_ktree(userstats_workername_root, NULL);
K_RUNLOCK(userstats_free);
}
rows++;
}
w_item = next_in_ktree(w_ctx);
DATA_WORKERS_NULL(workers, w_item);
}
snprintf(tmp, sizeof(tmp),
"rows=%d%cflds=%s%s%c",
rows, FLDSEP,
"workername,difficultydefault,idlenotificationenabled,"
"idlenotificationtime",
stats ? ",w_hashrate5m,w_hashrate1hr,w_elapsed,w_lastshare,"
"w_lastdiff,w_diffacc,w_diffinv,w_shareacc,w_shareinv" : "",
FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Workers", FLDSEP, "");
APPEND_REALLOC(buf, off, len, tmp);
LOGDEBUG("%s.ok.%s", id, transfer_data(i_username));
return buf;
}
static char *cmd_allusers(__maybe_unused PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *now, __maybe_unused char *by,
__maybe_unused char *code, __maybe_unused char *inet,
__maybe_unused tv_t *notcd,
__maybe_unused K_TREE *trf_root)
{
K_TREE *userstats_workername_root = new_ktree();
K_ITEM *us_item, *usw_item, *tmp_item, *u_item;
K_TREE_CTX us_ctx[1], usw_ctx[1];
USERSTATS *userstats, *userstats_w;
USERS *users;
char reply[1024] = "";
char tmp[1024];
char *buf;
size_t len, off;
int rows;
int64_t userid = -1;
double u_hashrate5m = 0.0;
double u_hashrate1hr = 0.0;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
// TODO: this really should just get the last value of each client_id (within the time limit)
// Find last records for each user/worker in ALLUSERS_LIMIT_S
// TODO: include pool_instance
K_WLOCK(userstats_free);
us_item = last_in_ktree(userstats_statsdate_root, us_ctx);
DATA_USERSTATS_NULL(userstats, us_item);
while (us_item && tvdiff(now, &(userstats->statsdate)) < ALLUSERS_LIMIT_S) {
usw_item = find_in_ktree(userstats_workername_root, us_item, cmp_userstats_workername, usw_ctx);
if (!usw_item) {
usw_item = k_unlink_head(userstats_free);
DATA_USERSTATS(userstats_w, usw_item);
userstats_w->userid = userstats->userid;
strcpy(userstats_w->workername, userstats->workername);
userstats_w->hashrate5m = userstats->hashrate5m;
userstats_w->hashrate1hr = userstats->hashrate1hr;
userstats_workername_root = add_to_ktree(userstats_workername_root, usw_item, cmp_userstats_workername);
}
us_item = prev_in_ktree(us_ctx);
DATA_USERSTATS_NULL(userstats, us_item);
}
APPEND_REALLOC_INIT(buf, off, len);
APPEND_REALLOC(buf, off, len, "ok.");
rows = 0;
// Add up per user
usw_item = first_in_ktree(userstats_workername_root, usw_ctx);
while (usw_item) {
DATA_USERSTATS(userstats_w, usw_item);
if (userstats_w->userid != userid) {
if (userid != -1) {
K_RLOCK(users_free);
u_item = find_userid(userid);
K_RUNLOCK(users_free);
if (!u_item) {
LOGERR("%s() userid %"PRId64" ignored - userstats but not users",
__func__, userid);
} else {
DATA_USERS(users, u_item);
str_to_buf(users->username, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "username:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
bigint_to_buf(userid, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "userid:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(u_hashrate5m, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "u_hashrate5m:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(u_hashrate1hr, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "u_hashrate1hr:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
rows++;
}
}
userid = userstats_w->userid;
u_hashrate5m = 0;
u_hashrate1hr = 0;
}
u_hashrate5m += userstats_w->hashrate5m;
u_hashrate1hr += userstats_w->hashrate1hr;
tmp_item = usw_item;
usw_item = next_in_ktree(usw_ctx);
k_add_head(userstats_free, tmp_item);
}
if (userid != -1) {
K_RLOCK(users_free);
u_item = find_userid(userid);
K_RUNLOCK(users_free);
if (!u_item) {
LOGERR("%s() userid %"PRId64" ignored - userstats but not users",
__func__, userid);
} else {
DATA_USERS(users, u_item);
str_to_buf(users->username, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "username:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
bigint_to_buf(userid, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "userid:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(u_hashrate5m, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "u_hashrate5m:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(u_hashrate1hr, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "u_hashrate1hr:%d=%s%c", rows, reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
rows++;
}
}
userstats_workername_root = free_ktree(userstats_workername_root, NULL);
K_WUNLOCK(userstats_free);
snprintf(tmp, sizeof(tmp),
"rows=%d%cflds=%s%c",
rows, FLDSEP,
"username,userid,u_hashrate5m,u_hashrate1hr", FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Users", FLDSEP, "");
APPEND_REALLOC(buf, off, len, tmp);
LOGDEBUG("%s.ok.allusers", id);
return buf;
}
static char *cmd_sharelog(PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *notnow, char *by,
char *code, char *inet, tv_t *cd,
K_TREE *trf_root)
{
char reply[1024] = "";
size_t siz = sizeof(reply);
int64_t workinfoid;
// log to logfile with processing success/failure code
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
if (strcasecmp(cmd, STR_WORKINFO) == 0) {
K_ITEM *i_workinfoid, *i_poolinstance, *i_transactiontree, *i_merklehash;
K_ITEM *i_prevhash, *i_coinbase1, *i_coinbase2, *i_version, *i_bits;
K_ITEM *i_ntime, *i_reward;
bool igndup = false;
if (reloading && !confirm_sharesummary) {
if (tv_equal(cd, &(dbstatus.newest_createdate_workinfo)))
igndup = true;
else if (tv_newer(cd, &(dbstatus.newest_createdate_workinfo)))
return NULL;
}
i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz);
if (!i_workinfoid)
return strdup(reply);
if (confirm_sharesummary) {
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid);
if (workinfoid < confirm_first_workinfoid ||
workinfoid > confirm_last_workinfoid)
goto wiconf;
}
i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz);
if (!i_poolinstance)
return strdup(reply);
i_transactiontree = require_name(trf_root, "transactiontree", 0, NULL, reply, siz);
if (!i_transactiontree)
return strdup(reply);
i_merklehash = require_name(trf_root, "merklehash", 0, NULL, reply, siz);
if (!i_merklehash)
return strdup(reply);
i_prevhash = require_name(trf_root, "prevhash", 1, NULL, reply, siz);
if (!i_prevhash)
return strdup(reply);
i_coinbase1 = require_name(trf_root, "coinbase1", 1, NULL, reply, siz);
if (!i_coinbase1)
return strdup(reply);
i_coinbase2 = require_name(trf_root, "coinbase2", 1, NULL, reply, siz);
if (!i_coinbase2)
return strdup(reply);
i_version = require_name(trf_root, "version", 1, NULL, reply, siz);
if (!i_version)
return strdup(reply);
i_bits = require_name(trf_root, "bits", 1, NULL, reply, siz);
if (!i_bits)
return strdup(reply);
i_ntime = require_name(trf_root, "ntime", 1, NULL, reply, siz);
if (!i_ntime)
return strdup(reply);
i_reward = require_name(trf_root, "reward", 1, NULL, reply, siz);
if (!i_reward)
return strdup(reply);
workinfoid = workinfo_add(conn, transfer_data(i_workinfoid),
transfer_data(i_poolinstance),
transfer_data(i_transactiontree),
transfer_data(i_merklehash),
transfer_data(i_prevhash),
transfer_data(i_coinbase1),
transfer_data(i_coinbase2),
transfer_data(i_version),
transfer_data(i_bits),
transfer_data(i_ntime),
transfer_data(i_reward),
by, code, inet, cd, igndup, trf_root);
if (workinfoid == -1) {
LOGERR("%s(%s) %s.failed.DBE", __func__, cmd, id);
return strdup("failed.DBE");
}
LOGDEBUG("%s.ok.added %"PRId64, id, workinfoid);
wiconf:
snprintf(reply, siz, "ok.%"PRId64, workinfoid);
return strdup(reply);
} else if (strcasecmp(cmd, STR_SHARES) == 0) {
K_ITEM *i_workinfoid, *i_username, *i_workername, *i_clientid, *i_errn;
K_ITEM *i_enonce1, *i_nonce2, *i_nonce, *i_diff, *i_sdiff;
K_ITEM *i_secondaryuserid;
bool ok;
// This just excludes the shares we certainly don't need
if (reloading && !confirm_sharesummary) {
if (tv_newer(cd, &(dbstatus.sharesummary_firstshare)))
return NULL;
}
i_nonce = require_name(trf_root, "nonce", 1, NULL, reply, siz);
if (!i_nonce)
return strdup(reply);
i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz);
if (!i_workinfoid)
return strdup(reply);
if (confirm_sharesummary) {
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid);
if (workinfoid < confirm_first_workinfoid ||
workinfoid > confirm_last_workinfoid)
goto sconf;
}
i_username = require_name(trf_root, "username", 1, NULL, reply, siz);
if (!i_username)
return strdup(reply);
i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz);
if (!i_workername)
return strdup(reply);
i_clientid = require_name(trf_root, "clientid", 1, NULL, reply, siz);
if (!i_clientid)
return strdup(reply);
i_errn = require_name(trf_root, "errn", 1, NULL, reply, siz);
if (!i_errn)
return strdup(reply);
i_enonce1 = require_name(trf_root, "enonce1", 1, NULL, reply, siz);
if (!i_enonce1)
return strdup(reply);
i_nonce2 = require_name(trf_root, "nonce2", 1, NULL, reply, siz);
if (!i_nonce2)
return strdup(reply);
i_diff = require_name(trf_root, "diff", 1, NULL, reply, siz);
if (!i_diff)
return strdup(reply);
i_sdiff = require_name(trf_root, "sdiff", 1, NULL, reply, siz);
if (!i_sdiff)
return strdup(reply);
i_secondaryuserid = optional_name(trf_root, "secondaryuserid", 1, NULL);
if (!i_secondaryuserid)
i_secondaryuserid = &shares_secondaryuserid;
ok = shares_add(conn, transfer_data(i_workinfoid),
transfer_data(i_username),
transfer_data(i_workername),
transfer_data(i_clientid),
transfer_data(i_errn),
transfer_data(i_enonce1),
transfer_data(i_nonce2),
transfer_data(i_nonce),
transfer_data(i_diff),
transfer_data(i_sdiff),
transfer_data(i_secondaryuserid),
by, code, inet, cd, trf_root);
if (!ok) {
LOGERR("%s(%s) %s.failed.DATA", __func__, cmd, id);
return strdup("failed.DATA");
}
LOGDEBUG("%s.ok.added %s", id, transfer_data(i_nonce));
sconf:
snprintf(reply, siz, "ok.added %s", transfer_data(i_nonce));
return strdup(reply);
} else if (strcasecmp(cmd, STR_SHAREERRORS) == 0) {
K_ITEM *i_workinfoid, *i_username, *i_workername, *i_clientid, *i_errn;
K_ITEM *i_error, *i_secondaryuserid;
bool ok;
// This just excludes the shareerrors we certainly don't need
if (reloading && !confirm_sharesummary) {
if (tv_newer(cd, &(dbstatus.sharesummary_firstshare)))
return NULL;
}
i_username = require_name(trf_root, "username", 1, NULL, reply, siz);
if (!i_username)
return strdup(reply);
i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz);
if (!i_workinfoid)
return strdup(reply);
if (confirm_sharesummary) {
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid);
if (workinfoid < confirm_first_workinfoid ||
workinfoid > confirm_last_workinfoid)
goto seconf;
}
i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz);
if (!i_workername)
return strdup(reply);
i_clientid = require_name(trf_root, "clientid", 1, NULL, reply, siz);
if (!i_clientid)
return strdup(reply);
i_errn = require_name(trf_root, "errn", 1, NULL, reply, siz);
if (!i_errn)
return strdup(reply);
i_error = require_name(trf_root, "error", 1, NULL, reply, siz);
if (!i_error)
return strdup(reply);
i_secondaryuserid = optional_name(trf_root, "secondaryuserid", 1, NULL);
if (!i_secondaryuserid)
i_secondaryuserid = &shareerrors_secondaryuserid;
ok = shareerrors_add(conn, transfer_data(i_workinfoid),
transfer_data(i_username),
transfer_data(i_workername),
transfer_data(i_clientid),
transfer_data(i_errn),
transfer_data(i_error),
transfer_data(i_secondaryuserid),
by, code, inet, cd, trf_root);
if (!ok) {
LOGERR("%s(%s) %s.failed.DATA", __func__, cmd, id);
return strdup("failed.DATA");
}
LOGDEBUG("%s.ok.added %s", id, transfer_data(i_username));
seconf:
snprintf(reply, siz, "ok.added %s", transfer_data(i_username));
return strdup(reply);
} else if (strcasecmp(cmd, STR_AGEWORKINFO) == 0) {
K_ITEM *i_workinfoid, *i_poolinstance;
int64_t ss_count, s_count, s_diff;
tv_t ss_first, ss_last;
bool ok;
if (reloading && !confirm_sharesummary) {
if (tv_newer(cd, &(dbstatus.sharesummary_firstshare)))
return NULL;
}
i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz);
if (!i_workinfoid)
return strdup(reply);
if (confirm_sharesummary) {
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid);
if (workinfoid < confirm_first_workinfoid ||
workinfoid > confirm_last_workinfoid)
goto awconf;
}
i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz);
if (!i_poolinstance)
return strdup(reply);
TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid);
ok = workinfo_age(conn, workinfoid,
transfer_data(i_poolinstance),
by, code, inet, cd,
&ss_first, &ss_last,
&ss_count, &s_count, &s_diff);
if (!ok) {
LOGERR("%s(%s) %s.failed.DATA", __func__, cmd, id);
return strdup("failed.DATA");
} else {
/* Don't slow down the reload - do them later
* N.B. this means if you abort/shutdown the reload,
* next restart will again go back to the oldest
* unaged sharesummary due to a pool shutdown */
if (!reloading) {
// Aging is a queued item thus the reply is ignored
auto_age_older(conn, workinfoid,
transfer_data(i_poolinstance),
by, code, inet, cd);
}
}
LOGDEBUG("%s.ok.aged %"PRId64, id, workinfoid);
awconf:
snprintf(reply, siz, "ok.%"PRId64, workinfoid);
return strdup(reply);
}
LOGERR("%s.bad.cmd %s", cmd);
return strdup("bad.cmd");
}
// TODO: the confirm update: identify block changes from workinfo height?
static char *cmd_blocks_do(PGconn *conn, char *cmd, char *id, char *by,
char *code, char *inet, tv_t *cd, bool igndup,
K_TREE *trf_root)
{
char reply[1024] = "";
size_t siz = sizeof(reply);
K_ITEM *i_height, *i_blockhash, *i_confirmed, *i_workinfoid, *i_username;
K_ITEM *i_workername, *i_clientid, *i_enonce1, *i_nonce2, *i_nonce, *i_reward;
TRANSFER *transfer;
char *msg;
bool ok;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_height = require_name(trf_root, "height", 1, NULL, reply, siz);
if (!i_height)
return strdup(reply);
i_blockhash = require_name(trf_root, "blockhash", 1, NULL, reply, siz);
if (!i_blockhash)
return strdup(reply);
i_confirmed = require_name(trf_root, "confirmed", 1, NULL, reply, siz);
if (!i_confirmed)
return strdup(reply);
DATA_TRANSFER(transfer, i_confirmed);
transfer->mvalue[0] = tolower(transfer->mvalue[0]);
switch(transfer->mvalue[0]) {
case BLOCKS_NEW:
i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz);
if (!i_workinfoid)
return strdup(reply);
i_username = require_name(trf_root, "username", 1, NULL, reply, siz);
if (!i_username)
return strdup(reply);
i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz);
if (!i_workername)
return strdup(reply);
i_clientid = require_name(trf_root, "clientid", 1, NULL, reply, siz);
if (!i_clientid)
return strdup(reply);
i_enonce1 = require_name(trf_root, "enonce1", 1, NULL, reply, siz);
if (!i_enonce1)
return strdup(reply);
i_nonce2 = require_name(trf_root, "nonce2", 1, NULL, reply, siz);
if (!i_nonce2)
return strdup(reply);
i_nonce = require_name(trf_root, "nonce", 1, NULL, reply, siz);
if (!i_nonce)
return strdup(reply);
i_reward = require_name(trf_root, "reward", 1, NULL, reply, siz);
if (!i_reward)
return strdup(reply);
msg = "added";
ok = blocks_add(conn, transfer_data(i_height),
transfer_data(i_blockhash),
transfer_data(i_confirmed),
transfer_data(i_workinfoid),
transfer_data(i_username),
transfer_data(i_workername),
transfer_data(i_clientid),
transfer_data(i_enonce1),
transfer_data(i_nonce2),
transfer_data(i_nonce),
transfer_data(i_reward),
by, code, inet, cd, igndup, id,
trf_root);
break;
case BLOCKS_CONFIRM:
msg = "confirmed";
ok = blocks_add(conn, transfer_data(i_height),
transfer_data(i_blockhash),
transfer_data(i_confirmed),
EMPTY, EMPTY, EMPTY, EMPTY,
EMPTY, EMPTY, EMPTY, EMPTY,
by, code, inet, cd, igndup, id,
trf_root);
break;
default:
LOGERR("%s(): %s.failed.invalid confirm='%s'",
__func__, id, transfer_data(i_confirmed));
return strdup("failed.DATA");
}
if (!ok) {
/* Ignore during startup,
* another error should have shown if it matters */
if (startup_complete)
LOGERR("%s() %s.failed.DBE", __func__, id);
return strdup("failed.DBE");
}
LOGDEBUG("%s.ok.blocks %s", id, msg);
snprintf(reply, siz, "ok.%s", msg);
return strdup(reply);
}
static char *cmd_blocks(PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *notnow, char *by,
char *code, char *inet, tv_t *cd,
K_TREE *trf_root)
{
bool igndup = false;
// confirm_summaries() doesn't call this
if (reloading) {
if (tv_equal(cd, &(dbstatus.newest_createdate_blocks)))
igndup = true;
else if (tv_newer(cd, &(dbstatus.newest_createdate_blocks)))
return NULL;
}
return cmd_blocks_do(conn, cmd, id, by, code, inet, cd, igndup, trf_root);
}
static char *cmd_auth_do(PGconn *conn, char *cmd, char *id, char *by,
char *code, char *inet, tv_t *cd, bool igndup,
K_TREE *trf_root)
{
char reply[1024] = "";
size_t siz = sizeof(reply);
K_ITEM *i_poolinstance, *i_username, *i_workername, *i_clientid;
K_ITEM *i_enonce1, *i_useragent, *i_preauth;
char *secuserid;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_poolinstance = optional_name(trf_root, "poolinstance", 1, NULL);
if (!i_poolinstance)
i_poolinstance = &auth_poolinstance;
i_username = require_name(trf_root, "username", 1, NULL, reply, siz);
if (!i_username)
return strdup(reply);
i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz);
if (!i_workername)
return strdup(reply);
i_clientid = require_name(trf_root, "clientid", 1, NULL, reply, siz);
if (!i_clientid)
return strdup(reply);
i_enonce1 = require_name(trf_root, "enonce1", 1, NULL, reply, siz);
if (!i_enonce1)
return strdup(reply);
i_useragent = require_name(trf_root, "useragent", 0, NULL, reply, siz);
if (!i_useragent)
return strdup(reply);
i_preauth = optional_name(trf_root, "preauth", 1, NULL);
if (!i_preauth)
i_preauth = &auth_preauth;
secuserid = auths_add(conn, transfer_data(i_poolinstance),
transfer_data(i_username),
transfer_data(i_workername),
transfer_data(i_clientid),
transfer_data(i_enonce1),
transfer_data(i_useragent),
transfer_data(i_preauth),
by, code, inet, cd, igndup, trf_root, false);
if (!secuserid) {
LOGDEBUG("%s() %s.failed.DBE", __func__, id);
return strdup("failed.DBE");
}
LOGDEBUG("%s.ok.auth added for %s", id, secuserid);
snprintf(reply, siz, "ok.%s", secuserid);
return strdup(reply);
}
static char *cmd_auth(PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *now, char *by,
char *code, char *inet, tv_t *cd,
K_TREE *trf_root)
{
bool igndup = false;
// confirm_summaries() doesn't call this
if (reloading) {
if (tv_equal(cd, &(dbstatus.newest_createdate_auths)))
igndup = true;
else if (tv_newer(cd, &(dbstatus.newest_createdate_auths)))
return NULL;
}
return cmd_auth_do(conn, cmd, id, by, code, inet, cd, igndup, trf_root);
}
static char *cmd_addrauth_do(PGconn *conn, char *cmd, char *id, char *by,
char *code, char *inet, tv_t *cd, bool igndup,
K_TREE *trf_root)
{
char reply[1024] = "";
size_t siz = sizeof(reply);
K_ITEM *i_poolinstance, *i_username, *i_workername, *i_clientid;
K_ITEM *i_enonce1, *i_useragent, *i_preauth;
char *secuserid;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_poolinstance = optional_name(trf_root, "poolinstance", 1, NULL);
if (!i_poolinstance)
i_poolinstance = &auth_poolinstance;
i_username = require_name(trf_root, "username", 1, NULL, reply, siz);
if (!i_username)
return strdup(reply);
i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz);
if (!i_workername)
return strdup(reply);
i_clientid = require_name(trf_root, "clientid", 1, NULL, reply, siz);
if (!i_clientid)
return strdup(reply);
i_enonce1 = require_name(trf_root, "enonce1", 1, NULL, reply, siz);
if (!i_enonce1)
return strdup(reply);
i_useragent = require_name(trf_root, "useragent", 0, NULL, reply, siz);
if (!i_useragent)
return strdup(reply);
i_preauth = require_name(trf_root, "preauth", 1, NULL, reply, siz);
if (!i_preauth)
return strdup(reply);
secuserid = auths_add(conn, transfer_data(i_poolinstance),
transfer_data(i_username),
transfer_data(i_workername),
transfer_data(i_clientid),
transfer_data(i_enonce1),
transfer_data(i_useragent),
transfer_data(i_preauth),
by, code, inet, cd, igndup, trf_root, true);
if (!secuserid) {
LOGDEBUG("%s() %s.failed.DBE", __func__, id);
return strdup("failed.DBE");
}
LOGDEBUG("%s.ok.auth added for %s", id, secuserid);
snprintf(reply, siz, "ok.%s", secuserid);
return strdup(reply);
}
static char *cmd_addrauth(PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *now, char *by,
char *code, char *inet, tv_t *cd,
K_TREE *trf_root)
{
bool igndup = false;
// confirm_summaries() doesn't call this
if (reloading) {
if (tv_equal(cd, &(dbstatus.newest_createdate_auths)))
igndup = true;
else if (tv_newer(cd, &(dbstatus.newest_createdate_auths)))
return NULL;
}
return cmd_addrauth_do(conn, cmd, id, by, code, inet, cd, igndup, trf_root);
}
static char *cmd_homepage(__maybe_unused PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *now, __maybe_unused char *by,
__maybe_unused char *code, __maybe_unused char *inet,
__maybe_unused tv_t *notcd, K_TREE *trf_root)
{
K_ITEM *i_username, *u_item, *b_item, *p_item, *us_item, look;
double u_hashrate5m, u_hashrate1hr;
char reply[1024], tmp[1024], *buf;
USERSTATS lookuserstats, *userstats;
POOLSTATS *poolstats;
BLOCKS *blocks;
USERS *users;
int64_t u_elapsed;
K_TREE_CTX ctx[1], w_ctx[1];
size_t len, off;
bool has_uhr;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_username = optional_name(trf_root, "username", 1, NULL);
APPEND_REALLOC_INIT(buf, off, len);
APPEND_REALLOC(buf, off, len, "ok.");
if (last_bc.tv_sec) {
tvs_to_buf(&last_bc, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "lastbc=%s%c", reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
K_RLOCK(workinfo_free);
if (workinfo_current) {
WORKINFO *wic;
int32_t hi;
DATA_WORKINFO(wic, workinfo_current);
hi = coinbase1height(wic->coinbase1);
snprintf(tmp, sizeof(tmp), "lastheight=%d%c",
hi-1, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
} else {
snprintf(tmp, sizeof(tmp), "lastheight=?%c", FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
}
K_RUNLOCK(workinfo_free);
} else {
snprintf(tmp, sizeof(tmp), "lastbc=?%clastheight=?%c",
FLDSEP, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
}
if (current_ndiff) {
snprintf(tmp, sizeof(tmp), "currndiff=%.1f%c", current_ndiff, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
} else {
snprintf(tmp, sizeof(tmp), "currndiff=?%c", FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
}
// TODO: handle orphans
K_RLOCK(blocks_free);
b_item = last_in_ktree(blocks_root, ctx);
K_RUNLOCK(blocks_free);
if (b_item) {
DATA_BLOCKS(blocks, b_item);
tvs_to_buf(&(blocks->createdate), reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "lastblock=%s%cconfirmed=%s%c",
reply, FLDSEP,
blocks->confirmed, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "lastblockheight=%d%c",
blocks->height, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
} else {
snprintf(tmp, sizeof(tmp), "lastblock=?%cconfirmed=?%c"
"lastblockheight=?%c",
FLDSEP, FLDSEP, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
}
snprintf(tmp, sizeof(tmp), "blockacc=%.1f%c",
pool.diffacc, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "blockerr=%.1f%c",
pool.diffinv, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "blockshareacc=%.1f%c",
pool.shareacc, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "blockshareinv=%.1f%c",
pool.shareinv, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
// TODO: assumes only one poolinstance (for now)
p_item = last_in_ktree(poolstats_root, ctx);
if (p_item) {
DATA_POOLSTATS(poolstats, p_item);
int_to_buf(poolstats->users, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "users=%s%c", reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
int_to_buf(poolstats->workers, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "workers=%s%c", reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(poolstats->hashrate5m, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "p_hashrate5m=%s%c", reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(poolstats->hashrate1hr, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "p_hashrate1hr=%s%c", reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
bigint_to_buf(poolstats->elapsed, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "p_elapsed=%s%c", reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
} else {
snprintf(tmp, sizeof(tmp), "users=?%cworkers=?%cp_hashrate5m=?%c"
"p_hashrate1hr=?%cp_elapsed=?%c",
FLDSEP, FLDSEP, FLDSEP, FLDSEP, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
}
u_item = NULL;
if (i_username) {
K_RLOCK(users_free);
u_item = find_users(transfer_data(i_username));
K_RUNLOCK(users_free);
}
has_uhr = false;
if (p_item && u_item) {
DATA_USERS(users, u_item);
K_TREE *userstats_workername_root = new_ktree();
u_hashrate5m = u_hashrate1hr = 0.0;
u_elapsed = -1;
// find last stored userid record
lookuserstats.userid = users->userid;
lookuserstats.statsdate.tv_sec = date_eot.tv_sec;
lookuserstats.statsdate.tv_usec = date_eot.tv_usec;
// find/cmp doesn't get to here
STRNCPY(lookuserstats.poolinstance, EMPTY);
STRNCPY(lookuserstats.workername, EMPTY);
INIT_USERSTATS(&look);
look.data = (void *)(&lookuserstats);
K_RLOCK(userstats_free);
us_item = find_before_in_ktree(userstats_root, &look, cmp_userstats, ctx);
DATA_USERSTATS_NULL(userstats, us_item);
while (us_item && userstats->userid == lookuserstats.userid &&
tvdiff(now, &(userstats->statsdate)) < USERSTATS_PER_S) {
// TODO: add the latest per pool instance (this is the latest per worker)
// Ignore summarised data from the DB, it should be old so irrelevant
if (userstats->poolinstance[0] &&
!find_in_ktree(userstats_workername_root, us_item, cmp_userstats_workername, w_ctx)) {
u_hashrate5m += userstats->hashrate5m;
u_hashrate1hr += userstats->hashrate1hr;
if (u_elapsed == -1 || u_elapsed > userstats->elapsed)
u_elapsed = userstats->elapsed;
has_uhr = true;
userstats_workername_root = add_to_ktree(userstats_workername_root,
us_item,
cmp_userstats_workername);
}
us_item = prev_in_ktree(ctx);
DATA_USERSTATS_NULL(userstats, us_item);
}
userstats_workername_root = free_ktree(userstats_workername_root, NULL);
K_RUNLOCK(userstats_free);
}
if (has_uhr) {
double_to_buf(u_hashrate5m, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "u_hashrate5m=%s%c", reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
double_to_buf(u_hashrate1hr, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "u_hashrate1hr=%s%c", reply, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
bigint_to_buf(u_elapsed, reply, sizeof(reply));
snprintf(tmp, sizeof(tmp), "u_elapsed=%s", reply);
APPEND_REALLOC(buf, off, len, tmp);
} else {
snprintf(tmp, sizeof(tmp), "u_hashrate5m=?%cu_hashrate1hr=?%cu_elapsed=?",
FLDSEP, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
}
LOGDEBUG("%s.ok.home,user=%s", id,
i_username ? transfer_data(i_username): "N");
return buf;
}
static char *cmd_getatts(__maybe_unused PGconn *conn, char *cmd, char *id,
tv_t *now, __maybe_unused char *by,
__maybe_unused char *code, __maybe_unused char *inet,
__maybe_unused tv_t *notcd, K_TREE *trf_root)
{
K_ITEM *i_username, *i_attlist, *u_item, *ua_item;
char reply[1024] = "";
size_t siz = sizeof(reply);
char tmp[1024];
USERATTS *useratts;
USERS *users;
char *reason = NULL;
char *answer = NULL;
char *ptr, *comma, *dot;
size_t len, off;
bool first;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz);
if (!i_username) {
reason = "Missing username";
goto nuts;
}
K_RLOCK(users_free);
u_item = find_users(transfer_data(i_username));
K_RUNLOCK(users_free);
if (!u_item) {
reason = "Unknown user";
goto nuts;
} else {
DATA_USERS(users, u_item);
i_attlist = require_name(trf_root, "attlist", 1, NULL, reply, siz);
if (!i_attlist) {
reason = "Missing attlist";
goto nuts;
}
APPEND_REALLOC_INIT(answer, off, len);
ptr = strdup(transfer_data(i_attlist));
first = true;
while (ptr && *ptr) {
comma = strchr(ptr, ',');
if (comma)
*(comma++) = '\0';
dot = strchr(ptr, '.');
if (!dot) {
free(answer);
reason = "Missing element";
goto nuts;
}
*(dot++) = '\0';
K_RLOCK(useratts_free);
ua_item = find_useratts(users->userid, ptr);
K_RUNLOCK(useratts_free);
/* web code must check the existance of the attname
* in the reply since it will be missing if it doesn't
* exist in the DB */
if (ua_item) {
char num_buf[BIGINT_BUFSIZ];
char ctv_buf[CDATE_BUFSIZ];
char *ans;
DATA_USERATTS(useratts, ua_item);
if (strcmp(dot, "str") == 0) {
ans = useratts->attstr;
} else if (strcmp(dot, "str2") == 0) {
ans = useratts->attstr2;
} else if (strcmp(dot, "num") == 0) {
bigint_to_buf(useratts->attnum,
num_buf,
sizeof(num_buf));
ans = num_buf;
} else if (strcmp(dot, "num2") == 0) {
bigint_to_buf(useratts->attnum2,
num_buf,
sizeof(num_buf));
ans = num_buf;
} else if (strcmp(dot, "date") == 0) {
ctv_to_buf(&(useratts->attdate),
ctv_buf,
sizeof(num_buf));
ans = ctv_buf;
} else if (strcmp(dot, "dateexp") == 0) {
// Y/N if date is before now (not expired)
if (tv_newer(&(useratts->attdate), now))
ans = TRUE_STR;
else
ans = FALSE_STR;
} else if (strcmp(dot, "date2") == 0) {
ctv_to_buf(&(useratts->attdate2),
ctv_buf,
sizeof(num_buf));
ans = ctv_buf;
} else if (strcmp(dot, "date2exp") == 0) {
// Y/N if date2 is before now (not expired)
if (tv_newer(&(useratts->attdate2), now))
ans = TRUE_STR;
else
ans = FALSE_STR;
} else {
free(answer);
reason = "Unknown element";
goto nuts;
}
snprintf(tmp, sizeof(tmp), "%s%s.%s=%s",
first ? EMPTY : FLDSEPSTR,
ptr, dot, ans);
APPEND_REALLOC(answer, off, len, tmp);
first = false;
}
ptr = comma;
}
}
nuts:
if (reason) {
snprintf(reply, siz, "ERR.%s", reason);
LOGERR("%s.%s.%s", cmd, id, reply);
return strdup(reply);
}
snprintf(reply, siz, "ok.%s", answer);
LOGDEBUG("%s.%s", id, answer);
free(answer);
return strdup(reply);
}
static void att_to_date(tv_t *date, char *data, tv_t *now)
{
int add;
if (strncasecmp(data, "now+", 4) == 0) {
add = atoi(data+4);
copy_tv(date, now);
date->tv_sec += add;
} else if (strcasecmp(data, "now") == 0) {
copy_tv(date, now);
} else {
txt_to_ctv("date", data, date, sizeof(*date));
}
}
static char *cmd_setatts(PGconn *conn, char *cmd, char *id,
tv_t *now, char *by, char *code, char *inet,
__maybe_unused tv_t *notcd, K_TREE *trf_root)
{
ExecStatusType rescode;
PGresult *res;
bool conned = false;
K_ITEM *i_username, *t_item, *u_item, *ua_item = NULL;
K_TREE_CTX ctx[1];
char reply[1024] = "";
size_t siz = sizeof(reply);
TRANSFER *transfer;
USERATTS *useratts;
USERS *users;
char attname[sizeof(useratts->attname)*2];
char *reason = NULL;
char *dot, *data;
bool begun = false;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz);
if (!i_username) {
reason = "Missing user";
goto bats;
}
K_RLOCK(users_free);
u_item = find_users(transfer_data(i_username));
K_RUNLOCK(users_free);
if (!u_item) {
reason = "Unknown user";
goto bats;
} else {
DATA_USERS(users, u_item);
/* format is: ua_attname.element=value
* i.e. eash starts with the constant "ua_"
* transfer will sort them so that any of the same attname
* will be next to each other
* thus can combine multiple elements for the same attname
* into one single useratts record (as is mandatory) */
t_item = first_in_ktree(trf_root, ctx);
while (t_item) {
DATA_TRANSFER(transfer, t_item);
if (strncmp(transfer->name, "ua_", 3) == 0) {
data = transfer_data(t_item);
STRNCPY(attname, transfer->name + 3);
dot = strchr(attname, '.');
if (!dot) {
reason = "Missing element";
goto bats;
}
*(dot++) = '\0';
// If we already had a different one, save it to the DB
if (ua_item && strcmp(useratts->attname, attname) != 0) {
if (conn == NULL) {
conn = dbconnect();
conned = true;
}
if (!begun) {
// Beginning of a write txn
res = PQexec(conn, "Begin", CKPQ_WRITE);
rescode = PQresultStatus(res);
PQclear(res);
if (!PGOK(rescode)) {
PGLOGERR("Begin", rescode, conn);
reason = "DBERR";
goto bats;
}
begun = true;
}
if (useratts_item_add(conn, ua_item, now, begun))
ua_item = NULL;
else {
res = PQexec(conn, "Rollback", CKPQ_WRITE);
PQclear(res);
reason = "DBERR";
goto bats;
}
}
if (!ua_item) {
K_RLOCK(useratts_free);
ua_item = k_unlink_head(useratts_free);
K_RUNLOCK(useratts_free);
DATA_USERATTS(useratts, ua_item);
bzero(useratts, sizeof(*useratts));
useratts->userid = users->userid;
STRNCPY(useratts->attname, attname);
HISTORYDATEINIT(useratts, now, by, code, inet);
HISTORYDATETRANSFER(trf_root, useratts);
}
if (strcmp(dot, "str") == 0) {
STRNCPY(useratts->attstr, data);
} else if (strcmp(dot, "str2") == 0) {
STRNCPY(useratts->attstr2, data);
} else if (strcmp(dot, "num") == 0) {
TXT_TO_BIGINT("num", data, useratts->attnum);
} else if (strcmp(dot, "num2") == 0) {
TXT_TO_BIGINT("num2", data, useratts->attnum2);
} else if (strcmp(dot, "date") == 0) {
att_to_date(&(useratts->attdate), data, now);
} else if (strcmp(dot, "date2") == 0) {
att_to_date(&(useratts->attdate2), data, now);
} else {
reason = "Unknown element";
goto bats;
}
}
t_item = next_in_ktree(ctx);
}
if (ua_item) {
if (conn == NULL) {
conn = dbconnect();
conned = true;
}
if (!begun) {
// Beginning of a write txn
res = PQexec(conn, "Begin", CKPQ_WRITE);
rescode = PQresultStatus(res);
PQclear(res);
if (!PGOK(rescode)) {
PGLOGERR("Begin", rescode, conn);
reason = "DBERR";
goto bats;
}
begun = true;
}
if (!useratts_item_add(conn, ua_item, now, begun)) {
res = PQexec(conn, "Rollback", CKPQ_WRITE);
PQclear(res);
reason = "DBERR";
goto bats;
}
res = PQexec(conn, "Commit", CKPQ_WRITE);
PQclear(res);
}
}
bats:
if (conned)
PQfinish(conn);
if (reason) {
if (ua_item) {
K_WLOCK(useratts_free);
k_add_head(useratts_free, ua_item);
K_WUNLOCK(useratts_free);
}
snprintf(reply, siz, "ERR.%s", reason);
LOGERR("%s.%s.%s", cmd, id, reply);
return strdup(reply);
}
snprintf(reply, siz, "ok.set");
LOGDEBUG("%s.%s", id, reply);
return strdup(reply);
}
// order by userid asc
static cmp_t cmp_mu(K_ITEM *a, K_ITEM *b)
{
MININGPAYOUTS *ma, *mb;
DATA_MININGPAYOUTS(ma, a);
DATA_MININGPAYOUTS(mb, b);
return CMP_BIGINT(ma->userid, mb->userid);
}
static K_TREE *upd_add_mu(K_TREE *mu_root, K_STORE *mu_store, int64_t userid, int64_t diffacc)
{
MININGPAYOUTS lookminingpayouts, *miningpayouts;
K_ITEM look, *mu_item;
K_TREE_CTX ctx[1];
lookminingpayouts.userid = userid;
INIT_MININGPAYOUTS(&look);
look.data = (void *)(&lookminingpayouts);
mu_item = find_in_ktree(mu_root, &look, cmp_mu, ctx);
if (mu_item) {
DATA_MININGPAYOUTS(miningpayouts, mu_item);
miningpayouts->amount += diffacc;
} else {
K_WLOCK(mu_store);
mu_item = k_unlink_head(miningpayouts_free);
DATA_MININGPAYOUTS(miningpayouts, mu_item);
miningpayouts->userid = userid;
miningpayouts->amount = diffacc;
mu_root = add_to_ktree(mu_root, mu_item, cmp_mu);
k_add_head(mu_store, mu_item);
K_WUNLOCK(mu_store);
}
return mu_root;
}
/* Find the block_workinfoid of the block requested
then add all it's diffacc shares
then keep stepping back shares until diffacc_total matches or exceeds
the number required (diff_want) - this is begin_workinfoid
(also summarising diffacc per user)
then keep stepping back until we complete the current begin_workinfoid
(also summarising diffacc per user)
This will give us the total number of diff1 shares (diffacc_total)
to use for the payment calculations
The value of diff_want defaults to the block's network difficulty
(block_ndiff) but can be changed with diff_times and diff_add to:
block_ndiff * diff_times + diff_add
N.B. diff_times and diff_add can be zero, positive or negative
The pplns_elapsed time of the shares is from the createdate of the
begin_workinfoid that has shares accounted to the total,
up to the createdate of the last share
The user average hashrate would be:
diffacc_user * 2^32 / pplns_elapsed
PPLNS fraction of the block would be:
diffacc_user / diffacc_total
*/
static char *cmd_pplns(__maybe_unused PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *now, __maybe_unused char *by,
__maybe_unused char *code, __maybe_unused char *inet,
__maybe_unused tv_t *notcd, K_TREE *trf_root)
{
char reply[1024], tmp[1024], *buf;
size_t siz = sizeof(reply);
K_ITEM *i_height, *i_difftimes, *i_diffadd, *i_allowaged;
K_ITEM b_look, ss_look, *b_item, *w_item, *ss_item;
K_ITEM *mu_item, *wb_item, *u_item;
SHARESUMMARY looksharesummary, *sharesummary;
MININGPAYOUTS *miningpayouts;
WORKINFO *workinfo;
TRANSFER *transfer;
BLOCKS lookblocks, *blocks;
K_TREE *mu_root;
K_STORE *mu_store;
USERS *users;
int32_t height;
int64_t workinfoid, end_workinfoid;
int64_t begin_workinfoid;
int64_t share_count;
char tv_buf[DATE_BUFSIZ];
tv_t cd, begin_tv, block_tv, end_tv;
K_TREE_CTX ctx[1];
double ndiff, total, elapsed;
double diff_times = 1.0;
double diff_add = 0.0;
double diff_want;
bool allow_aged = false;
char ndiffbin[TXT_SML+1];
size_t len, off;
int rows;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
i_height = require_name(trf_root, "height", 1, NULL, reply, siz);
if (!i_height)
return strdup(reply);
TXT_TO_INT("height", transfer_data(i_height), height);
i_difftimes = optional_name(trf_root, "diff_times", 1, NULL);
if (i_difftimes)
TXT_TO_DOUBLE("diff_times", transfer_data(i_difftimes), diff_times);
i_diffadd = optional_name(trf_root, "diff_add", 1, NULL);
if (i_diffadd)
TXT_TO_DOUBLE("diff_add", transfer_data(i_diffadd), diff_add);
i_allowaged = optional_name(trf_root, "allow_aged", 1, NULL);
if (i_allowaged) {
DATA_TRANSFER(transfer, i_allowaged);
if (toupper(transfer->mvalue[0]) == TRUE_STR[0])
allow_aged = true;
}
cd.tv_sec = cd.tv_usec = 0L;
lookblocks.height = height + 1;
lookblocks.blockhash[0] = '\0';
INIT_BLOCKS(&b_look);
b_look.data = (void *)(&lookblocks);
K_RLOCK(blocks_free);
b_item = find_before_in_ktree(blocks_root, &b_look, cmp_blocks, ctx);
if (!b_item) {
K_RUNLOCK(blocks_free);
snprintf(reply, siz, "ERR.no block height %d", height);
return strdup(reply);
}
DATA_BLOCKS_NULL(blocks, b_item);
while (b_item && blocks->height == height) {
if (blocks->confirmed[0] == BLOCKS_CONFIRM)
break;
b_item = prev_in_ktree(ctx);
DATA_BLOCKS_NULL(blocks, b_item);
}
K_RUNLOCK(blocks_free);
if (!b_item || blocks->height != height) {
snprintf(reply, siz, "ERR.unconfirmed block %d", height);
return strdup(reply);
}
workinfoid = blocks->workinfoid;
copy_tv(&block_tv, &(blocks->createdate));
copy_tv(&end_tv, &(blocks->createdate));
w_item = find_workinfo(workinfoid);
if (!w_item) {
snprintf(reply, siz, "ERR.missing workinfo %"PRId64, workinfoid);
return strdup(reply);
}
DATA_WORKINFO(workinfo, w_item);
hex2bin(ndiffbin, workinfo->bits, 4);
ndiff = diff_from_nbits(ndiffbin);
diff_want = ndiff * diff_times + diff_add;
if (diff_want < 1.0) {
snprintf(reply, siz,
"ERR.invalid diff_want result %f",
diff_want);
return strdup(reply);
}
begin_workinfoid = 0;
share_count = 0;
total = 0;
looksharesummary.workinfoid = workinfoid;
looksharesummary.userid = MAXID;
looksharesummary.workername[0] = '\0';
INIT_SHARESUMMARY(&ss_look);
ss_look.data = (void *)(&looksharesummary);
K_RLOCK(sharesummary_free);
ss_item = find_before_in_ktree(sharesummary_workinfoid_root, &ss_look,
cmp_sharesummary_workinfoid, ctx);
if (!ss_item) {
K_RUNLOCK(sharesummary_free);
snprintf(reply, siz,
"ERR.no shares found with or before "
"workinfo %"PRId64,
workinfoid);
return strdup(reply);
}
DATA_SHARESUMMARY(sharesummary, ss_item);
mu_store = k_new_store(miningpayouts_free);
mu_root = new_ktree();
end_workinfoid = sharesummary->workinfoid;
/* add up all sharesummaries until >= diff_want
* also record the latest lastshare - that will be the end pplns time
* which will be >= block_tv */
while (ss_item && total < diff_want) {
switch (sharesummary->complete[0]) {
case SUMMARY_CONFIRM:
break;
case SUMMARY_COMPLETE:
if (allow_aged)
break;
default:
K_RUNLOCK(sharesummary_free);
snprintf(reply, siz,
"ERR.sharesummary1 not ready in "
"workinfo %"PRId64,
sharesummary->workinfoid);
goto shazbot;
}
share_count++;
total += (int64_t)(sharesummary->diffacc);
begin_workinfoid = sharesummary->workinfoid;
if (tv_newer(&end_tv, &(sharesummary->lastshare)))
copy_tv(&end_tv, &(sharesummary->lastshare));
mu_root = upd_add_mu(mu_root, mu_store,
sharesummary->userid,
(int64_t)(sharesummary->diffacc));
ss_item = prev_in_ktree(ctx);
DATA_SHARESUMMARY_NULL(sharesummary, ss_item);
}
// include all the rest of the sharesummaries with begin_workinfoid
while (ss_item && sharesummary->workinfoid == begin_workinfoid) {
switch (sharesummary->complete[0]) {
case SUMMARY_CONFIRM:
break;
case SUMMARY_COMPLETE:
if (allow_aged)
break;
default:
K_RUNLOCK(sharesummary_free);
snprintf(reply, siz,
"ERR.sharesummary2 not ready in "
"workinfo %"PRId64,
sharesummary->workinfoid);
goto shazbot;
}
share_count++;
total += (int64_t)(sharesummary->diffacc);
mu_root = upd_add_mu(mu_root, mu_store,
sharesummary->userid,
(int64_t)(sharesummary->diffacc));
ss_item = prev_in_ktree(ctx);
DATA_SHARESUMMARY_NULL(sharesummary, ss_item);
}
K_RUNLOCK(sharesummary_free);
if (total == 0.0) {
snprintf(reply, siz,
"ERR.total share diff 0 before workinfo %"PRId64,
workinfoid);
goto shazbot;
}
wb_item = find_workinfo(begin_workinfoid);
if (!wb_item) {
snprintf(reply, siz, "ERR.missing begin workinfo record! %"PRId64, workinfoid);
goto shazbot;
}
DATA_WORKINFO(workinfo, wb_item);
copy_tv(&begin_tv, &(workinfo->createdate));
/* Elapsed is from the start of the first workinfoid used,
* to the time of the last share counted -
* which can be after the block, but must have the same workinfoid as
* the block, if it is after the block
* All shares accepted in all workinfoids after the block's workinfoid
* will not be creditied to this block no matter what the height
* of their workinfoid is - but will be candidates for the next block */
elapsed = tvdiff(&end_tv, &begin_tv);
APPEND_REALLOC_INIT(buf, off, len);
APPEND_REALLOC(buf, off, len, "ok.");
snprintf(tmp, sizeof(tmp), "block=%d%c", height, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "block_hash=%s%c", blocks->blockhash, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "block_reward=%"PRId64"%c", blocks->reward, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "workername=%s%c", blocks->workername, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "nonce=%s%c", blocks->nonce, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "begin_workinfoid=%"PRId64"%c", begin_workinfoid, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "block_workinfoid=%"PRId64"%c", workinfoid, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "end_workinfoid=%"PRId64"%c", end_workinfoid, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "diffacc_total=%.0f%c", total, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "pplns_elapsed=%f%c", elapsed, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
rows = 0;
mu_item = first_in_ktree(mu_root, ctx);
while (mu_item) {
DATA_MININGPAYOUTS(miningpayouts, mu_item);
K_RLOCK(users_free);
u_item = find_userid(miningpayouts->userid);
K_RUNLOCK(users_free);
if (u_item) {
K_ITEM *pa_item;
PAYMENTADDRESSES *pa;
char *payaddress;
pa_item = find_paymentaddresses(miningpayouts->userid);
if (pa_item) {
DATA_PAYMENTADDRESSES(pa, pa_item);
payaddress = pa->payaddress;
} else
payaddress = "none";
DATA_USERS(users, u_item);
snprintf(tmp, sizeof(tmp),
"user:%d=%s%cpayaddress:%d=%s%c",
rows, users->username, FLDSEP,
rows, payaddress, FLDSEP);
} else {
snprintf(tmp, sizeof(tmp),
"user:%d=%"PRId64"%cpayaddress:%d=none%c",
rows, miningpayouts->userid, FLDSEP,
rows, FLDSEP);
}
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp),
"diffacc_user:%d=%"PRId64"%c",
rows,
miningpayouts->amount,
FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
mu_item = next_in_ktree(ctx);
rows++;
}
snprintf(tmp, sizeof(tmp),
"rows=%d%cflds=%s%c",
rows, FLDSEP,
"user,diffacc_user,payaddress", FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s%c",
"Users", FLDSEP, "", FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
tv_to_buf(&begin_tv, tv_buf, sizeof(tv_buf));
snprintf(tmp, sizeof(tmp), "begin_stamp=%s%c", tv_buf, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "begin_epoch=%ld%c", begin_tv.tv_sec, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
tv_to_buf(&block_tv, tv_buf, sizeof(tv_buf));
snprintf(tmp, sizeof(tmp), "block_stamp=%s%c", tv_buf, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "block_epoch=%ld%c", block_tv.tv_sec, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
tv_to_buf(&end_tv, tv_buf, sizeof(tv_buf));
snprintf(tmp, sizeof(tmp), "end_stamp=%s%c", tv_buf, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "end_epoch=%ld%c", end_tv.tv_sec, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "block_ndiff=%f%c", ndiff, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "diff_times=%f%c", diff_times, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "diff_add=%f%c", diff_add, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "diff_want=%f%c", diff_want, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "share_count=%"PRId64, share_count);
APPEND_REALLOC(buf, off, len, tmp);
mu_root = free_ktree(mu_root, NULL);
K_WLOCK(mu_store);
k_list_transfer_to_head(mu_store, miningpayouts_free);
K_WUNLOCK(mu_store);
mu_store = k_free_store(mu_store);
LOGDEBUG("%s.ok.pplns.%s", id, buf);
return buf;
shazbot:
mu_root = free_ktree(mu_root, NULL);
K_WLOCK(mu_store);
k_list_transfer_to_head(mu_store, miningpayouts_free);
K_WUNLOCK(mu_store);
mu_store = k_free_store(mu_store);
return strdup(reply);
}
static char *cmd_dsp(__maybe_unused PGconn *conn, __maybe_unused char *cmd,
char *id, __maybe_unused tv_t *now,
__maybe_unused char *by, __maybe_unused char *code,
__maybe_unused char *inet, __maybe_unused tv_t *notcd,
__maybe_unused K_TREE *trf_root)
{
__maybe_unused K_ITEM *i_file;
__maybe_unused char reply[1024] = "";
__maybe_unused size_t siz = sizeof(reply);
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
// WARNING: This is a gaping security hole - only use in development
LOGDEBUG("%s.disabled.dsp", id);
return strdup("disabled.dsp");
/*
i_file = require_name(trf_root, "file", 1, NULL, reply, siz);
if (!i_file)
return strdup(reply);
dsp_ktree(blocks_free, blocks_root, transfer_data(i_file), NULL);
dsp_ktree(transfer_free, trf_root, transfer_data(i_file), NULL);
dsp_ktree(sharesummary_free, sharesummary_root, transfer_data(i_file), NULL);
dsp_ktree(userstats_free, userstats_root, transfer_data(i_file), NULL);
LOGDEBUG("%s.ok.dsp.file='%s'", id, transfer_data(i_file));
return strdup("ok.dsp");
*/
}
static char *cmd_stats(__maybe_unused PGconn *conn, char *cmd, char *id,
__maybe_unused tv_t *now, __maybe_unused char *by,
__maybe_unused char *code, __maybe_unused char *inet,
__maybe_unused tv_t *notcd, __maybe_unused K_TREE *trf_root)
{
char tmp[1024], *buf;
size_t len, off;
uint64_t ram, tot = 0;
K_LIST *klist;
int rows = 0;
LOGDEBUG("%s(): cmd '%s'", __func__, cmd);
APPEND_REALLOC_INIT(buf, off, len);
APPEND_REALLOC(buf, off, len, "ok.");
// Doesn't include blob memory
// - average transactiontree length of ~119k I have is ~28k (>3.3GB)
#define USEINFO(_obj, _stores, _trees) \
klist = _obj ## _free; \
ram = sizeof(K_LIST) + _stores * sizeof(K_STORE) + \
klist->allocate * klist->item_mem_count * klist->siz + \
sizeof(K_TREE) * (klist->total - klist->count) * _trees; \
snprintf(tmp, sizeof(tmp), \
"name:%d=" #_obj "%cinitial:%d=%d%callocated:%d=%d%c" \
"store:%d=%d%ctrees:%d=%d%cram:%d=%"PRIu64"%c" \
"cull:%d=%d%c", \
rows, FLDSEP, \
rows, klist->allocate, FLDSEP, \
rows, klist->total, FLDSEP, \
rows, klist->total - klist->count, FLDSEP, \
rows, _trees, FLDSEP, \
rows, ram, FLDSEP, \
rows, klist->cull_count, FLDSEP); \
APPEND_REALLOC(buf, off, len, tmp); \
tot += ram; \
rows++;
USEINFO(users, 1, 2);
USEINFO(workers, 1, 1);
USEINFO(paymentaddresses, 1, 1);
USEINFO(payments, 1, 1);
USEINFO(idcontrol, 1, 0);
USEINFO(workinfo, 1, 1);
USEINFO(shares, 1, 1);
USEINFO(shareerrors, 1, 1);
USEINFO(sharesummary, 1, 2);
USEINFO(blocks, 1, 1);
USEINFO(auths, 1, 1);
USEINFO(poolstats, 1, 1);
USEINFO(userstats, 4, 2);
USEINFO(workerstatus, 1, 1);
USEINFO(workqueue, 1, 0);
USEINFO(transfer, 0, 0);
USEINFO(logqueue, 1, 0);
snprintf(tmp, sizeof(tmp), "totalram=%"PRIu64"%c", tot, FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp),
"rows=%d%cflds=%s%c",
rows, FLDSEP,
"name,initial,allocated,store,trees,ram,cull", FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Stats", FLDSEP, "");
APPEND_REALLOC(buf, off, len, tmp);
LOGDEBUG("%s.ok.%s...", id, cmd);
return buf;
}
// TODO: limit access by having seperate sockets for each
#define ACCESS_POOL "p"
#define ACCESS_SYSTEM "s"
#define ACCESS_WEB "w"
#define ACCESS_PROXY "x"
#define ACCESS_CKDB "c"
/* The socket command format is as follows:
* Basic structure:
* cmd.ID.fld1=value1 FLDSEP fld2=value2 FLDSEP fld3=...
* cmd is the cmd_str from the table below
* ID is a string of anything but '.' - preferably just digits and/or letters
* FLDSEP is a single character macro - defined in the code near the top
* no spaces around FLDSEP - they are added above for readability
* i.e. it's really: cmd.ID.fld1=value1FLDSEPfld2...
* fldN names cannot contain '=' or FLDSEP
* valueN values cannot contain FLDSEP except for the json field (see below)
*
* The reply will be ID.timestamp.status.information...
* Status 'ok' means it succeeded
* Some cmds you can optionally send as just 'cmd' if 'noid' below is true
* then the reply will be .timestamp.status.information
* i.e. a zero length 'ID' at the start of the reply
*
* Data from ckpool starts with a fld1: json={...} of field data
* This is assumed to be the only field data sent and any other fields after
* it will cause a json error
* Any fields before it will circumvent the json interpretation of {...} and
* the full json in {...} will be stored as text in TRANSFER under the name
* 'json' - which will (usually) mean the command will fail if it requires
* actual field data
*
* Examples of the commands not from ckpool with an example reply
* STAMP is the unix timestamp in seconds
* With no ID:
* ping
* .STAMP.ok.pong
*
* shutdown
* .STAMP.ok.exiting
*
* With an ID
* In each case the ID in these examples, also returned, is 'ID' which can
* of course be most any string, as stated above
* For commands with multiple fld=value the space between them must be typed
* as a TAB
* ping.ID
* ID.STAMP.ok.pong
*
* newid.ID.idname=fooid idvalue=1234
* ID.STAMP.ok.added fooid 1234
*
* loglevel is a special case to make it quick and easy to use:
* loglevel.ID
* sets the loglevel to atoi(ID)
* Without an ID, it just reports the current value
*/
static struct CMDS {
enum cmd_values cmd_val;
char *cmd_str;
bool noid; // doesn't require an id
bool createdate; // requires a createdate
char *(*func)(PGconn *, char *, char *, tv_t *, char *, char *,
char *, tv_t *, K_TREE *);
char *access;
} cmds[] = {
{ CMD_SHUTDOWN, "shutdown", true, false, NULL, ACCESS_SYSTEM },
{ CMD_PING, "ping", true, false, NULL, ACCESS_SYSTEM ACCESS_POOL ACCESS_WEB },
{ CMD_VERSION, "version", true, false, NULL, ACCESS_SYSTEM ACCESS_POOL ACCESS_WEB },
{ CMD_LOGLEVEL, "loglevel", true, false, NULL, ACCESS_SYSTEM },
{ CMD_SHARELOG, STR_WORKINFO, false, true, cmd_sharelog, ACCESS_POOL },
{ CMD_SHARELOG, STR_SHARES, false, true, cmd_sharelog, ACCESS_POOL },
{ CMD_SHARELOG, STR_SHAREERRORS, false, true, cmd_sharelog, ACCESS_POOL },
{ CMD_SHARELOG, STR_AGEWORKINFO, false, true, cmd_sharelog, ACCESS_POOL },
{ CMD_AUTH, "authorise", false, true, cmd_auth, ACCESS_POOL },
{ CMD_ADDRAUTH, "addrauth", false, true, cmd_addrauth, ACCESS_POOL },
{ CMD_ADDUSER, "adduser", false, false, cmd_adduser, ACCESS_WEB },
{ CMD_NEWPASS, "newpass", false, false, cmd_newpass, ACCESS_WEB },
{ CMD_CHKPASS, "chkpass", false, false, cmd_chkpass, ACCESS_WEB },
{ CMD_USERSET, "usersettings", false, false, cmd_userset, ACCESS_WEB },
{ CMD_POOLSTAT, "poolstats", false, true, cmd_poolstats, ACCESS_POOL },
{ CMD_USERSTAT, "userstats", false, true, cmd_userstats, ACCESS_POOL },
{ CMD_BLOCK, "block", false, true, cmd_blocks, ACCESS_POOL },
{ CMD_BLOCKLIST,"blocklist", false, false, cmd_blocklist, ACCESS_WEB },
{ CMD_BLOCKSTATUS,"blockstatus",false, false, cmd_blockstatus,ACCESS_WEB },
{ CMD_NEWID, "newid", false, false, cmd_newid, ACCESS_SYSTEM },
{ CMD_PAYMENTS, "payments", false, false, cmd_payments, ACCESS_WEB },
{ CMD_WORKERS, "workers", false, false, cmd_workers, ACCESS_WEB },
{ CMD_ALLUSERS, "allusers", false, false, cmd_allusers, ACCESS_WEB },
{ CMD_HOMEPAGE, "homepage", false, false, cmd_homepage, ACCESS_WEB },
{ CMD_GETATTS, "getatts", false, false, cmd_getatts, ACCESS_WEB },
{ CMD_SETATTS, "setatts", false, false, cmd_setatts, ACCESS_WEB },
{ CMD_DSP, "dsp", false, false, cmd_dsp, ACCESS_SYSTEM },
{ CMD_STATS, "stats", true, false, cmd_stats, ACCESS_SYSTEM },
{ CMD_PPLNS, "pplns", false, false, cmd_pplns, ACCESS_SYSTEM },
{ CMD_END, NULL, false, false, NULL, NULL }
};
static enum cmd_values breakdown(K_TREE **trf_root, K_STORE **trf_store,
char *buf, int *which_cmds, char *cmd,
char *id, tv_t *cd)
{
char reply[1024] = "";
TRANSFER *transfer;
K_TREE_CTX ctx[1];
K_ITEM *item;
char *cmdptr, *idptr, *next, *eq;
char *data = NULL;
bool noid = false;
*trf_root = NULL;
*trf_store = NULL;
*which_cmds = CMD_UNSET;
*cmd = *id = '\0';
cd->tv_sec = 0;
cd->tv_usec = 0;
cmdptr = strdup(buf);
idptr = strchr(cmdptr, '.');
if (!idptr || !*idptr)
noid = true;
else {
*(idptr++) = '\0';
data = strchr(idptr, '.');
if (data)
*(data++) = '\0';
STRNCPYSIZ(id, idptr, ID_SIZ);
}
STRNCPYSIZ(cmd, cmdptr, CMD_SIZ);
for (*which_cmds = 0; cmds[*which_cmds].cmd_val != CMD_END; (*which_cmds)++) {
if (strcasecmp(cmd, cmds[*which_cmds].cmd_str) == 0)
break;
}
if (cmds[*which_cmds].cmd_val == CMD_END) {
LOGERR("Listener received unknown command: '%s'", buf);
free(cmdptr);
return CMD_REPLY;
}
if (noid) {
if (cmds[*which_cmds].noid) {
*id = '\0';
free(cmdptr);
return cmds[*which_cmds].cmd_val;
}
STRNCPYSIZ(id, cmdptr, ID_SIZ);
LOGERR("Listener received invalid (noid) message: '%s'", buf);
free(cmdptr);
return CMD_REPLY;
}
*trf_root = new_ktree();
*trf_store = k_new_store(transfer_free);
next = data;
if (next && strncmp(next, JSON_TRANSFER, JSON_TRANSFER_LEN) == 0) {
json_t *json_data;
json_error_t err_val;
void *json_iter;
const char *json_key, *json_str;
json_t *json_value;
int json_typ;
size_t siz;
bool ok;
next += JSON_TRANSFER_LEN;
json_data = json_loads(next, JSON_DISABLE_EOF_CHECK, &err_val);
if (!json_data) {
/* This REALLY shouldn't ever get an error since the input
* is a json generated string
* If that happens then dump lots of information */
char *text = safe_text(next);
LOGERR("Json decode error from command: '%s' "
"json_err=(%d:%d:%d)%s:%s input='%s'",
cmd, err_val.line, err_val.column,
err_val.position, err_val.source,
err_val.text, text);
free(text);
free(cmdptr);
return CMD_REPLY;
}
json_iter = json_object_iter(json_data);
K_WLOCK(transfer_free);
while (json_iter) {
json_key = json_object_iter_key(json_iter);
json_value = json_object_iter_value(json_iter);
item = k_unlink_head(transfer_free);
DATA_TRANSFER(transfer, item);
ok = true;
json_typ = json_typeof(json_value);
switch (json_typ) {
case JSON_STRING:
json_str = json_string_value(json_value);
siz = strlen(json_str);
if (siz >= sizeof(transfer->svalue))
transfer->mvalue = strdup(json_str);
else {
STRNCPY(transfer->svalue, json_str);
transfer->mvalue = transfer->svalue;
}
break;
case JSON_REAL:
snprintf(transfer->svalue,
sizeof(transfer->svalue),
"%f", json_real_value(json_value));
transfer->mvalue = transfer->svalue;
break;
case JSON_INTEGER:
snprintf(transfer->svalue,
sizeof(transfer->svalue),
"%"PRId64,
(int64_t)json_integer_value(json_value));
transfer->mvalue = transfer->svalue;
break;
case JSON_TRUE:
case JSON_FALSE:
snprintf(transfer->svalue,
sizeof(transfer->svalue),
"%s", (json_typ == JSON_TRUE) ?
TRUE_STR : FALSE_STR);
transfer->mvalue = transfer->svalue;
break;
case JSON_ARRAY:
{
/* only one level array of strings for now (merkletree)
* ignore other data */
size_t i, len, off, count = json_array_size(json_value);
json_t *json_element;
bool first = true;
APPEND_REALLOC_INIT(transfer->mvalue, off, len);
for (i = 0; i < count; i++) {
json_element = json_array_get(json_value, i);
if (json_is_string(json_element)) {
json_str = json_string_value(json_element);
siz = strlen(json_str);
if (first)
first = false;
else {
APPEND_REALLOC(transfer->mvalue,
off, len, " ");
}
APPEND_REALLOC(transfer->mvalue,
off, len, json_str);
} else
LOGERR("%s() unhandled json type %d in array %s"
" in cmd %s", __func__,
json_typ, json_key, cmd);
}
}
break;
default:
LOGERR("%s() unhandled json type %d in cmd %s",
__func__, json_typ, cmd);
ok = false;
break;
}
if (ok)
STRNCPY(transfer->name, json_key);
if (!ok || find_in_ktree(*trf_root, item, cmp_transfer, ctx)) {
if (transfer->mvalue != transfer->svalue)
free(transfer->mvalue);
k_add_head(transfer_free, item);
} else {
*trf_root = add_to_ktree(*trf_root, item, cmp_transfer);
k_add_head(*trf_store, item);
}
json_iter = json_object_iter_next(json_data, json_iter);
}
K_WUNLOCK(transfer_free);
json_decref(json_data);
} else {
K_WLOCK(transfer_free);
while (next && *next) {
data = next;
next = strchr(data, FLDSEP);
if (next)
*(next++) = '\0';
eq = strchr(data, '=');
if (!eq)
eq = EMPTY;
else
*(eq++) = '\0';
item = k_unlink_head(transfer_free);
DATA_TRANSFER(transfer, item);
STRNCPY(transfer->name, data);
STRNCPY(transfer->svalue, eq);
transfer->mvalue = transfer->svalue;
if (find_in_ktree(*trf_root, item, cmp_transfer, ctx)) {
if (transfer->mvalue != transfer->svalue)
free(transfer->mvalue);
k_add_head(transfer_free, item);
} else {
*trf_root = add_to_ktree(*trf_root, item, cmp_transfer);
k_add_head(*trf_store, item);
}
}
K_WUNLOCK(transfer_free);
}
if (cmds[*which_cmds].createdate) {
item = require_name(*trf_root, "createdate", 10, NULL, reply, sizeof(reply));
if (!item)
return CMD_REPLY;
DATA_TRANSFER(transfer, item);
txt_to_ctv("createdate", transfer->mvalue, cd, sizeof(*cd));
if (cd->tv_sec == 0) {
LOGERR("%s(): failed, %s has invalid createdate '%s'",
__func__, cmdptr, transfer->mvalue);
free(cmdptr);
return CMD_REPLY;
}
if (confirm_check_createdate)
check_createdate_ccl(cmd, cd);
}
free(cmdptr);
return cmds[*which_cmds].cmd_val;
}
static void summarise_blocks()
{
K_ITEM *b_item, *b_prev, *wi_item, ss_look, *ss_item;
K_TREE_CTX ctx[1], ss_ctx[1];
double diffacc, diffinv, shareacc, shareinv;
tv_t now, elapsed_start, elapsed_finish;
int64_t elapsed, wi_start, wi_finish;
BLOCKS *blocks, *prev_blocks;
WORKINFO *prev_workinfo;
SHARESUMMARY looksharesummary, *sharesummary;
int32_t hi, prev_hi;
bool ok;
setnow(&now);
K_RLOCK(blocks_free);
// Find the oldest, stats pending, not new, block
b_item = first_in_ktree(blocks_root, ctx);
while (b_item) {
DATA_BLOCKS(blocks, b_item);
if (CURRENT(&(blocks->expirydate)) &&
blocks->statsconfirmed[0] == BLOCKS_STATSPENDING &&
blocks->confirmed[0] != BLOCKS_NEW)
break;
b_item = next_in_ktree(ctx);
}
K_RUNLOCK(blocks_free);
// None
if (!b_item)
return;
wi_finish = blocks->workinfoid;
hi = 0;
K_RLOCK(workinfo_free);
if (workinfo_current) {
WORKINFO *wic;
DATA_WORKINFO(wic, workinfo_current);
hi = coinbase1height(wic->coinbase1);
}
K_RUNLOCK(workinfo_free);
// Wait at least for the (badly named) '2nd' confirm
if (hi == 0 || blocks->height >= (hi - 1))
return;
diffacc = diffinv = shareacc = shareinv = 0;
elapsed = 0;
K_RLOCK(blocks_free);
b_prev = find_prev_blocks(blocks->height);
K_RUNLOCK(blocks_free);
if (!b_prev) {
wi_start = 0;
elapsed_start.tv_sec = elapsed_start.tv_usec = 0L;
prev_hi = 0;
} else {
DATA_BLOCKS(prev_blocks, b_prev);
wi_start = prev_blocks->workinfoid;
wi_item = find_workinfo(wi_start);
if (!wi_item) {
// This will repeat until fixed ...
LOGERR("%s() block %d, but prev %d wid "
"%"PRId64" is missing",
__func__, blocks->height,
prev_blocks->height,
prev_blocks->workinfoid);
return;
}
DATA_WORKINFO(prev_workinfo, wi_item);
copy_tv(&elapsed_start, &(prev_workinfo->createdate));
prev_hi = prev_blocks->height;
}
elapsed_finish.tv_sec = elapsed_finish.tv_usec = 0L;
// Add up the sharesummaries, abort if any SUMMARY_NEW
looksharesummary.workinfoid = wi_finish;
looksharesummary.userid = MAXID;
looksharesummary.workername[0] = '\0';
INIT_SHARESUMMARY(&ss_look);
ss_look.data = (void *)(&looksharesummary);
K_RLOCK(sharesummary_free);
ss_item = find_before_in_ktree(sharesummary_workinfoid_root, &ss_look,
cmp_sharesummary_workinfoid, ss_ctx);
if (!ss_item) {
K_RUNLOCK(sharesummary_free);
// This will repeat each call here until fixed ...
LOGERR("%s() block %d, prev %d no sharesummaries "
"on or before %"PRId64,
__func__, blocks->height,
prev_hi, wi_finish);
return;
}
DATA_SHARESUMMARY(sharesummary, ss_item);
while (ss_item && sharesummary->workinfoid > wi_start) {
if (sharesummary->complete[0] == SUMMARY_NEW) {
// Not aged yet
K_RUNLOCK(sharesummary_free);
return;
}
if (elapsed_start.tv_sec == 0 ||
!tv_newer(&elapsed_start, &(sharesummary->firstshare))) {
copy_tv(&elapsed_start, &(sharesummary->firstshare));
}
if (tv_newer(&elapsed_finish, &(sharesummary->lastshare)))
copy_tv(&elapsed_finish, &(sharesummary->lastshare));
diffacc += sharesummary->diffacc;
diffinv += sharesummary->diffsta + sharesummary->diffdup +
sharesummary->diffhi + sharesummary-> diffrej;
shareacc += sharesummary->shareacc;
shareinv += sharesummary->sharesta + sharesummary->sharedup +
sharesummary->sharehi + sharesummary-> sharerej;
ss_item = prev_in_ktree(ss_ctx);
DATA_SHARESUMMARY_NULL(sharesummary, ss_item);
}
K_RUNLOCK(sharesummary_free);
elapsed = (int64_t)(tvdiff(&elapsed_finish, &elapsed_start) + 0.5);
ok = blocks_stats(NULL, blocks->height, blocks->blockhash,
diffacc, diffinv, shareacc, shareinv, elapsed,
by_default, (char *)__func__, inet_default, &now);
if (ok) {
LOGWARNING("%s() block %d, stats confirmed "
"%0.f/%.0f/%.0f/%.0f/%"PRId64,
__func__, blocks->height,
diffacc, diffinv, shareacc, shareinv, elapsed);
} else {
LOGERR("%s() block %d, failed to confirm stats",
__func__, blocks->height);
}
}
static void summarise_poolstats()
{
// TODO
}
// TODO: daily
// TODO: consider limiting how much/how long this processes each time
static void summarise_userstats()
{
K_TREE_CTX ctx[1], ctx2[1];
K_ITEM *first, *last, *new, *next, *tmp;
USERSTATS *userstats, *us_first, *us_last, *us_next;
double statrange, factor;
bool locked, upgrade;
tv_t now, process, when;
PGconn *conn = NULL;
int count;
char error[1024];
char tvbuf1[DATE_BUFSIZ], tvbuf2[DATE_BUFSIZ];
upgrade = false;
locked = false;
while (1764) {
error[0] = '\0';
setnow(&now);
upgrade = false;
locked = true;
K_ILOCK(userstats_free);
// confirm_summaries() doesn't call this
if (!reloading)
copy_tv(&process, &now);
else {
// During reload, base the check date on the newest statsdate
last = last_in_ktree(userstats_statsdate_root, ctx);
if (!last)
break;
DATA_USERSTATS(us_last, last);
copy_tv(&process, &us_last->statsdate);
}
first = first_in_ktree(userstats_statsdate_root, ctx);
DATA_USERSTATS_NULL(us_first, first);
// Oldest non DB stat
// TODO: make the index start with summarylevel? so can find faster
while (first && us_first->summarylevel[0] != SUMMARY_NONE) {
first = next_in_ktree(ctx);
DATA_USERSTATS_NULL(us_first, first);
}
if (!first)
break;
statrange = tvdiff(&process, &(us_first->statsdate));
// Is there data ready for summarising?
if (statrange <= USERSTATS_AGE)
break;
copy_tv(&when, &(us_first->statsdate));
/* Convert when to the start of the timeframe after the one it is in
* assume timeval ignores leapseconds ... */
when.tv_sec = when.tv_sec - (when.tv_sec % USERSTATS_DB_S) + USERSTATS_DB_S;
when.tv_usec = 0;
// Is the whole timerange up to before 'when' ready for summarising?
statrange = tvdiff(&process, &when);
if (statrange < USERSTATS_AGE)
break;
next = next_in_ktree(ctx);
upgrade = true;
K_ULOCK(userstats_free);
new = k_unlink_head(userstats_free);
DATA_USERSTATS(userstats, new);
memcpy(userstats, us_first, sizeof(USERSTATS));
userstats_root = remove_from_ktree(userstats_root, first, cmp_userstats, ctx2);
userstats_statsdate_root = remove_from_ktree(userstats_statsdate_root, first,
cmp_userstats_statsdate, ctx2);
k_unlink_item(userstats_store, first);
k_add_head(userstats_summ, first);
count = 1;
while (next) {
DATA_USERSTATS(us_next, next);
statrange = tvdiff(&when, &(us_next->statsdate));
if (statrange <= 0)
break;
tmp = next_in_ktree(ctx);
if (us_next->summarylevel[0] == SUMMARY_NONE &&
us_next->userid == userstats->userid &&
strcmp(us_next->workername, userstats->workername) == 0) {
count++;
userstats->hashrate += us_next->hashrate;
userstats->hashrate5m += us_next->hashrate5m;
userstats->hashrate1hr += us_next->hashrate1hr;
userstats->hashrate24hr += us_next->hashrate24hr;
if (userstats->elapsed > us_next->elapsed)
userstats->elapsed = us_next->elapsed;
userstats->summarycount += us_next->summarycount;
userstats_root = remove_from_ktree(userstats_root, next, cmp_userstats, ctx2);
userstats_statsdate_root = remove_from_ktree(userstats_statsdate_root, next,
cmp_userstats_statsdate, ctx2);
k_unlink_item(userstats_store, next);
k_add_head(userstats_summ, next);
}
next = tmp;
}
// Can temporarily release the lock since all our data is now not part of the lock
if (upgrade)
K_WUNLOCK(userstats_free);
else
K_IUNLOCK(userstats_free);
upgrade = false;
locked = false;
if (userstats->hashrate5m > 0.0 || userstats->hashrate1hr > 0.0)
userstats->idle = false;
else
userstats->idle = true;
userstats->summarylevel[0] = SUMMARY_DB;
userstats->summarylevel[1] = '\0';
// Expect 6 per poolinstance
factor = (double)count / 6.0;
userstats->hashrate *= factor;
userstats->hashrate5m *= factor;
userstats->hashrate1hr *= factor;
userstats->hashrate24hr *= factor;
copy_tv(&(userstats->statsdate), &when);
// Stats to the end of this timeframe
userstats->statsdate.tv_sec -= 1;
userstats->statsdate.tv_usec = 999999;
// This is simply when it was written, so 'now' is fine
SIMPLEDATEDEFAULT(userstats, &now);
if (!conn)
conn = dbconnect();
if (!userstats_add_db(conn, userstats)) {
/* This should only happen if a restart finds data
that wasn't found during the reload but is in
the same timeframe as DB data
i.e. it shouldn't happen, but keep the summary anyway */
when.tv_sec -= USERSTATS_DB_S;
tv_to_buf(&when, tvbuf1, sizeof(tvbuf1));
tv_to_buf(&(userstats->statsdate), tvbuf2, sizeof(tvbuf2));
snprintf(error, sizeof(error),
"Userid %"PRId64" Worker %s, %d userstats record%s "
"discarded from %s to %s",
userstats->userid,
userstats->workername,
count, (count == 1 ? "" : "s"),
tvbuf1, tvbuf2);
}
// The flags are not needed
//upgrade = true;
//locked = true;
K_WLOCK(userstats_free);
k_list_transfer_to_tail(userstats_summ, userstats_free);
k_add_head(userstats_store, new);
userstats_root = add_to_ktree(userstats_root, new, cmp_userstats);
userstats_statsdate_root = add_to_ktree(userstats_statsdate_root, new,
cmp_userstats_statsdate);
K_WUNLOCK(userstats_free);
//locked = false;
//upgrade = false;
if (error[0])
LOGERR(error);
}
if (locked) {
if (upgrade)
K_WUNLOCK(userstats_free);
else
K_IUNLOCK(userstats_free);
}
if (conn)
PQfinish(conn);
}
static void *summariser(__maybe_unused void *arg)
{
pthread_detach(pthread_self());
rename_proc("db_summariser");
while (!everyone_die && !db_load_complete)
cksleep_ms(42);
while (!everyone_die) {
sleep(5);
if (!everyone_die)
summarise_blocks();
sleep(4);
if (!everyone_die)
summarise_poolstats();
sleep(4);
if (!everyone_die)
summarise_userstats();
}
return NULL;
}
static void *logger(__maybe_unused void *arg)
{
K_ITEM *lq_item;
LOGQUEUE *lq;
char buf[128];
tv_t now;
pthread_detach(pthread_self());
snprintf(buf, sizeof(buf), "db%s_logger", dbcode);
rename_proc(buf);
setnow(&now);
snprintf(buf, sizeof(buf), "logstart.%ld,%ld",
now.tv_sec, now.tv_usec);
LOGFILE(buf);
while (!everyone_die) {
K_WLOCK(logqueue_free);
lq_item = k_unlink_head(logqueue_store);
K_WUNLOCK(logqueue_free);
while (lq_item) {
DATA_LOGQUEUE(lq, lq_item);
LOGFILE(lq->msg);
free(lq->msg);
K_WLOCK(logqueue_free);
k_add_head(logqueue_free, lq_item);
if (!everyone_die)
lq_item = k_unlink_head(logqueue_store);
else
lq_item = NULL;
K_WUNLOCK(logqueue_free);
}
cksleep_ms(42);
}
K_WLOCK(logqueue_free);
setnow(&now);
snprintf(buf, sizeof(buf), "logstopping.%d.%ld,%ld",
logqueue_store->count,
now.tv_sec, now.tv_usec);
LOGFILE(buf);
while((lq_item = k_unlink_head(logqueue_store))) {
DATA_LOGQUEUE(lq, lq_item);
LOGFILE(lq->msg);
}
K_WUNLOCK(logqueue_free);
setnow(&now);
snprintf(buf, sizeof(buf), "logstop.%ld,%ld",
now.tv_sec, now.tv_usec);
LOGFILE(buf);
return NULL;
}
#define STORELASTREPLY(_cmd) do { \
if (last_ ## _cmd) \
free(last_ ## _cmd); \
last_ ## _cmd = buf; \
buf = NULL; \
if (reply_ ## _cmd) \
free(reply_ ## _cmd); \
reply_ ## _cmd = rep; \
} while (0)
static void *socketer(__maybe_unused void *arg)
{
proc_instance_t *pi = (proc_instance_t *)arg;
unixsock_t *us = &pi->us;
char *end, *ans = NULL, *rep = NULL, *buf = NULL, *dot;
char cmd[CMD_SIZ+1], id[ID_SIZ+1], reply[1024+1];
char *last_auth = NULL, *reply_auth = NULL;
char *last_addrauth = NULL, *reply_addrauth = NULL;
char *last_chkpass = NULL, *reply_chkpass = NULL;
char *last_adduser = NULL, *reply_adduser = NULL;
char *last_newpass = NULL, *reply_newpass = NULL;
char *last_userset = NULL, *reply_userset = NULL;
char *last_newid = NULL, *reply_newid = NULL;
char *last_setatts = NULL, *reply_setatts = NULL;
char *last_web = NULL, *reply_web = NULL;
char *reply_last, duptype[CMD_SIZ+1];
enum cmd_values cmdnum;
int sockd, which_cmds;
WORKQUEUE *workqueue;
TRANSFER *transfer;
K_STORE *trf_store;
K_TREE *trf_root;
K_ITEM *item;
size_t siz;
tv_t now, cd;
bool dup, want_first;
int loglevel, oldloglevel;
pthread_detach(pthread_self());
rename_proc("db_socketer");
while (!everyone_die && !db_auths_complete)
cksem_mswait(&socketer_sem, 420);
want_first = true;
while (!everyone_die) {
if (buf)
dealloc(buf);
sockd = accept(us->sockd, NULL, NULL);
if (sockd < 0) {
LOGERR("Failed to accept on socket in listener");
break;
}
cmdnum = CMD_UNSET;
trf_root = NULL;
trf_store = NULL;
buf = recv_unix_msg(sockd);
// Once we've read the message
setnow(&now);
if (buf) {
end = buf + strlen(buf) - 1;
// strip trailing \n and \r
while (end >= buf && (*end == '\n' || *end == '\r'))
*(end--) = '\0';
}
if (!buf || !*buf) {
// An empty message wont get a reply
if (!buf)
LOGWARNING("Failed to get message in listener");
else
LOGWARNING("Empty message in listener");
} else {
/* For duplicates:
* Queued pool messages are handled by the queue code
* but since they reply ok.queued that message can
* be returned every time here
* System: repeat process them
* Web: current php web sends a timestamp of seconds
* so duplicate code will only trigger if the same
* message is sent within the same second and thus
* will effectively reduce the processing load for
* sequential duplicates
* As per the 'if' list below,
* remember individual last messages and replies and
* repeat the reply without reprocessing the message
* The rest are remembered in the same buffer 'web'
* so a duplicate will not be seen if another 'web'
* command arrived between two duplicate commands
*/
dup = false;
// These are ordered approximately most likely first
if (last_auth && strcmp(last_auth, buf) == 0) {
reply_last = reply_auth;
dup = true;
} else if (last_chkpass && strcmp(last_chkpass, buf) == 0) {
reply_last = reply_chkpass;
dup = true;
} else if (last_adduser && strcmp(last_adduser, buf) == 0) {
reply_last = reply_adduser;
dup = true;
} else if (last_newpass && strcmp(last_newpass, buf) == 0) {
reply_last = reply_newpass;
dup = true;
} else if (last_newid && strcmp(last_newid, buf) == 0) {
reply_last = reply_newid;
dup = true;
} else if (last_addrauth && strcmp(last_addrauth, buf) == 0) {
reply_last = reply_addrauth;
dup = true;
} else if (last_userset && strcmp(last_userset, buf) == 0) {
reply_last = reply_userset;
dup = true;
} else if (last_setatts && strcmp(last_setatts, buf) == 0) {
reply_last = reply_setatts;
dup = true;
} else if (last_web && strcmp(last_web, buf) == 0) {
reply_last = reply_web;
dup = true;
}
if (dup) {
send_unix_msg(sockd, reply_last);
STRNCPY(duptype, buf);
dot = strchr(duptype, '.');
if (dot)
*dot = '\0';
snprintf(reply, sizeof(reply), "%s%ld,%ld.%s",
LOGDUP, now.tv_sec, now.tv_usec, duptype);
LOGQUE(reply);
LOGWARNING("Duplicate '%s' message received", duptype);
} else {
LOGQUE(buf);
cmdnum = breakdown(&trf_root, &trf_store, buf, &which_cmds, cmd, id, &cd);
switch (cmdnum) {
case CMD_REPLY:
snprintf(reply, sizeof(reply), "%s.%ld.?.", id, now.tv_sec);
send_unix_msg(sockd, reply);
break;
case CMD_SHUTDOWN:
LOGWARNING("Listener received shutdown message, terminating ckdb");
snprintf(reply, sizeof(reply), "%s.%ld.ok.exiting", id, now.tv_sec);
send_unix_msg(sockd, reply);
everyone_die = true;
break;
case CMD_PING:
LOGDEBUG("Listener received ping request");
snprintf(reply, sizeof(reply), "%s.%ld.ok.pong", id, now.tv_sec);
send_unix_msg(sockd, reply);
break;
case CMD_VERSION:
LOGDEBUG("Listener received version request");
snprintf(reply, sizeof(reply),
"%s.%ld.ok.CKDB V%s",
id, now.tv_sec, CKDB_VERSION);
send_unix_msg(sockd, reply);
break;
case CMD_LOGLEVEL:
if (!*id) {
LOGDEBUG("Listener received loglevel, currently %d",
pi->ckp->loglevel);
snprintf(reply, sizeof(reply),
"%s.%ld.ok.loglevel currently %d",
id, now.tv_sec,
pi->ckp->loglevel);
} else {
oldloglevel = pi->ckp->loglevel;
loglevel = atoi(id);
LOGDEBUG("Listener received loglevel %d currently %d A",
loglevel, oldloglevel);
if (loglevel < LOG_EMERG || loglevel > LOG_DEBUG) {
snprintf(reply, sizeof(reply),
"%s.%ld.ERR.invalid loglevel %d"
" - currently %d",
id, now.tv_sec,
loglevel, oldloglevel);
} else {
pi->ckp->loglevel = loglevel;
snprintf(reply, sizeof(reply),
"%s.%ld.ok.loglevel now %d - was %d",
id, now.tv_sec,
pi->ckp->loglevel, oldloglevel);
}
// Do this twice since the loglevel may have changed
LOGDEBUG("Listener received loglevel %d currently %d B",
loglevel, oldloglevel);
}
send_unix_msg(sockd, reply);
break;
// Always process immediately:
case CMD_AUTH:
case CMD_ADDRAUTH:
// First message from the pool
if (want_first) {
ck_wlock(&fpm_lock);
first_pool_message = strdup(buf);
ck_wunlock(&fpm_lock);
want_first = false;
}
case CMD_CHKPASS:
case CMD_ADDUSER:
case CMD_NEWPASS:
case CMD_USERSET:
case CMD_GETATTS:
case CMD_SETATTS:
case CMD_BLOCKLIST:
case CMD_NEWID:
case CMD_STATS:
ans = cmds[which_cmds].func(NULL, cmd, id, &now,
by_default,
(char *)__func__,
inet_default,
&cd, trf_root);
siz = strlen(ans) + strlen(id) + 32;
rep = malloc(siz);
snprintf(rep, siz, "%s.%ld.%s", id, now.tv_sec, ans);
send_unix_msg(sockd, rep);
free(ans);
ans = NULL;
switch (cmdnum) {
case CMD_AUTH:
STORELASTREPLY(auth);
break;
case CMD_ADDRAUTH:
STORELASTREPLY(addrauth);
break;
case CMD_CHKPASS:
STORELASTREPLY(chkpass);
break;
case CMD_ADDUSER:
STORELASTREPLY(adduser);
break;
case CMD_NEWPASS:
STORELASTREPLY(newpass);
break;
case CMD_USERSET:
STORELASTREPLY(userset);
break;
case CMD_NEWID:
STORELASTREPLY(newid);
break;
case CMD_SETATTS:
STORELASTREPLY(setatts);
break;
// The rest
default:
free(rep);
}
rep = NULL;
break;
// Process, but reject (loading) until startup_complete
case CMD_HOMEPAGE:
case CMD_ALLUSERS:
case CMD_WORKERS:
case CMD_PAYMENTS:
case CMD_PPLNS:
case CMD_DSP:
case CMD_BLOCKSTATUS:
if (!startup_complete) {
snprintf(reply, sizeof(reply),
"%s.%ld.loading.%s",
id, now.tv_sec, cmd);
send_unix_msg(sockd, reply);
} else {
ans = cmds[which_cmds].func(NULL, cmd, id, &now,
by_default,
(char *)__func__,
inet_default,
&cd, trf_root);
siz = strlen(ans) + strlen(id) + 32;
rep = malloc(siz);
snprintf(rep, siz, "%s.%ld.%s", id, now.tv_sec, ans);
send_unix_msg(sockd, rep);
free(ans);
ans = NULL;
if (cmdnum == CMD_DSP)
free(rep);
else {
if (last_web)
free(last_web);
last_web = buf;
buf = NULL;
if (reply_web)
free(reply_web);
reply_web = rep;
}
rep = NULL;
}
break;
// Always queue (ok.queued)
case CMD_SHARELOG:
case CMD_POOLSTAT:
case CMD_USERSTAT:
case CMD_BLOCK:
// First message from the pool
if (want_first) {
ck_wlock(&fpm_lock);
first_pool_message = strdup(buf);
ck_wunlock(&fpm_lock);
want_first = false;
}
snprintf(reply, sizeof(reply),
"%s.%ld.ok.queued",
id, now.tv_sec);
send_unix_msg(sockd, reply);
K_WLOCK(workqueue_free);
item = k_unlink_head(workqueue_free);
K_WUNLOCK(workqueue_free);
DATA_WORKQUEUE(workqueue, item);
workqueue->buf = buf;
buf = NULL;
workqueue->which_cmds = which_cmds;
workqueue->cmdnum = cmdnum;
STRNCPY(workqueue->cmd, cmd);
STRNCPY(workqueue->id, id);
copy_tv(&(workqueue->now), &now);
STRNCPY(workqueue->by, by_default);
STRNCPY(workqueue->code, __func__);
STRNCPY(workqueue->inet, inet_default);
copy_tv(&(workqueue->cd), &cd);
workqueue->trf_root = trf_root;
trf_root = NULL;
workqueue->trf_store = trf_store;
trf_store = NULL;
K_WLOCK(workqueue_free);
k_add_tail(workqueue_store, item);
K_WUNLOCK(workqueue_free);
mutex_lock(&wq_waitlock);
pthread_cond_signal(&wq_waitcond);
mutex_unlock(&wq_waitlock);
break;
// Code error
default:
LOGEMERG("%s() CODE ERROR unhandled message %d %.32s...",
__func__, cmdnum, buf);
snprintf(reply, sizeof(reply),
"%s.%ld.failed.code",
id, now.tv_sec);
send_unix_msg(sockd, reply);
break;
}
}
}
close(sockd);
tick();
if (trf_root)
trf_root = free_ktree(trf_root, NULL);
if (trf_store) {
item = trf_store->head;
while (item) {
DATA_TRANSFER(transfer, item);
if (transfer->mvalue != transfer->svalue)
free(transfer->mvalue);
item = item->next;
}
K_WLOCK(transfer_free);
k_list_transfer_to_head(trf_store, transfer_free);
trf_store = k_free_store(trf_store);
if (transfer_free->count == transfer_free->total &&
transfer_free->total > ALLOC_TRANSFER * CULL_TRANSFER)
k_cull_list(transfer_free);
K_WUNLOCK(transfer_free);
}
}
if (buf)
dealloc(buf);
// TODO: if anyone cares, free all the dup buffers :P
close_unix_socket(us->sockd, us->path);
return NULL;
}
static bool reload_line(PGconn *conn, char *filename, uint64_t count, char *buf)
{
char cmd[CMD_SIZ+1], id[ID_SIZ+1];
enum cmd_values cmdnum;
char *end, *ans;
int which_cmds;
K_STORE *trf_store = NULL;
K_TREE *trf_root = NULL;
TRANSFER *transfer;
K_ITEM *item;
tv_t now, cd;
bool finished;
// Once we've read the message
setnow(&now);
if (buf) {
end = buf + strlen(buf) - 1;
// strip trailing \n and \r
while (end >= buf && (*end == '\n' || *end == '\r'))
*(end--) = '\0';
}
if (!buf || !*buf) {
if (!buf)
LOGERR("%s() NULL message line %"PRIu64, __func__, count);
else
LOGERR("%s() Empty message line %"PRIu64, __func__, count);
} else {
finished = false;
ck_wlock(&fpm_lock);
if (first_pool_message && strcmp(first_pool_message, buf) == 0)
finished = true;
ck_wunlock(&fpm_lock);
if (finished) {
LOGERR("%s() reload completed, ckpool queue match at line %"PRIu64, __func__, count);
return true;
}
LOGQUE(buf);
cmdnum = breakdown(&trf_root, &trf_store, buf, &which_cmds, cmd, id, &cd);
switch (cmdnum) {
// Ignore
case CMD_REPLY:
break;
// Shouldn't be there
case CMD_SHUTDOWN:
case CMD_PING:
case CMD_VERSION:
case CMD_LOGLEVEL:
// Non pool commands, shouldn't be there
case CMD_ADDUSER:
case CMD_NEWPASS:
case CMD_CHKPASS:
case CMD_USERSET:
case CMD_BLOCKLIST:
case CMD_BLOCKSTATUS:
case CMD_NEWID:
case CMD_PAYMENTS:
case CMD_WORKERS:
case CMD_ALLUSERS:
case CMD_HOMEPAGE:
case CMD_GETATTS:
case CMD_SETATTS:
case CMD_DSP:
case CMD_STATS:
case CMD_PPLNS:
LOGERR("%s() Message line %"PRIu64" '%s' - invalid - ignored",
__func__, count, cmd);
break;
case CMD_AUTH:
case CMD_ADDRAUTH:
case CMD_POOLSTAT:
case CMD_USERSTAT:
case CMD_BLOCK:
if (confirm_sharesummary)
break;
case CMD_SHARELOG:
ans = cmds[which_cmds].func(conn, cmd, id, &now,
by_default,
(char *)__func__,
inet_default,
&cd, trf_root);
if (ans)
free(ans);
break;
default:
// Force this switch to be updated if new cmds are added
quithere(1, "%s line %"PRIu64" '%s' - not handled by reload",
filename, count, cmd);
break;
}
if (trf_root)
trf_root = free_ktree(trf_root, NULL);
if (trf_store) {
item = trf_store->head;
while (item) {
DATA_TRANSFER(transfer, item);
if (transfer->mvalue != transfer->svalue)
free(transfer->mvalue);
item = item->next;
}
K_WLOCK(transfer_free);
k_list_transfer_to_head(trf_store, transfer_free);
K_WUNLOCK(transfer_free);
trf_store = k_free_store(trf_store);
}
}
tick();
return false;
}
// 10Mb for now - transactiontree can be large
#define MAX_READ (10 * 1024 * 1024)
static char *reload_buf;
/* If the reload start file is missing and -r was specified correctly:
* touch the filename reported in "Failed to open 'filename'",
* if ckdb aborts at the beginning of the reload, then start again */
static bool reload_from(tv_t *start)
{
PGconn *conn = NULL;
char buf[DATE_BUFSIZ+1], run[DATE_BUFSIZ+1];
size_t rflen = strlen(restorefrom);
char *missingfirst = NULL, *missinglast = NULL;
int missing_count;
int processing;
bool finished = false, matched = false, ret = true;
char *filename = NULL;
uint64_t count, total;
tv_t now;
FILE *fp = NULL;
reload_buf = malloc(MAX_READ);
if (!reload_buf)
quithere(1, "OOM");
reloading = true;
copy_tv(&reload_timestamp, start);
reload_timestamp.tv_sec -= reload_timestamp.tv_sec % ROLL_S;
tv_to_buf(start, buf, sizeof(buf));
tv_to_buf(&reload_timestamp, run, sizeof(run));
LOGWARNING("%s(): from %s (stamp %s)", __func__, buf, run);
filename = rotating_filename(restorefrom, reload_timestamp.tv_sec);
fp = fopen(filename, "re");
if (!fp)
quithere(1, "Failed to open '%s'", filename);
setnow(&now);
tvs_to_buf(&now, run, sizeof(run));
snprintf(reload_buf, MAX_READ, "reload.%s.s0", run);
LOGQUE(reload_buf);
conn = dbconnect();
total = 0;
processing = 0;
while (!everyone_die && !finished) {
LOGWARNING("%s(): processing %s", __func__, filename);
processing++;
count = 0;
while (!everyone_die && !matched && fgets_unlocked(reload_buf, MAX_READ, fp))
matched = reload_line(conn, filename, ++count, reload_buf);
if (ferror(fp)) {
int err = errno;
quithere(1, "Read failed on %s (%d) '%s'",
filename, err, strerror(err));
}
LOGWARNING("%s(): %sread %"PRIu64" line%s from %s",
__func__,
everyone_die ? "Shutdown, aborting - " : "",
count, count == 1 ? "" : "s",
filename);
total += count;
fclose(fp);
free(filename);
if (everyone_die || matched)
break;
reload_timestamp.tv_sec += ROLL_S;
if (confirm_sharesummary && tv_newer(&confirm_finish, &reload_timestamp)) {
LOGWARNING("%s(): confirm range complete", __func__);
break;
}
filename = rotating_filename(restorefrom, reload_timestamp.tv_sec);
fp = fopen(filename, "re");
if (!fp) {
missingfirst = strdup(filename);
free(filename);
filename = NULL;
errno = 0;
missing_count = 1;
setnow(&now);
now.tv_sec += ROLL_S;
while (42) {
reload_timestamp.tv_sec += ROLL_S;
/* WARNING: if the system clock is wrong, any CCLs
* missing or not created due to a ckpool outage of
* an hour or more can stop the reload early and
* cause DB problems! Though, the clock being wrong
* can screw up ckpool and ckdb anyway ... */
if (!tv_newer(&reload_timestamp, &now)) {
finished = true;
break;
}
filename = rotating_filename(restorefrom, reload_timestamp.tv_sec);
fp = fopen(filename, "re");
if (fp)
break;
errno = 0;
if (missing_count++ > 1)
free(missinglast);
missinglast = strdup(filename);
free(filename);
filename = NULL;
}
if (missing_count == 1)
LOGWARNING("%s(): skipped %s", __func__, missingfirst+rflen);
else {
LOGWARNING("%s(): skipped %d files from %s to %s",
__func__, missing_count, missingfirst+rflen, missinglast+rflen);
free(missinglast);
missinglast = NULL;
}
free(missingfirst);
missingfirst = NULL;
}
}
PQfinish(conn);
snprintf(reload_buf, MAX_READ, "reload.%s.%"PRIu64, run, total);
LOGQUE(reload_buf);
LOGWARNING("%s(): read %d file%s, total %"PRIu64" line%s",
__func__,
processing, processing == 1 ? "" : "s",
total, total == 1 ? "" : "s");
if (everyone_die)
return true;
if (!matched) {
ck_wlock(&fpm_lock);
if (first_pool_message) {
LOGERR("%s() reload completed without finding ckpool queue match '%.32s'...",
__func__, first_pool_message);
LOGERR("%s() restart ckdb to resolve this", __func__);
ret = false;
}
ck_wunlock(&fpm_lock);
}
reloading = false;
free(reload_buf);
reload_buf = NULL;
return ret;
}
static void process_queued(PGconn *conn, K_ITEM *wq_item)
{
static char *last_buf = NULL;
WORKQUEUE *workqueue;
TRANSFER *transfer;
K_ITEM *item;
char *ans;
DATA_WORKQUEUE(workqueue, wq_item);
// Simply ignore the (very rare) duplicates
if (!last_buf || strcmp(workqueue->buf, last_buf)) {
ans = cmds[workqueue->which_cmds].func(conn, workqueue->cmd, workqueue->id,
&(workqueue->now), workqueue->by,
workqueue->code, workqueue->inet,
&(workqueue->cd), workqueue->trf_root);
free(ans);
}
if (last_buf)
free(last_buf);
last_buf = workqueue->buf;
workqueue->trf_root = free_ktree(workqueue->trf_root, NULL);
item = workqueue->trf_store->head;
while (item) {
DATA_TRANSFER(transfer, item);
if (transfer->mvalue != transfer->svalue)
free(transfer->mvalue);
item = item->next;
}
K_WLOCK(transfer_free);
k_list_transfer_to_head(workqueue->trf_store, transfer_free);
K_WUNLOCK(transfer_free);
workqueue->trf_store = k_free_store(workqueue->trf_store);
K_WLOCK(workqueue_free);
k_add_head(workqueue_free, wq_item);
if (workqueue_free->count == workqueue_free->total &&
workqueue_free->total > ALLOC_WORKQUEUE * CULL_WORKQUEUE)
k_cull_list(workqueue_free);
K_WUNLOCK(workqueue_free);
}
// TODO: equivalent of api_allow
static void *listener(void *arg)
{
PGconn *conn = NULL;
pthread_t log_pt;
pthread_t sock_pt;
pthread_t summ_pt;
K_ITEM *wq_item;
int qc;
logqueue_free = k_new_list("LogQueue", sizeof(LOGQUEUE),
ALLOC_LOGQUEUE, LIMIT_LOGQUEUE, true);
logqueue_store = k_new_store(logqueue_free);
create_pthread(&log_pt, logger, NULL);
create_pthread(&sock_pt, socketer, arg);
create_pthread(&summ_pt, summariser, NULL);
rename_proc("db_listener");
if (!setup_data()) {
if (!everyone_die) {
LOGEMERG("ABORTING");
everyone_die = true;
}
return NULL;
}
if (!everyone_die) {
K_RLOCK(workqueue_store);
qc = workqueue_store->count;
K_RUNLOCK(workqueue_store);
LOGWARNING("%s(): ckdb ready, queue %d", __func__, qc);
startup_complete = true;
}
if (!everyone_die)
conn = dbconnect();
// Process queued work
while (!everyone_die) {
K_WLOCK(workqueue_store);
wq_item = k_unlink_head(workqueue_store);
K_WUNLOCK(workqueue_store);
if (wq_item) {
process_queued(conn, wq_item);
tick();
} else {
const ts_t tsdiff = {0, 420000000};
tv_t now;
ts_t abs;
tv_time(&now);
tv_to_ts(&abs, &now);
timeraddspec(&abs, &tsdiff);
mutex_lock(&wq_waitlock);
pthread_cond_timedwait(&wq_waitcond, &wq_waitlock, &abs);
mutex_unlock(&wq_waitlock);
}
}
if (conn)
PQfinish(conn);
return NULL;
}
/* TODO: This will be way faster traversing both trees simultaneously
* rather than traversing one and searching the other, then repeating
* in reverse. Will change it later */
static void compare_summaries(K_TREE *leftsum, char *leftname,
K_TREE *rightsum, char *rightname,
bool show_missing, bool show_diff)
{
K_TREE_CTX ctxl[1], ctxr[1];
K_ITEM look, *lss, *rss;
char cd_buf[DATE_BUFSIZ];
SHARESUMMARY looksharesummary, *l_ss, *r_ss;
uint64_t total, ok, missing, diff;
uint64_t first_used = 0, last_used = 0;
int64_t miss_first = 0, miss_last = 0;
tv_t miss_first_cd = {0,0}, miss_last_cd = {0,0};
int64_t diff_first = 0, diff_last = 0;
tv_t diff_first_cd = {0,0}, diff_last_cd = {0,0};
char cd_buf1[DATE_BUFSIZ], cd_buf2[DATE_BUFSIZ];
looksharesummary.workinfoid = confirm_first_workinfoid;
looksharesummary.userid = -1;
looksharesummary.workername[0] = '\0';
INIT_SHARESUMMARY(&look);
look.data = (void *)(&looksharesummary);
total = ok = missing = diff = 0;
lss = find_after_in_ktree(leftsum, &look, cmp_sharesummary_workinfoid, ctxl);
while (lss) {
DATA_SHARESUMMARY(l_ss, lss);
if (l_ss->workinfoid > confirm_last_workinfoid)
break;
total++;
if (first_used == 0)
first_used = l_ss->workinfoid;
last_used = l_ss->workinfoid;
rss = find_in_ktree(rightsum, lss, cmp_sharesummary_workinfoid, ctxr);
DATA_SHARESUMMARY_NULL(r_ss, rss);
if (!rss) {
missing++;
if (miss_first == 0) {
miss_first = l_ss->workinfoid;
copy_tv(&miss_first_cd, &(l_ss->createdate));
}
miss_last = l_ss->workinfoid;
copy_tv(&miss_last_cd, &(l_ss->createdate));
if (show_missing) {
LOGERR("ERROR: %s %"PRId64"/%s/%ld,%ld %.19s missing from %s",
leftname,
l_ss->workinfoid,
l_ss->workername,
l_ss->createdate.tv_sec,
l_ss->createdate.tv_usec,
tv_to_buf(&(l_ss->createdate), cd_buf, sizeof(cd_buf)),
rightname);
}
} else if (r_ss->diffacc != l_ss->diffacc) {
diff++;
if (show_diff) {
if (diff_first == 0) {
diff_first = l_ss->workinfoid;
copy_tv(&diff_first_cd, &(l_ss->createdate));
}
diff_last = l_ss->workinfoid;
copy_tv(&diff_last_cd, &(l_ss->createdate));
LOGERR("ERROR: %"PRId64"/%s/%ld,%ld %.19s - diffacc: %s: %.0f %s: %.0f",
l_ss->workinfoid,
l_ss->workername,
l_ss->createdate.tv_sec,
l_ss->createdate.tv_usec,
tv_to_buf(&(l_ss->createdate), cd_buf, sizeof(cd_buf)),
leftname,
l_ss->diffacc,
rightname,
r_ss->diffacc);
}
} else
ok++;
lss = next_in_ktree(ctxl);
}
LOGERR("RESULT: %s->%s Total %"PRIu64" workinfoid %"PRId64"-%"PRId64
" %s missing: %"PRIu64" different: %"PRIu64,
leftname, rightname, total, first_used, last_used,
rightname, missing, diff);
if (miss_first) {
tv_to_buf(&miss_first_cd, cd_buf1, sizeof(cd_buf1));
tv_to_buf(&miss_last_cd, cd_buf2, sizeof(cd_buf2));
LOGERR(" workinfoid range for missing: %"PRId64"-%"PRId64
" (%s .. %s)",
miss_first, miss_last, cd_buf1, cd_buf2);
}
if (show_diff && diff_first) {
tv_to_buf(&diff_first_cd, cd_buf1, sizeof(cd_buf1));
tv_to_buf(&diff_last_cd, cd_buf2, sizeof(cd_buf2));
LOGERR(" workinfoid range for differences: %"PRId64"-%"PRId64
" (%s .. %s)",
diff_first, diff_last, cd_buf1, cd_buf2);
}
}
static void confirm_reload()
{
K_TREE *sharesummary_workinfoid_save;
__maybe_unused K_TREE *sharesummary_save;
__maybe_unused K_TREE *workinfo_save;
K_ITEM b_look, wi_look, *wi_item, *wif_item, *wil_item;
K_ITEM *b_begin_item, *b_end_item;
K_ITEM *ss_begin_item, *ss_end_item;
WORKINFO lookworkinfo, *workinfo;
BLOCKS lookblocks, *b_blocks, *e_blocks;
SHARESUMMARY *b_ss, *e_ss;
K_TREE_CTX ctx[1];
char buf[DATE_BUFSIZ+1];
char *first_reason;
char *last_reason;
char cd_buf[DATE_BUFSIZ];
char first_buf[64], last_buf[64];
char *filename;
tv_t start;
FILE *fp;
// TODO: // abort reload when we get an age after the end of a workinfo after the Xs after the last workinfo before the end
wif_item = first_in_ktree(workinfo_root, ctx);
wil_item = last_in_ktree(workinfo_root, ctx);
if (!wif_item || !wil_item) {
LOGWARNING("%s(): DB contains no workinfo records", __func__);
return;
}
DATA_WORKINFO(workinfo, wif_item);
tv_to_buf(&(workinfo->createdate), cd_buf, sizeof(cd_buf));
LOGWARNING("%s(): DB first workinfoid %"PRId64" %s",
__func__, workinfo->workinfoid, cd_buf);
DATA_WORKINFO(workinfo, wil_item);
tv_to_buf(&(workinfo->createdate), cd_buf, sizeof(cd_buf));
LOGWARNING("%s(): DB last workinfoid %"PRId64" %s",
__func__, workinfo->workinfoid, cd_buf);
b_begin_item = first_in_ktree(blocks_root, ctx);
b_end_item = last_in_ktree(blocks_root, ctx);
if (!b_begin_item || !b_end_item)
LOGWARNING("%s(): DB contains no blocks :(", __func__);
else {
DATA_BLOCKS(b_blocks, b_begin_item);
DATA_BLOCKS(e_blocks, b_end_item);
tv_to_buf(&(b_blocks->createdate), cd_buf, sizeof(cd_buf));
LOGWARNING("%s(): DB first block %d/%"PRId64" %s",
__func__,
b_blocks->height,
b_blocks->workinfoid,
cd_buf);
tv_to_buf(&(e_blocks->createdate),
cd_buf, sizeof(cd_buf));
LOGWARNING("%s(): DB last block %d/%"PRId64" %s",
__func__,
e_blocks->height,
e_blocks->workinfoid,
cd_buf);
}
ss_begin_item = first_in_ktree(sharesummary_workinfoid_root, ctx);
ss_end_item = last_in_ktree(sharesummary_workinfoid_root, ctx);
if (!ss_begin_item || !ss_end_item)
LOGWARNING("%s(): DB contains no sharesummary records", __func__);
else {
DATA_SHARESUMMARY(b_ss, ss_begin_item);
DATA_SHARESUMMARY(e_ss, ss_end_item);
tv_to_buf(&(b_ss->createdate), cd_buf, sizeof(cd_buf));
LOGWARNING("%s(): DB first sharesummary %"PRId64"/%s %s",
__func__,
b_ss->workinfoid,
b_ss->workername,
cd_buf);
tv_to_buf(&(e_ss->createdate), cd_buf, sizeof(cd_buf));
LOGWARNING("%s(): DB last sharesummary %"PRId64"/%s %s",
__func__,
e_ss->workinfoid,
e_ss->workername,
cd_buf);
}
/* The first workinfo we should process
* With no y records we should start from the beginning (0)
* With any y records, we should start from the oldest of: y+1 and a
* which can produce y records as reload a's, if a is used */
if (dbstatus.newest_workinfoid_y > 0) {
confirm_first_workinfoid = dbstatus.newest_workinfoid_y + 1;
if (confirm_first_workinfoid > dbstatus.oldest_workinfoid_a) {
confirm_first_workinfoid = dbstatus.oldest_workinfoid_a;
first_reason = "oldest aged";
} else
first_reason = "newest confirmed+1";
} else
first_reason = "0 - none confirmed";
/* The last workinfo we should process
* The reason for going past the last 'a' up to before
* the first 'n' is in case there were shares missed between them -
* but that should only be the case with a code bug -
* so it checks that */
if (dbstatus.newest_workinfoid_a > 0) {
confirm_last_workinfoid = dbstatus.newest_workinfoid_a;
last_reason = "newest aged";
}
if (confirm_last_workinfoid < dbstatus.oldest_workinfoid_n) {
confirm_last_workinfoid = dbstatus.oldest_workinfoid_n - 1;
last_reason = "oldest new-1";
}
if (confirm_last_workinfoid == 0) {
LOGWARNING("%s(): there are no unconfirmed sharesummary records in the DB",
__func__, buf);
return;
}
INIT_BLOCKS(&b_look);
INIT_WORKINFO(&wi_look);
// Do this after above code for checking and so we can use the results
if (confirm_range && *confirm_range) {
switch(tolower(confirm_range[0])) {
case 'b':
// First DB record of the block = or after confirm_block
lookblocks.height = confirm_block;
lookblocks.blockhash[0] = '\0';
b_look.data = (void *)(&lookblocks);
b_end_item = find_after_in_ktree(blocks_root, &b_look, cmp_blocks, ctx);
if (!b_end_item) {
LOGWARNING("%s(): no DB block height found matching or after %d",
__func__, confirm_block);
return;
}
DATA_BLOCKS(e_blocks, b_end_item);
confirm_last_workinfoid = e_blocks->workinfoid;
// Now find the last DB record of the previous block
lookblocks.height = e_blocks->height;
lookblocks.blockhash[0] = '\0';
b_look.data = (void *)(&lookblocks);
b_begin_item = find_before_in_ktree(blocks_root, &b_look, cmp_blocks, ctx);
if (!b_begin_item)
confirm_first_workinfoid = 0;
else {
DATA_BLOCKS(b_blocks, b_begin_item);
// First DB record of the block 'begin'
lookblocks.height = b_blocks->height;
lookblocks.blockhash[0] = '\0';
b_look.data = (void *)(&lookblocks);
b_begin_item = find_after_in_ktree(blocks_root, &b_look, cmp_blocks, ctx);
// Not possible
if (!b_begin_item)
confirm_first_workinfoid = 0;
else {
DATA_BLOCKS(b_blocks, b_begin_item);
confirm_first_workinfoid = b_blocks->workinfoid;
}
}
snprintf(first_buf, sizeof(first_buf),
"block %d",
b_begin_item ? b_blocks->height : 0);
first_reason = first_buf;
snprintf(last_buf, sizeof(last_buf),
"block %d",
e_blocks->height);
last_reason = last_buf;
break;
case 'i':
LOGWARNING("%s(): info displayed - exiting", __func__);
exit(0);
case 'c':
case 'r':
confirm_first_workinfoid = confirm_range_start;
confirm_last_workinfoid = confirm_range_finish;
first_reason = "start range";
last_reason = "end range";
break;
case 'w':
confirm_first_workinfoid = confirm_range_start;
// last from default
if (confirm_last_workinfoid < confirm_first_workinfoid) {
LOGWARNING("%s(): no unconfirmed sharesummary records before start",
__func__, buf);
return;
}
first_reason = "start range";
break;
default:
quithere(1, "Code fail");
}
}
/* These two below find the closest valid workinfo to the ones chosen
* however we still use the original ones chosen to select/ignore data */
/* Find the workinfo before confirm_first_workinfoid+1
* i.e. the one we want or the previous before it */
lookworkinfo.workinfoid = confirm_first_workinfoid + 1;
lookworkinfo.expirydate.tv_sec = date_begin.tv_sec;
lookworkinfo.expirydate.tv_usec = date_begin.tv_usec;
wi_look.data = (void *)(&lookworkinfo);
wi_item = find_before_in_ktree(workinfo_root, &wi_look, cmp_workinfo, ctx);
if (wi_item) {
DATA_WORKINFO(workinfo, wi_item);
copy_tv(&start, &(workinfo->createdate));
if (workinfo->workinfoid != confirm_first_workinfoid) {
LOGWARNING("%s() start workinfo not found ... using time of %"PRId64,
__func__, workinfo->workinfoid);
}
} else {
start.tv_sec = start.tv_usec = 0;
LOGWARNING("%s() no start workinfo found ... using time 0", __func__);
}
/* Find the workinfo after confirm_last_workinfoid-1
* i.e. the one we want or the next after it */
lookworkinfo.workinfoid = confirm_last_workinfoid - 1;
lookworkinfo.expirydate.tv_sec = date_eot.tv_sec;
lookworkinfo.expirydate.tv_usec = date_eot.tv_usec;
wi_look.data = (void *)(&lookworkinfo);
wi_item = find_after_in_ktree(workinfo_root, &wi_look, cmp_workinfo, ctx);
if (wi_item) {
DATA_WORKINFO(workinfo, wi_item);
/* Now find the one after the one we found to determine the
* confirm_finish timestamp */
lookworkinfo.workinfoid = workinfo->workinfoid;
lookworkinfo.expirydate.tv_sec = date_eot.tv_sec;
lookworkinfo.expirydate.tv_usec = date_eot.tv_usec;
wi_look.data = (void *)(&lookworkinfo);
wi_item = find_after_in_ktree(workinfo_root, &wi_look, cmp_workinfo, ctx);
if (wi_item) {
DATA_WORKINFO(workinfo, wi_item);
copy_tv(&confirm_finish, &(workinfo->createdate));
confirm_finish.tv_sec += WORKINFO_AGE;
} else {
confirm_finish.tv_sec = date_eot.tv_sec;
confirm_finish.tv_usec = date_eot.tv_usec;
}
} else {
confirm_finish.tv_sec = date_eot.tv_sec;
confirm_finish.tv_usec = date_eot.tv_usec;
LOGWARNING("%s() no finish workinfo found ... using EOT", __func__);
}
LOGWARNING("%s() workinfo range: %"PRId64" to %"PRId64" ('%s' to '%s')",
__func__, confirm_first_workinfoid, confirm_last_workinfoid,
first_reason, last_reason);
tv_to_buf(&start, buf, sizeof(buf));
LOGWARNING("%s() load start timestamp %s", __func__, buf);
tv_to_buf(&confirm_finish, buf, sizeof(buf));
LOGWARNING("%s() load finish timestamp %s", __func__, buf);
/* Save the DB info for comparing to the reload
* i.e. the reload will generate from scratch all the
* sharesummaries and workinfo from the CCLs */
sharesummary_workinfoid_save = sharesummary_workinfoid_root;
sharesummary_save = sharesummary_root;
workinfo_save = workinfo_root;
sharesummary_workinfoid_root = new_ktree();
sharesummary_root = new_ktree();
workinfo_root = new_ktree();
if (start.tv_sec < DATE_BEGIN) {
start.tv_sec = DATE_BEGIN;
start.tv_usec = 0L;
filename = rotating_filename(restorefrom, start.tv_sec);
fp = fopen(filename, "re");
if (fp)
fclose(fp);
else {
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
int fd = open(filename, O_CREAT|O_RDONLY, mode);
if (fd == -1) {
int ern = errno;
quithere(1, "Couldn't create '%s' (%d) %s",
filename, ern, strerror(ern));
}
close(fd);
}
free(filename);
}
if (!reload_from(&start)) {
LOGEMERG("%s() ABORTING from reload_from()", __func__);
return;
}
if (confirm_check_createdate) {
LOGERR("%s(): CCL mismatches %"PRId64"/%"PRId64" %.6f/%.6f unordered "
"%"PRId64"/%"PRId64" %.6f",
__func__, ccl_mismatch, ccl_mismatch_abs,
ccl_mismatch_min, ccl_mismatch_max,
ccl_unordered, ccl_unordered_abs, ccl_unordered_most);
return;
}
compare_summaries(sharesummary_workinfoid_save, "DB",
sharesummary_workinfoid_root, "ReLoad",
true, true);
compare_summaries(sharesummary_workinfoid_root, "ReLoad",
sharesummary_workinfoid_save, "DB",
true, false);
}
static void confirm_summaries()
{
pthread_t log_pt;
char *range, *minus;
// Simple value check to abort early
if (confirm_range && *confirm_range) {
switch(tolower(confirm_range[0])) {
case 'b':
case 'c':
case 'r':
case 'w':
if (strlen(confirm_range) < 2) {
LOGEMERG("%s() invalid confirm range length '%s'",
__func__, confirm_range);
return;
}
break;
case 'i':
break;
default:
LOGEMERG("%s() invalid confirm range '%s'",
__func__, confirm_range);
return;
}
switch(tolower(confirm_range[0])) {
case 'b':
confirm_block = atoi(confirm_range+1);
if (confirm_block <= 0) {
LOGEMERG("%s() invalid confirm block '%s' - must be >0",
__func__, confirm_range);
return;
}
break;
case 'i':
break;
case 'c':
confirm_check_createdate = true;
case 'r':
range = strdup(confirm_range);
minus = strchr(range+1, '-');
if (!minus || minus == range+1) {
LOGEMERG("%s() invalid confirm range '%s' - must be %cNNN-MMM",
__func__, confirm_range, tolower(confirm_range[0]));
return;
}
*(minus++) = '\0';
confirm_range_start = atoll(range+1);
if (confirm_range_start <= 0) {
LOGEMERG("%s() invalid confirm start in '%s' - must be >0",
__func__, confirm_range);
return;
}
confirm_range_finish = atoll(minus);
if (confirm_range_finish <= 0) {
LOGEMERG("%s() invalid confirm finish in '%s' - must be >0",
__func__, confirm_range);
return;
}
if (confirm_range_finish < confirm_range_start) {
LOGEMERG("%s() invalid confirm range in '%s' - finish < start",
__func__, confirm_range);
return;
}
free(range);
break;
case 'w':
confirm_range_start = atoll(confirm_range+1);
if (confirm_range_start <= 0) {
LOGEMERG("%s() invalid confirm start '%s' - must be >0",
__func__, confirm_range);
return;
}
}
}
logqueue_free = k_new_list("LogQueue", sizeof(LOGQUEUE),
ALLOC_LOGQUEUE, LIMIT_LOGQUEUE, true);
logqueue_store = k_new_store(logqueue_free);
create_pthread(&log_pt, logger, NULL);
rename_proc("dby_confirmer");
alloc_storage();
if (!getdata1()) {
LOGEMERG("%s() ABORTING from getdata1()", __func__);
return;
}
if (!getdata2()) {
LOGEMERG("%s() ABORTING from getdata2()", __func__);
return;
}
confirm_reload();
}
static void check_restore_dir(char *name)
{
struct stat statbuf;
if (!restorefrom) {
restorefrom = strdup("logs");
if (!restorefrom)
quithere(1, "OOM");
}
if (!(*restorefrom))
quit(1, "ERR: '-r dir' can't be empty");
trail_slash(&restorefrom);
if (stat(restorefrom, &statbuf))
quit(1, "ERR: -r '%s' directory doesn't exist", restorefrom);
restorefrom = realloc(restorefrom, strlen(restorefrom)+strlen(name)+1);
if (!restorefrom)
quithere(1, "OOM");
strcat(restorefrom, name);
}
static struct option long_options[] = {
{ "dbprefix", required_argument, 0, 'b' },
{ "config", required_argument, 0, 'c' },
{ "dbname", required_argument, 0, 'd' },
{ "help", no_argument, 0, 'h' },
{ "killold", no_argument, 0, 'k' },
{ "loglevel", required_argument, 0, 'l' },
{ "name", required_argument, 0, 'n' },
{ "dbpass", required_argument, 0, 'p' },
{ "ckpool-logdir", required_argument, 0, 'r' },
{ "logdir", required_argument, 0, 'R' },
{ "sockdir", required_argument, 0, 's' },
{ "dbuser", required_argument, 0, 'u' },
{ "version", no_argument, 0, 'v' },
{ "confirm", no_argument, 0, 'y' },
{ "confirmrange", required_argument, 0, 'Y' },
{ 0, 0, 0, 0 }
};
static void sighandler(int sig)
{
LOGWARNING("Received signal %d, shutting down", sig);
everyone_die = true;
cksleep_ms(420);
exit(0);
}
int main(int argc, char **argv)
{
struct sigaction handler;
char buf[512];
ckpool_t ckp;
int c, ret, i = 0, j;
char *kill;
tv_t now;
printf("CKDB Master V%s (C) Kano (see source code)\n", CKDB_VERSION);
feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
global_ckp = &ckp;
memset(&ckp, 0, sizeof(ckp));
ckp.loglevel = LOG_NOTICE;
while ((c = getopt_long(argc, argv, "c:d:hkl:n:p:r:R:s:u:vyY:", long_options, &i)) != -1) {
switch(c) {
case 'c':
ckp.config = strdup(optarg);
break;
case 'd':
db_name = strdup(optarg);
kill = optarg;
while (*kill)
*(kill++) = ' ';
break;
case 'h':
for (j = 0; long_options[j].val; j++) {
struct option *jopt = &long_options[j];
if (jopt->has_arg) {
char *upper = alloca(strlen(jopt->name) + 1);
int offset = 0;
do {
upper[offset] = toupper(jopt->name[offset]);
} while (upper[offset++] != '\0');
printf("-%c %s | --%s %s\n", jopt->val,
upper, jopt->name, upper);
} else
printf("-%c | --%s\n", jopt->val, jopt->name);
}
exit(0);
case 'k':
ckp.killold = true;
break;
case 'l':
ckp.loglevel = atoi(optarg);
if (ckp.loglevel < LOG_EMERG || ckp.loglevel > LOG_DEBUG) {
quit(1, "Invalid loglevel (range %d - %d): %d",
LOG_EMERG, LOG_DEBUG, ckp.loglevel);
}
break;
case 'n':
ckp.name = strdup(optarg);
break;
case 'p':
db_pass = strdup(optarg);
kill = optarg;
if (*kill)
*(kill++) = ' ';
while (*kill)
*(kill++) = '\0';
break;
case 'r':
restorefrom = strdup(optarg);
break;
case 'R':
ckp.logdir = strdup(optarg);
break;
case 's':
ckp.socket_dir = strdup(optarg);
break;
case 'u':
db_user = strdup(optarg);
kill = optarg;
while (*kill)
*(kill++) = ' ';
break;
case 'v':
exit(0);
case 'y':
confirm_sharesummary = true;
break;
case 'Y':
confirm_range = strdup(optarg);
// Auto enable it also
confirm_sharesummary = true;
break;
}
}
if (confirm_sharesummary)
dbcode = "y";
else
dbcode = "";
if (!db_name)
db_name = "ckdb";
if (!db_user)
db_user = "postgres";
if (!ckp.name)
ckp.name = "ckdb";
snprintf(buf, 15, "%s%s", ckp.name, dbcode);
prctl(PR_SET_NAME, buf, 0, 0, 0);
memset(buf, 0, 15);
check_restore_dir(ckp.name);
if (!ckp.config) {
ckp.config = strdup(ckp.name);
realloc_strcat(&ckp.config, ".conf");
}
if (!ckp.socket_dir) {
ckp.socket_dir = strdup("/opt/");
realloc_strcat(&ckp.socket_dir, ckp.name);
}
trail_slash(&ckp.socket_dir);
/* Ignore sigpipe */
signal(SIGPIPE, SIG_IGN);
ret = mkdir(ckp.socket_dir, 0770);
if (ret && errno != EEXIST)
quit(1, "Failed to make directory %s", ckp.socket_dir);
// parse_config(&ckp);
if (!ckp.logdir)
ckp.logdir = strdup("dblogs");
/* Create the log directory */
trail_slash(&ckp.logdir);
ret = mkdir(ckp.logdir, 0700);
if (ret && errno != EEXIST)
quit(1, "Failed to make log directory %s", ckp.logdir);
/* Create the logfile */
sprintf(buf, "%s%s%s.log", ckp.logdir, ckp.name, dbcode);
ckp.logfp = fopen(buf, "ae");
if (!ckp.logfp)
quit(1, "Failed to open log file %s", buf);
ckp.logfd = fileno(ckp.logfp);
snprintf(logname, sizeof(logname), "%s%s-db%s-",
ckp.logdir, ckp.name, dbcode);
setnow(&now);
srandom((unsigned int)(now.tv_usec * 4096 + now.tv_sec % 4096));
ckp.main.ckp = &ckp;
ckp.main.processname = strdup("main");
if (confirm_sharesummary) {
// TODO: add a system lock to stop running 2 at once?
confirm_summaries();
} else {
ckp.main.sockname = strdup("listener");
write_namepid(&ckp.main);
create_process_unixsock(&ckp.main);
create_pthread(&ckp.pth_listener, listener, &ckp.main);
handler.sa_handler = sighandler;
handler.sa_flags = 0;
sigemptyset(&handler.sa_mask);
sigaction(SIGTERM, &handler, NULL);
sigaction(SIGINT, &handler, NULL);
/* Shutdown from here if the listener is sent a shutdown message */
join_pthread(ckp.pth_listener);
}
clean_up(&ckp);
return 0;
}