/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBPQ_FE_H #include #elif defined (HAVE_POSTGRESQL_LIBPQ_FE_H) #include #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 * */ #define DB_VLOCK "1" #define DB_VERSION "0.5" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ #define WHERE_FFL_PASS file, func, line #define WHERE_FFL_ARGS __maybe_unused const char *file, \ __maybe_unused const char *func, \ __maybe_unused const int line #define STRINT(x) STRINT2(x) #define STRINT2(x) #x // So they can fit into a 1 byte flag field #define TRUE_STR "Y" #define FALSE_STR "N" #define coinbase1height(_cb1) _coinbase1height(_cb1, WHERE_FFL_HERE) #define cmp_height(_cb1a, _cb1b) _cmp_height(_cb1a, _cb1b, WHERE_FFL_HERE) static char *EMPTY = ""; static char *db_user; static char *db_pass; // Currently hard coded at 4 characters static char *status_chars = "|/-\\"; static char *restorefrom; /* Restart data needed * ------------------- * After the DB load, 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 * 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: for each pool/user/worker it would be the start * of the time band containing the latest DB statsdate, * since all previous data was summarised to the DB and RAM and * deleted - use the oldest of these for all pools/users/workers * TODO: multiple pools is not yet handled by ckdb * TODO: handle a pool restart with a different instance name * since this would always make the userstats reload point * just after the instance name was changed - however * this can be handled for now by simply ignoring the * poolinstance * DB+RAM workers: created by auths so auths will resolve it * DB+RAM blocks: resolved by workinfo - any unsaved blocks (if any) * will be after the last DB workinfo * DB+RAM accountbalance (TODO): resolved by shares/workinfo/blocks * RAM workerstatus: last_auth, last_share, last_stats all handled by * DB load up to whatever the CCL restart point is, and then * corrected with the CCL reload * last_idle will be the last idle userstats in the CCL load or 0 * Code currently doesn't use last_idle, so for now this is OK * * TODO: handle the ckpool messages/CCL overlap in the start of the * ckpool messages that will arrive after the CCL load finishes * * 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_createdate_workinfo; tv_t newest_createdate_auths; tv_t newest_createdate_poolstats; tv_t userstats; tv_t newest_createdate_blocks; } LOADSTATUS; static LOADSTATUS dbstatus; /* Temporary while doing restart - it (of course) contains the fields * required to track the newest userstats per user/worker */ static K_TREE *userstats_db_root; static K_STORE *userstats_db; // 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 // 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) static char *pqerrmsg(PGconn *conn) { char *ptr, *buf = strdup(PQerrorMessage(conn)); ptr = buf + strlen(buf) - 1; while (ptr >= buf && (*ptr == '\n' || *ptr == '\r')) *(ptr--) = '\0'; while (--ptr >= buf) { if (*ptr == '\n' || *ptr == '\r' || *ptr == '\t') *ptr = ' '; } return buf; } #define PGLOG(__LOG, __str, __rescode, __conn) do { \ char *__buf = pqerrmsg(__conn); \ __LOG("%s(): %s failed (%d) '%s'", __func__, \ __str, (int)rescode, __buf); \ free(__buf); \ } while (0) #define PGLOGERR(_str, _rescode, _conn) PGLOG(LOGERR, _str, _rescode, _conn) #define PGLOGEMERG(_str, _rescode, _conn) PGLOG(LOGEMERG, _str, _rescode, _conn) /* N.B. STRNCPY() truncates, whereas txt_to_str() aborts ckdb if src > trg * If copying data from the DB, code should always use txt_to_str() since * data should never be lost/truncated if it came from the DB - * that simply implies a code bug or a database change that must be fixed */ #define STRNCPY(trg, src) do { \ strncpy((char *)(trg), (char *)(src), sizeof(trg)); \ trg[sizeof(trg) - 1] = '\0'; \ } while (0) #define STRNCPYSIZ(trg, src, siz) do { \ strncpy((char *)(trg), (char *)(src), siz); \ trg[siz - 1] = '\0'; \ } while (0) #define AR_SIZ 1024 #define APPEND_REALLOC_INIT(_buf, _off, _len) do { \ _len = AR_SIZ; \ (_buf) = malloc(_len); \ if (!(_buf)) \ quithere(1, "malloc (%d) OOM", (int)_len); \ (_buf)[0] = '\0'; \ _off = 0; \ } while(0) #define APPEND_REALLOC(_dst, _dstoff, _dstsiz, _src) do { \ size_t _newlen, _srclen = strlen(_src); \ _newlen = (_dstoff) + _srclen; \ if (_newlen >= (_dstsiz)) { \ _dstsiz = _newlen + AR_SIZ - (_newlen % AR_SIZ); \ _dst = realloc(_dst, _dstsiz); \ if (!(_dst)) \ quithere(1, "realloc (%d) OOM", (int)_dstsiz); \ } \ strcpy((_dst)+(_dstoff), _src); \ _dstoff += _srclen; \ } while(0) enum data_type { TYPE_STR, TYPE_BIGINT, TYPE_INT, TYPE_TV, TYPE_TVS, TYPE_CTV, TYPE_BLOB, TYPE_DOUBLE }; #define TXT_TO_STR(__nam, __fld, __data) txt_to_str(__nam, __fld, (__data), sizeof(__data)) #define TXT_TO_BIGINT(__nam, __fld, __data) txt_to_bigint(__nam, __fld, &(__data), sizeof(__data)) #define TXT_TO_INT(__nam, __fld, __data) txt_to_int(__nam, __fld, &(__data), sizeof(__data)) #define TXT_TO_TV(__nam, __fld, __data) txt_to_tv(__nam, __fld, &(__data), sizeof(__data)) #define TXT_TO_CTV(__nam, __fld, __data) txt_to_ctv(__nam, __fld, &(__data), sizeof(__data)) #define TXT_TO_BLOB(__nam, __fld, __data) txt_to_blob(__nam, __fld, __data) #define TXT_TO_DOUBLE(__nam, __fld, __data) txt_to_double(__nam, __fld, &(__data), sizeof(__data)) #define PQ_GET_FLD(__res, __row, __name, __fld, __ok) do { \ int __col = PQfnumber(__res, __name); \ if (__col == -1) { \ LOGERR("%s(): Unknown field '%s' row %d", __func__, __name, __row); \ __ok = false; \ } else \ __fld = PQgetvalue(__res, __row, __col); \ } while (0) // HISTORY FIELDS #define HISTORYDATECONTROL ",createdate,createby,createcode,createinet,expirydate" #define HISTORYDATECOUNT 5 #define HISTORYDATEFLDS(_res, _row, _data, _ok) do { \ char *_fld; \ PQ_GET_FLD(_res, _row, "createdate", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_TV("createdate", _fld, (_data)->createdate); \ PQ_GET_FLD(_res, _row, "createby", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("createby", _fld, (_data)->createby); \ PQ_GET_FLD(_res, _row, "createcode", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("createcode", _fld, (_data)->createcode); \ PQ_GET_FLD(_res, _row, "createinet", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("createinet", _fld, (_data)->createinet); \ PQ_GET_FLD(_res, _row, "expirydate", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_TV("expirydate", _fld, (_data)->expirydate); \ } while (0) #define HISTORYDATECONTROLFIELDS \ tv_t createdate; \ char createby[TXT_SML+1]; \ char createcode[TXT_MED+1]; \ char createinet[TXT_MED+1]; \ tv_t expirydate #define HISTORYDATEPARAMS(_params, _his_pos, _row) do { \ _params[_his_pos++] = tv_to_buf(&(_row->createdate), NULL, 0); \ _params[_his_pos++] = str_to_buf(_row->createby, NULL, 0); \ _params[_his_pos++] = str_to_buf(_row->createcode, NULL, 0); \ _params[_his_pos++] = str_to_buf(_row->createinet, NULL, 0); \ _params[_his_pos++] = tv_to_buf(&(_row->expirydate), NULL, 0); \ } while (0) // 6-Jun-6666 06:06:06+00 #define DEFAULT_EXPIRY 148204965966L // 1-Jun-6666 00:00:00+00 #define COMPARE_EXPIRY 148204512000L static const tv_t default_expiry = { DEFAULT_EXPIRY, 0L }; // 31-Dec-9999 23:59:59+00 #define DATE_S_EOT 253402300799L #define DATE_uS_EOT 0L static const tv_t date_eot = { DATE_S_EOT, DATE_uS_EOT }; // All data will be after: 2-Jan-2014 00:00:00+00 #define DATE_BEGIN 1388620800L static const tv_t date_begin = { DATE_BEGIN, 0L }; #define HISTORYDATEINIT(_row, _cd, _by, _code, _inet) do { \ _row->createdate.tv_sec = (_cd)->tv_sec; \ _row->createdate.tv_usec = (_cd)->tv_usec; \ STRNCPY(_row->createby, _by); \ STRNCPY(_row->createcode, _code); \ STRNCPY(_row->createinet, _inet); \ _row->expirydate.tv_sec = default_expiry.tv_sec; \ _row->expirydate.tv_usec = default_expiry.tv_usec; \ } while (0) // Override _row defaults if transfer fields are present #define HISTORYDATETRANSFER(_row) do { \ K_ITEM *item; \ item = optional_name("createby", 1, NULL); \ if (item) \ STRNCPY(_row->createby, DATA_TRANSFER(item)->data); \ item = optional_name("createcode", 1, NULL); \ if (item) \ STRNCPY(_row->createcode, DATA_TRANSFER(item)->data); \ item = optional_name("createinet", 1, NULL); \ if (item) \ STRNCPY(_row->createinet, DATA_TRANSFER(item)->data); \ } while (0) // MODIFY FIELDS #define MODIFYDATECONTROL ",createdate,createby,createcode,createinet" \ ",modifydate,modifyby,modifycode,modifyinet" #define MODIFYDATECOUNT 8 #define MODIFYUPDATECOUNT 4 #define MODIFYDATEFLDS(_res, _row, _data, _ok) do { \ char *_fld; \ PQ_GET_FLD(_res, _row, "createdate", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_TV("createdate", _fld, (_data)->createdate); \ PQ_GET_FLD(_res, _row, "createby", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("createby", _fld, (_data)->createby); \ PQ_GET_FLD(_res, _row, "createcode", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("createcode", _fld, (_data)->createcode); \ PQ_GET_FLD(_res, _row, "createinet", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("createinet", _fld, (_data)->createinet); \ PQ_GET_FLD(_res, _row, "modifydate", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_TV("modifydate", _fld, (_data)->modifydate); \ PQ_GET_FLD(_res, _row, "modifyby", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("modifyby", _fld, (_data)->modifyby); \ PQ_GET_FLD(_res, _row, "modifycode", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("modifycode", _fld, (_data)->modifycode); \ PQ_GET_FLD(_res, _row, "modifyinet", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("modifyinet", _fld, (_data)->modifyinet); \ } while (0) #define MODIFYDATECONTROLFIELDS \ tv_t createdate; \ char createby[TXT_SML+1]; \ char createcode[TXT_MED+1]; \ char createinet[TXT_MED+1]; \ tv_t modifydate; \ char modifyby[TXT_SML+1]; \ char modifycode[TXT_MED+1]; \ char modifyinet[TXT_MED+1] #define MODIFYDATEPARAMS(_params, _mod_pos, _row) do { \ _params[_mod_pos++] = tv_to_buf(&(_row->createdate), NULL, 0); \ _params[_mod_pos++] = str_to_buf(_row->createby, NULL, 0); \ _params[_mod_pos++] = str_to_buf(_row->createcode, NULL, 0); \ _params[_mod_pos++] = str_to_buf(_row->createinet, NULL, 0); \ _params[_mod_pos++] = tv_to_buf(&(_row->modifydate), NULL, 0); \ _params[_mod_pos++] = str_to_buf(_row->modifyby, NULL, 0); \ _params[_mod_pos++] = str_to_buf(_row->modifycode, NULL, 0); \ _params[_mod_pos++] = str_to_buf(_row->modifyinet, NULL, 0); \ } while (0) #define MODIFYUPDATEPARAMS(_params, _mod_pos, _row) do { \ _params[_mod_pos++] = tv_to_buf(&(_row->modifydate), NULL, 0); \ _params[_mod_pos++] = str_to_buf(_row->modifyby, NULL, 0); \ _params[_mod_pos++] = str_to_buf(_row->modifycode, NULL, 0); \ _params[_mod_pos++] = str_to_buf(_row->modifyinet, NULL, 0); \ } while (0) #define MODIFYDATEINIT(_row, _cd, _by, _code, _inet) do { \ _row->createdate.tv_sec = (_cd)->tv_sec; \ _row->createdate.tv_usec = (_cd)->tv_usec; \ STRNCPY(_row->createby, _by); \ STRNCPY(_row->createcode, _code); \ STRNCPY(_row->createinet, _inet); \ _row->modifydate.tv_sec = 0; \ _row->modifydate.tv_usec = 0; \ _row->modifyby[0] = '\0'; \ _row->modifycode[0] = '\0'; \ _row->modifyinet[0] = '\0'; \ } while (0) #define MODIFYUPDATE(_row, _cd, _by, _code, _inet) do { \ _row->modifydate.tv_sec = (_cd)->tv_sec; \ _row->modifydate.tv_usec = (_cd)->tv_usec; \ STRNCPY(_row->modifyby, _by); \ STRNCPY(_row->modifycode, _code); \ STRNCPY(_row->modifyinet, _inet); \ } while (0) // SIMPLE FIELDS #define SIMPLEDATECONTROL ",createdate,createby,createcode,createinet" #define SIMPLEDATECOUNT 4 #define SIMPLEDATEFLDS(_res, _row, _data, _ok) do { \ char *_fld; \ PQ_GET_FLD(_res, _row, "createdate", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_TV("createdate", _fld, (_data)->createdate); \ PQ_GET_FLD(_res, _row, "createby", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("createby", _fld, (_data)->createby); \ PQ_GET_FLD(_res, _row, "createcode", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("createcode", _fld, (_data)->createcode); \ PQ_GET_FLD(_res, _row, "createinet", _fld, _ok); \ if (!_ok) \ break; \ TXT_TO_STR("createinet", _fld, (_data)->createinet); \ } while (0) #define SIMPLEDATECONTROLFIELDS \ tv_t createdate; \ char createby[TXT_SML+1]; \ char createcode[TXT_MED+1]; \ char createinet[TXT_MED+1] #define SIMPLEDATEPARAMS(_params, _his_pos, _row) do { \ _params[_his_pos++] = tv_to_buf(&(_row->createdate), NULL, 0); \ _params[_his_pos++] = str_to_buf(_row->createby, NULL, 0); \ _params[_his_pos++] = str_to_buf(_row->createcode, NULL, 0); \ _params[_his_pos++] = str_to_buf(_row->createinet, NULL, 0); \ } while (0) #define SIMPLEDATEINIT(_row, _cd, _by, _code, _inet) do { \ _row->createdate.tv_sec = (_cd)->tv_sec; \ _row->createdate.tv_usec = (_cd)->tv_usec; \ STRNCPY(_row->createby, _by); \ STRNCPY(_row->createcode, _code); \ STRNCPY(_row->createinet, _inet); \ } while (0) #define SIMPLEDATEDEFAULT(_row, _cd) do { \ _row->createdate.tv_sec = (_cd)->tv_sec; \ _row->createdate.tv_usec = (_cd)->tv_usec; \ STRNCPY(_row->createby, (char *)"code"); \ STRNCPY(_row->createcode, (char *)__func__); \ STRNCPY(_row->createinet, (char *)"127.0.0.1"); \ } while (0) // Override _row defaults if transfer fields are present #define SIMPLEDATETRANSFER(_row) do { \ K_ITEM *item; \ item = optional_name("createby", 1, NULL); \ if (item) \ STRNCPY(_row->createby, DATA_TRANSFER(item)->data); \ item = optional_name("createcode", 1, NULL); \ if (item) \ STRNCPY(_row->createcode, DATA_TRANSFER(item)->data); \ item = optional_name("createinet", 1, NULL); \ if (item) \ STRNCPY(_row->createinet, DATA_TRANSFER(item)->data); \ } while (0) // For easy parameter constant strings #define PQPARAM1 "$1" #define PQPARAM2 "$1,$2" #define PQPARAM3 "$1,$2,$3" #define PQPARAM4 "$1,$2,$3,$4" #define PQPARAM5 "$1,$2,$3,$4,$5" #define PQPARAM6 "$1,$2,$3,$4,$5,$6" #define PQPARAM7 "$1,$2,$3,$4,$5,$6,$7" #define PQPARAM8 "$1,$2,$3,$4,$5,$6,$7,$8" #define PQPARAM9 "$1,$2,$3,$4,$5,$6,$7,$8,$9" #define PQPARAM10 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10" #define PQPARAM11 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11" #define PQPARAM12 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12" #define PQPARAM13 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13" #define PQPARAM14 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14" #define PQPARAM15 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15" #define PQPARAM16 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16" #define PQPARAM17 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17" #define PQPARAM26 "$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26" #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) // Different input data handling static bool reloading = false; // Data load is complete static bool startup_complete = false; // Tell the summarizer to die static bool summarizer_die = false; static const char *userpatt = "^[!-~]*$"; // no spaces 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" // TRANSFER #define NAME_SIZE 63 #define VALUE_SIZE 1023 typedef struct transfer { char name[NAME_SIZE+1]; char value[VALUE_SIZE+1]; char *data; } TRANSFER; #define ALLOC_TRANSFER 64 #define LIMIT_TRANSFER 0 #define DATA_TRANSFER(_item) ((TRANSFER *)(_item->data)) static K_TREE *transfer_root; static K_LIST *transfer_free; static K_STORE *transfer_store; // older version missing field defaults static TRANSFER auth_1 = { "poolinstance", "", auth_1.value }; static K_ITEM auth_poolinstance = { "tmp", NULL, NULL, (void *)(&auth_1) }; static TRANSFER poolstats_1 = { "elapsed", "0", poolstats_1.value }; static K_ITEM poolstats_elapsed = { "tmp", NULL, NULL, (void *)(&poolstats_1) }; static TRANSFER userstats_1 = { "elapsed", "0", userstats_1.value }; static K_ITEM userstats_elapsed = { "tmp", NULL, NULL, (void *)(&userstats_1) }; static TRANSFER userstats_2 = { "workername", "all", userstats_2.value }; static K_ITEM userstats_workername = { "tmp", NULL, NULL, (void *)(&userstats_2) }; static TRANSFER userstats_3 = { "idle", FALSE_STR, userstats_3.value }; static K_ITEM userstats_idle = { "tmp", NULL, NULL, (void *)(&userstats_3) }; static TRANSFER userstats_4 = { "eos", TRUE_STR, userstats_4.value }; static K_ITEM userstats_eos = { "tmp", NULL, NULL, (void *)(&userstats_4) }; // USERS typedef struct users { int64_t userid; char username[TXT_BIG+1]; char emailaddress[TXT_BIG+1]; tv_t joineddate; char passwordhash[TXT_BIG+1]; char secondaryuserid[TXT_SML+1]; HISTORYDATECONTROLFIELDS; } USERS; #define ALLOC_USERS 1024 #define LIMIT_USERS 0 #define DATA_USERS(_item) ((USERS *)(_item->data)) static K_TREE *users_root; static K_TREE *userid_root; static K_LIST *users_free; static K_STORE *users_store; /* TODO: for account settings - but do we want manual/auto payouts? // USERACCOUNTS typedef struct useraccounts { int64_t userid; int64_t payoutlimit; char autopayout[TXT_FLG+1]; HISTORYDATECONTROLFIELDS; } USERACCOUNTS; #define ALLOC_USERACCOUNTS 1024 #define LIMIT_USERACCOUNTS 0 #define DATA_USERACCOUNTS(_item) ((USERACCOUNTS *)(_item->data)) static K_TREE *useraccounts_root; static K_LIST *useraccounts_free; static K_STORE *useraccounts_store; */ // WORKERS typedef struct workers { int64_t workerid; int64_t userid; char workername[TXT_BIG+1]; // includes username int32_t difficultydefault; char idlenotificationenabled[TXT_FLAG+1]; int32_t idlenotificationtime; HISTORYDATECONTROLFIELDS; } WORKERS; #define ALLOC_WORKERS 1024 #define LIMIT_WORKERS 0 #define DATA_WORKERS(_item) ((WORKERS *)(_item->data)) static K_TREE *workers_root; static K_LIST *workers_free; static K_STORE *workers_store; #define DIFFICULTYDEFAULT_MIN 10 #define DIFFICULTYDEFAULT_MAX 1000000 #define DIFFICULTYDEFAULT_DEF DIFFICULTYDEFAULT_MIN #define DIFFICULTYDEFAULT_DEF_STR STRINT(DIFFICULTYDEFAULT_DEF) #define IDLENOTIFICATIONENABLED "y" #define IDLENOTIFICATIONDISABLED " " #define IDLENOTIFICATIONENABLED_DEF IDLENOTIFICATIONDISABLED #define IDLENOTIFICATIONTIME_MIN 10 #define IDLENOTIFICATIONTIME_MAX 60 #define IDLENOTIFICATIONTIME_DEF IDLENOTIFICATIONTIME_MIN #define IDLENOTIFICATIONTIME_DEF_STR STRINT(IDLENOTIFICATIONTIME_DEF) /* unused yet // PAYMENTADDRESSES typedef struct paymentaddresses { int64_t paymentaddressid; int64_t userid; char payaddress[TXT_BIG+1]; int32_t payratio; HISTORYDATECONTROLFIELDS; } PAYMENTADDRESSES; #define ALLOC_PAYMENTADDRESSES 1024 #define LIMIT_PAYMENTADDRESSES 0 #define DATA_PAYMENTADDRESSES(_item) ((PAYMENTADDRESSES *)(_item->data)) static K_TREE *paymentaddresses_root; static K_LIST *paymentaddresses_free; static K_STORE *paymentaddresses_store; */ // PAYMENTS typedef struct payments { int64_t paymentid; int64_t userid; tv_t paydate; char payaddress[TXT_BIG+1]; char originaltxn[TXT_BIG+1]; int64_t amount; char committxn[TXT_BIG+1]; char commitblockhash[TXT_BIG+1]; HISTORYDATECONTROLFIELDS; } PAYMENTS; #define ALLOC_PAYMENTS 1024 #define LIMIT_PAYMENTS 0 #define DATA_PAYMENTS(_item) ((PAYMENTS *)(_item->data)) static K_TREE *payments_root; static K_LIST *payments_free; static K_STORE *payments_store; /* unused yet // ACCOUNTBALANCE typedef struct accountbalance { int64_t userid; int64_t confirmedpaid; int64_t confirmedunpaid; int64_t pendingconfirm; int32_t heightupdate; HISTORYDATECONTROLFIELDS; } ACCOUNTBALANCE; #define ALLOC_ACCOUNTBALANCE 1024 #define LIMIT_ACCOUNTBALANCE 0 #define DATA_ACCOUNTBALANCE(_item) ((ACCOUNTBALANCE *)(_item->data)) static K_TREE *accountbalance_root; static K_LIST *accountbalance_free; static K_STORE *accountbalance_store; // ACCOUNTADJUSTMENT typedef struct accountadjustment { int64_t userid; char authority[TXT_BIG+1]; char *reason; int64_t amount; HISTORYDATECONTROLFIELDS; } ACCOUNTADJUSTMENT; #define ALLOC_ACCOUNTADJUSTMENT 100 #define LIMIT_ACCOUNTADJUSTMENT 0 #define DATA_ACCOUNTADJUSTMENT(_item) ((ACCOUNTADJUSTMENT *)(_item->data)) static K_TREE *accountadjustment_root; static K_LIST *accountadjustment_free; static K_STORE *accountadjustment_store; */ // IDCONTROL typedef struct idcontrol { char idname[TXT_SML+1]; int64_t lastid; MODIFYDATECONTROLFIELDS; } IDCONTROL; #define ALLOC_IDCONTROL 16 #define LIMIT_IDCONTROL 0 #define DATA_IDCONTROL(_item) ((IDCONTROL *)(_item->data)) // These are only used for db access - not stored in memory //static K_TREE *idcontrol_root; static K_LIST *idcontrol_free; static K_STORE *idcontrol_store; /* unused yet // OPTIONCONTROL typedef struct optioncontrol { char optionname[TXT_SML+1]; char *optionvalue; tv_t activationdate; int32_t activationheight; HISTORYDATECONTROLFIELDS; } OPTIONCONTROL; #define ALLOC_OPTIONCONTROL 64 #define LIMIT_OPTIONCONTROL 0 #define DATA_OPTIONCONTROL(_item) ((OPTIONCONTROL *)(_item->data)) static K_TREE *optioncontrol_root; static K_LIST *optioncontrol_free; static K_STORE *optioncontrol_store; */ // TODO: aging/discarding workinfo,shares // WORKINFO workinfo.id.json={...} typedef struct workinfo { int64_t workinfoid; char poolinstance[TXT_BIG+1]; char *transactiontree; char *merklehash; char prevhash[TXT_BIG+1]; char coinbase1[TXT_BIG+1]; char coinbase2[TXT_BIG+1]; char version[TXT_SML+1]; char bits[TXT_SML+1]; char ntime[TXT_SML+1]; int64_t reward; HISTORYDATECONTROLFIELDS; } WORKINFO; // ~10 hrs #define ALLOC_WORKINFO 1400 #define LIMIT_WORKINFO 0 #define DATA_WORKINFO(_item) ((WORKINFO *)(_item->data)) static K_TREE *workinfo_root; // created during data load then destroyed since not needed later static K_TREE *workinfo_height_root; static K_LIST *workinfo_free; static K_STORE *workinfo_store; // one in the current block static K_ITEM *workinfo_current; // first workinfo of current block static tv_t last_bc; // SHARES shares.id.json={...} typedef struct shares { int64_t workinfoid; int64_t userid; char workername[TXT_BIG+1]; int32_t clientid; char enonce1[TXT_SML+1]; char nonce2[TXT_BIG+1]; char nonce[TXT_SML+1]; double diff; double sdiff; int32_t errn; char error[TXT_SML+1]; char secondaryuserid[TXT_SML+1]; HISTORYDATECONTROLFIELDS; } SHARES; #define ALLOC_SHARES 10000 #define LIMIT_SHARES 0 #define DATA_SHARES(_item) ((SHARES *)(_item->data)) static K_TREE *shares_root; static K_LIST *shares_free; static K_STORE *shares_store; // SHAREERRORS shareerrors.id.json={...} typedef struct shareerrorss { int64_t workinfoid; int64_t userid; char workername[TXT_BIG+1]; int32_t clientid; int32_t errn; char error[TXT_SML+1]; char secondaryuserid[TXT_SML+1]; HISTORYDATECONTROLFIELDS; } SHAREERRORS; #define ALLOC_SHAREERRORS 10000 #define LIMIT_SHAREERRORS 0 #define DATA_SHAREERRORS(_item) ((SHAREERRORS *)(_item->data)) static K_TREE *shareerrors_root; static K_LIST *shareerrors_free; static K_STORE *shareerrors_store; // SHARESUMMARY typedef struct sharesummary { int64_t userid; char workername[TXT_BIG+1]; int64_t workinfoid; double diffacc; double diffsta; double diffdup; double diffhi; double diffrej; double shareacc; double sharesta; double sharedup; double sharehi; double sharerej; int64_t sharecount; int64_t errorcount; int64_t countlastupdate; // non-DB field bool inserted; // non-DB field bool saveaged; // non-DB field bool reset; // non-DB field tv_t firstshare; tv_t lastshare; char complete[TXT_FLAG+1]; MODIFYDATECONTROLFIELDS; } SHARESUMMARY; /* After this many shares added, we need to update the DB record The DB record is added with the 1st share */ #define SHARESUMMARY_UPDATE_EVERY 10 #define ALLOC_SHARESUMMARY 10000 #define LIMIT_SHARESUMMARY 0 #define DATA_SHARESUMMARY(_item) ((SHARESUMMARY *)(_item->data)) #define SUMMARY_NEW 'n' #define SUMMARY_AGED 'a' #define SUMMARY_CONFIRM 'y' static K_TREE *sharesummary_root; static K_TREE *sharesummary_workinfoid_root; static K_LIST *sharesummary_free; static K_STORE *sharesummary_store; // BLOCKS block.id.json={...} typedef struct blocks { int32_t height; char blockhash[TXT_BIG+1]; int64_t workinfoid; int64_t userid; char workername[TXT_BIG+1]; int32_t clientid; char enonce1[TXT_SML+1]; char nonce2[TXT_BIG+1]; char nonce[TXT_SML+1]; int64_t reward; char confirmed[TXT_FLAG+1]; HISTORYDATECONTROLFIELDS; } BLOCKS; #define ALLOC_BLOCKS 100 #define LIMIT_BLOCKS 0 #define DATA_BLOCKS(_item) ((BLOCKS *)(_item->data)) #define BLOCKS_NEW 'n' #define BLOCKS_CONFIRM '1' static K_TREE *blocks_root; static K_LIST *blocks_free; static K_STORE *blocks_store; /* // MININGPAYOUTS typedef struct miningpayouts { int64_t miningpayoutid; int64_t userid; int32_t height; char blockhash[TXT_BIG+1]; int64_t amount; HISTORYDATECONTROLFIELDS; } MININGPAYOUTS; #define ALLOC_MININGPAYOUTS 1000 #define LIMIT_MININGPAYOUTS 0 #define DATA_MININGPAYOUTS(_item) ((MININGPAYOUTS *)(_item->data)) static K_TREE *miningpayouts_root; static K_LIST *miningpayouts_free; static K_STORE *miningpayouts_store; // EVENTLOG typedef struct eventlog { int64_t eventlogid; char poolinstance[TXT_BIG+1]; char eventlogcode[TXT_SML+1]; char *eventlogdescription; HISTORYDATECONTROLFIELDS; } EVENTLOG; #define ALLOC_EVENTLOG 100 #define LIMIT_EVENTLOG 0 #define DATA_EVENTLOG(_item) ((EVENTLOG *)(_item->data)) static K_TREE *eventlog_root; static K_LIST *eventlog_free; static K_STORE *eventlog_store; */ // AUTHS authorise.id.json={...} typedef struct auths { int64_t authid; char poolinstance[TXT_BIG+1]; int64_t userid; char workername[TXT_BIG+1]; int32_t clientid; char enonce1[TXT_SML+1]; char useragent[TXT_BIG+1]; HISTORYDATECONTROLFIELDS; } AUTHS; #define ALLOC_AUTHS 1000 #define LIMIT_AUTHS 0 #define DATA_AUTHS(_item) ((AUTHS *)(_item->data)) static K_TREE *auths_root; static K_LIST *auths_free; static K_STORE *auths_store; // POOLSTATS poolstats.id.json={...} // Store every > 9.5m? #define STATS_PER (9.5*60.0) typedef struct poolstats { char poolinstance[TXT_BIG+1]; int64_t elapsed; int32_t users; int32_t workers; double hashrate; double hashrate5m; double hashrate1hr; double hashrate24hr; SIMPLEDATECONTROLFIELDS; } POOLSTATS; #define ALLOC_POOLSTATS 10000 #define LIMIT_POOLSTATS 0 #define DATA_POOLSTATS(_item) ((POOLSTATS *)(_item->data)) static K_TREE *poolstats_root; static K_LIST *poolstats_free; static K_STORE *poolstats_store; // USERSTATS userstats.id.json={...} // Pool sends each user (staggered) once per 10m typedef struct userstats { char poolinstance[TXT_BIG+1]; int64_t userid; char workername[TXT_BIG+1]; int64_t elapsed; double hashrate; double hashrate5m; double hashrate1hr; double hashrate24hr; bool idle; // Non-db field char summarylevel[TXT_FLAG+1]; // Initially SUMMARY_NONE in RAM int32_t summarycount; tv_t statsdate; SIMPLEDATECONTROLFIELDS; } USERSTATS; /* USERSTATS protocol includes a boolean 'eos' that when true, * we have received the full set of data for the given * createdate batch, and thus can move all (complete) records * matching the createdate from userstats_eos_store into the tree */ #define ALLOC_USERSTATS 10000 #define LIMIT_USERSTATS 0 #define DATA_USERSTATS(_item) ((USERSTATS *)(_item->data)) static K_TREE *userstats_root; static K_TREE *userstats_statsdate_root; // ordered by statsdate first static K_TREE *userstats_workerstatus_root; // during data load static K_LIST *userstats_free; static K_STORE *userstats_store; // Awaiting EOS static K_STORE *userstats_eos_store; // Temporary while summarising static K_STORE *userstats_summ; /* 1.5 x how often we expect to get user's stats from ckpool * This is used when grouping the sub-worker stats into a single user * We add each worker's latest stats to the total - except we ignore * any worker with newest stats being older than USERSTATS_PER_S */ #define USERSTATS_PER_S 900 /* on the allusers page, show any with stats in the last ... */ #define ALLUSERS_LIMIT_S 3600 #define SUMMARY_NONE '0' #define SUMMARY_DB '1' #define SUMMARY_FULL '2' /* Userstats get stored in the DB for each time band of this * amount from midnight (UTC+00) * Thus we simply put each stats value in the time band of the * stat's timestamp * Userstats are sumarised in the the same userstats table * If USERSTATS_DB_S is close to the expected time per USERSTATS * then it will have higher variance i.e. obviously: a higher * average of stats per sample will mean a lower SD of the number * of stats per sample * The #if below ensures USERSTATS_DB_S times an integer = a day * so the last band is the same size as the rest - * and will graph easily * Obvious WARNING - the smaller this is, the more stats in the DB * This is summary level '1' */ #define USERSTATS_DB_S 3600 #if (((24*60*60) % USERSTATS_DB_S) != 0) #error "USERSTATS_DB_S times an integer must = a day" #endif #if ((24*60*60) < USERSTATS_DB_S) #error "USERSTATS_DB_S must be at most 1 day" #endif /* We summarise and discard userstats that are older than the * maximum of USERSTATS_DB_S, USERSTATS_PER_S, ALLUSERS_LIMIT_S */ #if (USERSTATS_PER_S > ALLUSERS_LIMIT_S) #if (USERSTATS_PER_S > USERSTATS_DB_S) #define USERSTATS_AGE USERSTATS_PER_S #else #define USERSTATS_AGE USERSTATS_DB_S #endif #else #if (ALLUSERS_LIMIT_S > USERSTATS_DB_S) #define USERSTATS_AGE ALLUSERS_LIMIT_S #else #define USERSTATS_AGE USERSTATS_DB_S #endif #endif /* TODO: summarisation of the userstats after this many days are done * at the day level and the above stats are deleted from the db * Obvious WARNING - the larger this is, the more stats in the DB * This is summary level '2' */ #define USERSTATS_DB_D 7 #define USERSTATS_DB_DS (USERSTATS_DB_D * (60*60*24)) // true if _new is newer, i.e. _old is before _new #define tv_newer(_old, _new) (((_old)->tv_sec == (_new)->tv_sec) ? \ ((_old)->tv_usec < (_new)->tv_usec) : \ ((_old)->tv_sec < (_new)->tv_sec)) #define tv_equal(_a, _b) (((_a)->tv_sec == (_b)->tv_sec) && \ ((_a)->tv_usec == (_b)->tv_usec)) // WORKERSTATUS from various incoming data typedef struct workerstatus { int64_t userid; char workername[TXT_BIG+1]; tv_t last_auth; tv_t last_share; tv_t last_stats; tv_t last_idle; } WORKERSTATUS; #define ALLOC_WORKERSTATUS 1000 #define LIMIT_WORKERSTATUS 0 #define DATA_WORKERSTATUS(_item) ((WORKERSTATUS *)(_item->data)) static K_TREE *workerstatus_root; static K_LIST *workerstatus_free; static K_STORE *workerstatus_store; static char logname[512]; #define LOGFILE(_msg) rotating_log(logname, _msg) #define LOGDUP "dup." 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; now_t = time(NULL); tm = localtime(&now_t); 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)); 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); } 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; } static void dsp_transfer(K_ITEM *item, FILE *stream) { TRANSFER *t = NULL; if (!item) fprintf(stream, "%s() called with (null) item\n", __func__); else { t = DATA_TRANSFER(item); fprintf(stream, " name='%s' data='%s' malloc=%c\n", t->name, t->data, (t->value == t->data) ? 'N' : 'Y'); } } // order by name asc static double cmp_transfer(K_ITEM *a, K_ITEM *b) { double c = (double)strcmp(DATA_TRANSFER(a)->name, DATA_TRANSFER(b)->name); return c; } static K_ITEM *find_transfer(char *name) { TRANSFER transfer; K_TREE_CTX ctx[1]; K_ITEM look; STRNCPY(transfer.name, name); look.data = (void *)(&transfer); return find_in_ktree(transfer_root, &look, cmp_transfer, ctx); } static K_ITEM *optional_name(char *name, int len, char *patt) { K_ITEM *item; char *value; regex_t re; int ret; item = find_transfer(name); if (!item) return NULL; value = DATA_TRANSFER(item)->data; if (!value || (int)strlen(value) < len) return NULL; if (patt) { if (regcomp(&re, patt, REG_NOSUB) != 0) return NULL; ret = regexec(&re, value, (size_t)0, NULL, 0); regfree(&re); if (ret != 0) return NULL; } return item; } #define require_name(_name, _len, _patt, _reply, _siz) \ _require_name(_name, _len, _patt, _reply, _siz, \ WHERE_FFL_HERE) static K_ITEM *_require_name(char *name, int len, char *patt, char *reply, size_t siz, WHERE_FFL_ARGS) { K_ITEM *item; char *value; regex_t re; size_t dlen; int ret; item = find_transfer(name); if (!item) { LOGERR("%s(): failed, field '%s' missing from %s():%d", __func__, name, func, line); snprintf(reply, siz, "failed.missing %s", name); return NULL; } value = DATA_TRANSFER(item)->data; if (value) dlen = strlen(value); else dlen = 0; if (!value || (int)dlen < len) { LOGERR("%s(): failed, field '%s' short (%s%d<%d) from %s():%d", __func__, name, value ? EMPTY : "null", (int)dlen, len, func, line); snprintf(reply, siz, "failed.short %s", name); return NULL; } if (patt) { if (regcomp(&re, patt, REG_NOSUB) != 0) { LOGERR("%s(): failed, field '%s' failed to" " compile patt from %s():%d", __func__, name, func, line); snprintf(reply, siz, "failed.REC %s", name); return NULL; } ret = regexec(&re, value, (size_t)0, NULL, 0); regfree(&re); if (ret != 0) { LOGERR("%s(): failed, field '%s' invalid from %s():%d", __func__, name, func, line); snprintf(reply, siz, "failed.invalid %s", name); return NULL; } } return item; } static void _txt_to_data(enum data_type typ, char *nam, char *fld, void *data, size_t siz, WHERE_FFL_ARGS) { char *tmp; switch (typ) { case TYPE_STR: // A database field being bigger than local storage is a fatal error if (siz < (strlen(fld)+1)) { quithere(1, "Field %s structure size %d is smaller than db %d" WHERE_FFL, nam, (int)siz, (int)strlen(fld)+1, WHERE_FFL_PASS); } strcpy((char *)data, fld); break; case TYPE_BIGINT: if (siz != sizeof(int64_t)) { quithere(1, "Field %s bigint incorrect structure size %d - should be %d" WHERE_FFL, nam, (int)siz, (int)sizeof(int64_t), WHERE_FFL_PASS); } *((long long *)data) = atoll(fld); break; case TYPE_INT: if (siz != sizeof(int32_t)) { quithere(1, "Field %s int incorrect structure size %d - should be %d" WHERE_FFL, nam, (int)siz, (int)sizeof(int32_t), WHERE_FFL_PASS); } *((int32_t *)data) = atoi(fld); break; case TYPE_TV: if (siz != sizeof(tv_t)) { quithere(1, "Field %s timeval 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; struct tm tm; time_t tim; int n; n = sscanf(fld, "%u-%u-%u %u:%u:%u+%u", &yyyy, &mm, &dd, &HH, &MM, &SS, &tz); if (n != 7) { // allow uS n = sscanf(fld, "%u-%u-%u %u:%u:%u.%u+%u", &yyyy, &mm, &dd, &HH, &MM, &SS, &uS, &tz); if (n != 8) { quithere(1, "Field %s timeval 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 = mktime(&tm); // Fix TZ offsets errors if (tim > COMPARE_EXPIRY) { ((tv_t *)data)->tv_sec = default_expiry.tv_sec; ((tv_t *)data)->tv_usec = default_expiry.tv_usec; } else { ((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 timeval 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; char *buf2; 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: buf2 = malloc(siz); if (!buf2) { quithere(1, "OOM (%d)" WHERE_FFL, (int)siz, WHERE_FFL_PASS); } localtime_r(&(((struct timeval *)data)->tv_sec), &tm); strftime(buf2, siz, "%Y-%m-%d %H:%M:%S", &tm); snprintf(buf, siz, "%s.%06ld", buf2, (((struct timeval *)data)->tv_usec)); free(buf2); break; case TYPE_CTV: snprintf(buf, siz, "%ld,%ld", (((struct timeval *)data)->tv_sec), (((struct timeval *)data)->tv_usec)); break; case TYPE_TVS: snprintf(buf, siz, "%ld", (((struct timeval *)data)->tv_sec)); break; case TYPE_DOUBLE: snprintf(buf, siz, "%f", *((double *)data)); break; } return buf; } #define str_to_buf(_data, _buf, _siz) _str_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) #define bigint_to_buf(_data, _buf, _siz) _bigint_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) #define int_to_buf(_data, _buf, _siz) _int_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) #define tv_to_buf(_data, _buf, _siz) _tv_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) #define ctv_to_buf(_data, _buf, _siz) _ctv_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) #define tvs_to_buf(_data, _buf, _siz) _tvs_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) //#define blob_to_buf(_data, _buf, _siz) _blob_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) #define double_to_buf(_data, _buf, _siz) _double_to_buf(_data, _buf, _siz, WHERE_FFL_HERE) static char *_str_to_buf(char data[], char *buf, size_t siz, WHERE_FFL_ARGS) { return _data_to_buf(TYPE_STR, (void *)data, buf, siz, WHERE_FFL_PASS); } static char *_bigint_to_buf(int64_t data, char *buf, size_t siz, WHERE_FFL_ARGS) { return _data_to_buf(TYPE_BIGINT, (void *)(&data), buf, siz, WHERE_FFL_PASS); } static char *_int_to_buf(int32_t data, char *buf, size_t siz, WHERE_FFL_ARGS) { return _data_to_buf(TYPE_INT, (void *)(&data), buf, siz, WHERE_FFL_PASS); } static char *_tv_to_buf(tv_t *data, char *buf, size_t siz, WHERE_FFL_ARGS) { return _data_to_buf(TYPE_TV, (void *)data, buf, siz, WHERE_FFL_PASS); } // Convert tv to S,uS static char *_ctv_to_buf(tv_t *data, char *buf, size_t siz, WHERE_FFL_ARGS) { return _data_to_buf(TYPE_CTV, (void *)data, buf, siz, WHERE_FFL_PASS); } // Convert tv to seconds (ignore uS) static char *_tvs_to_buf(tv_t *data, char *buf, size_t siz, WHERE_FFL_ARGS) { return _data_to_buf(TYPE_TVS, (void *)data, buf, siz, WHERE_FFL_PASS); } /* unused yet static char *_blob_to_buf(char *data, char *buf, size_t siz, WHERE_FFL_ARGS) { return _data_to_buf(TYPE_BLOB, (void *)data, buf, siz, WHERE_FFL_PASS); } */ static char *_double_to_buf(double data, char *buf, size_t siz, WHERE_FFL_ARGS) { return _data_to_buf(TYPE_DOUBLE, (void *)(&data), buf, siz, WHERE_FFL_PASS); } static PGconn *dbconnect() { char conninfo[128]; PGconn *conn; snprintf(conninfo, sizeof(conninfo), "host=127.0.0.1 dbname=ckdb user=%s", db_user); 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; 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); res = PQexec(conn, qry); 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); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Update", rescode, conn); lastid = 0; } for (n = 0; n < par; n++) free(params[n]); cleanup: PQclear(res); return lastid; } // order by userid asc,workername asc static double cmp_workerstatus(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_WORKERSTATUS(a)->userid - DATA_WORKERSTATUS(b)->userid); if (c == 0.0) { c = strcmp(DATA_WORKERSTATUS(a)->workername, DATA_WORKERSTATUS(b)->workername); } return c; } static K_ITEM *get_workerstatus(int64_t userid, char *workername) { WORKERSTATUS workerstatus; K_TREE_CTX ctx[1]; K_ITEM look; workerstatus.userid = userid; STRNCPY(workerstatus.workername, workername); look.data = (void *)(&workerstatus); return find_in_ktree(workerstatus_root, &look, cmp_workerstatus, ctx); } static K_ITEM *_find_create_workerstatus(int64_t userid, char *workername, bool create) { K_ITEM *item = NULL; WORKERSTATUS *row; item = get_workerstatus(userid, workername); if (!item && create) { K_WLOCK(workerstatus_free); item = k_unlink_head(workerstatus_free); row = DATA_WORKERSTATUS(item); bzero(row, sizeof(*row)); row->userid = userid; STRNCPY(row->workername, workername); workerstatus_root = add_to_ktree(workerstatus_root, item, cmp_workerstatus); k_add_head(workerstatus_store, item); K_WUNLOCK(workerstatus_free); } return item; } #define find_create_workerstatus(_u, _w) _find_create_workerstatus(_u, _w, true) #define find_workerstatus(_u, _w) _find_create_workerstatus(_u, _w, false) static double cmp_userstats_workerstatus(K_ITEM *a, K_ITEM *b); static double cmp_sharesummary(K_ITEM *a, K_ITEM *b); /* All data is loaded, now update workerstatus last_share, last_idle, last_stats * shares are all part of a sharesummary so no need to search shares */ static void workerstatus_ready() { K_TREE_CTX ws_ctx[1], us_ctx[1], ss_ctx[1]; K_ITEM *ws_item, look, *us_item, *ss_item; USERSTATS userstats; SHARESUMMARY sharesummary; ws_item = first_in_ktree(workerstatus_root, ws_ctx); while (ws_item) { userstats.userid = DATA_WORKERSTATUS(ws_item)->userid; STRNCPY(userstats.workername, DATA_WORKERSTATUS(ws_item)->workername); userstats.statsdate.tv_sec = date_eot.tv_sec; userstats.statsdate.tv_usec = date_eot.tv_usec; look.data = (void *)(&userstats); us_item = find_before_in_ktree(userstats_workerstatus_root, &look, cmp_userstats_workerstatus, us_ctx); if (us_item) { if (DATA_USERSTATS(us_item)->idle) { if (tv_newer(&(DATA_WORKERSTATUS(ws_item)->last_idle), &(DATA_USERSTATS(us_item)->statsdate))) { copy_tv(&(DATA_WORKERSTATUS(ws_item)->last_idle), &(DATA_USERSTATS(us_item)->statsdate)); } } else { if (tv_newer(&(DATA_WORKERSTATUS(ws_item)->last_stats), &(DATA_USERSTATS(us_item)->statsdate))) { copy_tv(&(DATA_WORKERSTATUS(ws_item)->last_stats), &(DATA_USERSTATS(us_item)->statsdate)); } } } sharesummary.userid = DATA_WORKERSTATUS(ws_item)->userid; STRNCPY(sharesummary.workername, DATA_WORKERSTATUS(ws_item)->workername); sharesummary.workinfoid = MAXID; look.data = (void *)(&sharesummary); ss_item = find_before_in_ktree(sharesummary_root, &look, cmp_sharesummary, ss_ctx); if (ss_item) { if (tv_newer(&(DATA_WORKERSTATUS(ws_item)->last_share), &(DATA_SHARESUMMARY(ss_item)->lastshare))) { copy_tv(&(DATA_WORKERSTATUS(ws_item)->last_share), &(DATA_SHARESUMMARY(ss_item)->lastshare)); } } ws_item = next_in_ktree(ws_ctx); } free_ktree(userstats_workerstatus_root, NULL); } static void workerstatus_update(AUTHS *auths, SHARES *shares, USERSTATS *userstats, SHARESUMMARY *sharesummary) { WORKERSTATUS *row; K_ITEM *item; LOGDEBUG("%s()", __func__); if (auths) { item = find_create_workerstatus(auths->userid, auths->workername); row = DATA_WORKERSTATUS(item); if (tv_newer(&(row->last_auth), &(auths->createdate))) copy_tv(&(row->last_auth), &(auths->createdate)); } if (startup_complete && shares) { item = find_create_workerstatus(shares->userid, shares->workername); row = DATA_WORKERSTATUS(item); if (tv_newer(&(row->last_share), &(shares->createdate))) copy_tv(&(row->last_share), &(shares->createdate)); } if (startup_complete && userstats) { item = find_create_workerstatus(userstats->userid, userstats->workername); row = DATA_WORKERSTATUS(item); if (userstats->idle) { if (tv_newer(&(row->last_idle), &(userstats->statsdate))) copy_tv(&(row->last_idle), &(userstats->statsdate)); } else { if (tv_newer(&(row->last_stats), &(userstats->statsdate))) copy_tv(&(row->last_stats), &(userstats->statsdate)); } } if (startup_complete && sharesummary) { item = find_create_workerstatus(sharesummary->userid, sharesummary->workername); row = DATA_WORKERSTATUS(item); if (tv_newer(&(row->last_share), &(sharesummary->lastshare))) copy_tv(&(row->last_share), &(sharesummary->lastshare)); } } // default tree order by username asc,expirydate desc static double cmp_users(K_ITEM *a, K_ITEM *b) { double c = strcmp(DATA_USERS(a)->username, DATA_USERS(b)->username); if (c == 0.0) { c = tvdiff(&(DATA_USERS(b)->expirydate), &(DATA_USERS(a)->expirydate)); } return c; } // order by userid asc,expirydate desc static double cmp_userid(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_USERS(a)->userid - DATA_USERS(b)->userid); if (c == 0.0) { c = tvdiff(&(DATA_USERS(b)->expirydate), &(DATA_USERS(a)->expirydate)); } return c; } static K_ITEM *find_users(char *username) { USERS users; K_TREE_CTX ctx[1]; K_ITEM look; STRNCPY(users.username, username); users.expirydate.tv_sec = default_expiry.tv_sec; users.expirydate.tv_usec = default_expiry.tv_usec; look.data = (void *)(&users); return find_in_ktree(users_root, &look, cmp_users, ctx); } static K_ITEM *find_userid(int64_t userid) { USERS users; K_TREE_CTX ctx[1]; K_ITEM look; users.userid = userid; users.expirydate.tv_sec = default_expiry.tv_sec; users.expirydate.tv_usec = default_expiry.tv_usec; look.data = (void *)(&users); return find_in_ktree(userid_root, &look, cmp_userid, ctx); } static bool users_add(PGconn *conn, char *username, char *emailaddress, char *passwordhash, tv_t *now, char *by, char *code, char *inet) { ExecStatusType rescode; PGresult *res; K_ITEM *item; int n; USERS *row; char *ins; char tohash[64]; uint64_t hash; __maybe_unused uint64_t tmp; bool ok = false; char *params[6 + HISTORYDATECOUNT]; int par; LOGDEBUG("%s(): add", __func__); K_WLOCK(users_free); item = k_unlink_head(users_free); K_WUNLOCK(users_free); row = DATA_USERS(item); row->userid = nextid(conn, "userid", (int64_t)(666 + (rand() % 334)), now, by, code, inet); if (row->userid == 0) goto unitem; // TODO: pre-check the username exists? (to save finding out via a DB error) STRNCPY(row->username, username); STRNCPY(row->emailaddress, emailaddress); STRNCPY(row->passwordhash, passwordhash); snprintf(tohash, sizeof(tohash), "%s&#%s", username, emailaddress); HASH_BER(tohash, strlen(tohash), 1, hash, tmp); __bin2hex(row->secondaryuserid, (void *)(&hash), sizeof(hash)); HISTORYDATEINIT(row, now, by, code, inet); // copy createdate row->joineddate.tv_sec = row->createdate.tv_sec; row->joineddate.tv_usec = row->createdate.tv_usec; par = 0; params[par++] = bigint_to_buf(row->userid, NULL, 0); params[par++] = str_to_buf(row->username, NULL, 0); params[par++] = str_to_buf(row->emailaddress, NULL, 0); params[par++] = tv_to_buf(&(row->joineddate), NULL, 0); params[par++] = str_to_buf(row->passwordhash, NULL, 0); params[par++] = str_to_buf(row->secondaryuserid, NULL, 0); HISTORYDATEPARAMS(params, par, row); PARCHK(par, params); ins = "insert into users " "(userid,username,emailaddress,joineddate,passwordhash," "secondaryuserid" HISTORYDATECONTROL ") values (" PQPARAM11 ")"; res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0); 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: K_WLOCK(users_free); if (!ok) k_add_head(users_free, item); else { users_root = add_to_ktree(users_root, item, cmp_users); userid_root = add_to_ktree(userid_root, item, cmp_userid); k_add_head(users_store, item); } K_WUNLOCK(users_free); return ok; } static bool users_fill(PGconn *conn) { ExecStatusType rescode; PGresult *res; K_ITEM *item; int n, i; USERS *row; char *field; char *sel; int fields = 6; bool ok; LOGDEBUG("%s(): select", __func__); sel = "select " "userid,username,emailaddress,joineddate,passwordhash," "secondaryuserid" HISTORYDATECONTROL " from users"; res = PQexec(conn, sel); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Select", rescode, conn); PQclear(res); return false; } n = PQnfields(res); if (n != (fields + HISTORYDATECOUNT)) { LOGERR("%s(): Invalid field count - should be %d, but is %d", __func__, fields + HISTORYDATECOUNT, n); PQclear(res); return false; } n = PQntuples(res); LOGDEBUG("%s(): tree build count %d", __func__, n); ok = true; K_WLOCK(users_free); for (i = 0; i < n; i++) { item = k_unlink_head(users_free); row = DATA_USERS(item); PQ_GET_FLD(res, i, "userid", field, ok); if (!ok) break; TXT_TO_BIGINT("userid", field, row->userid); PQ_GET_FLD(res, i, "username", field, ok); if (!ok) break; TXT_TO_STR("username", field, row->username); PQ_GET_FLD(res, i, "emailaddress", field, ok); if (!ok) break; TXT_TO_STR("emailaddress", field, row->emailaddress); PQ_GET_FLD(res, i, "joineddate", field, ok); if (!ok) break; TXT_TO_TV("joineddate", field, row->joineddate); PQ_GET_FLD(res, i, "passwordhash", field, ok); if (!ok) break; TXT_TO_STR("passwordhash", field, row->passwordhash); PQ_GET_FLD(res, i, "secondaryuserid", field, ok); if (!ok) break; TXT_TO_STR("secondaryuserid", field, row->secondaryuserid); HISTORYDATEFLDS(res, i, row, ok); if (!ok) break; users_root = add_to_ktree(users_root, item, cmp_users); userid_root = add_to_ktree(userid_root, item, cmp_userid); k_add_head(users_store, item); } if (!ok) k_add_head(users_free, item); K_WUNLOCK(users_free); PQclear(res); if (ok) { LOGDEBUG("%s(): built", __func__); LOGWARNING("%s(): loaded %d users records", __func__, n); } return ok; } void users_reload() { PGconn *conn = dbconnect(); K_WLOCK(users_free); users_root = free_ktree(users_root, NULL); userid_root = free_ktree(userid_root, NULL); k_list_transfer_to_head(users_store, users_free); K_WUNLOCK(users_free); users_fill(conn); PQfinish(conn); } // order by userid asc,workername asc,expirydate desc static double cmp_workers(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_WORKERS(a)->userid - DATA_WORKERS(b)->userid); if (c == 0.0) { c = strcmp(DATA_WORKERS(a)->workername, DATA_WORKERS(b)->workername); if (c == 0.0) { c = tvdiff(&(DATA_WORKERS(b)->expirydate), &(DATA_WORKERS(a)->expirydate)); } } return c; } static K_ITEM *find_workers(int64_t userid, char *workername) { WORKERS workers; K_TREE_CTX ctx[1]; K_ITEM look; workers.userid = userid; STRNCPY(workers.workername, workername); workers.expirydate.tv_sec = default_expiry.tv_sec; workers.expirydate.tv_usec = default_expiry.tv_usec; look.data = (void *)(&workers); return find_in_ktree(workers_root, &look, cmp_workers, ctx); } static K_ITEM *workers_add(PGconn *conn, int64_t userid, char *workername, char *difficultydefault, char *idlenotificationenabled, char *idlenotificationtime, char *by, char *code, char *inet, tv_t *cd) { ExecStatusType rescode; PGresult *res; K_ITEM *item, *ret = NULL; int n; WORKERS *row; char *ins; char *params[6 + HISTORYDATECOUNT]; int par; int32_t diffdef; int32_t nottime; LOGDEBUG("%s(): add", __func__); K_WLOCK(workers_free); item = k_unlink_head(workers_free); K_WUNLOCK(workers_free); row = DATA_WORKERS(item); 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); 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); 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: K_WLOCK(workers_free); if (!ret) k_add_head(workers_free, item); else { workers_root = add_to_ktree(workers_root, item, cmp_workers); k_add_head(workers_store, item); } K_WUNLOCK(workers_free); return ret; } static bool workers_update(PGconn *conn, K_ITEM *item, char *difficultydefault, char *idlenotificationenabled, char *idlenotificationtime, char *by, char *code, char *inet, tv_t *cd) { ExecStatusType rescode; PGresult *res; int n; WORKERS *row; char *upd, *ins; bool ok = false; char *params[6 + HISTORYDATECOUNT]; int par; int32_t diffdef; char idlenot; int32_t nottime; LOGDEBUG("%s(): update", __func__); row = DATA_WORKERS(item); if (difficultydefault && *difficultydefault) { diffdef = atoi(difficultydefault); if (diffdef < DIFFICULTYDEFAULT_MIN) diffdef = row->difficultydefault; if (diffdef > DIFFICULTYDEFAULT_MAX) diffdef = row->difficultydefault; } else diffdef = row->difficultydefault; if (idlenotificationenabled && *idlenotificationenabled) { if (tolower(*idlenotificationenabled) == IDLENOTIFICATIONENABLED[0]) idlenot = IDLENOTIFICATIONENABLED[0]; else idlenot = IDLENOTIFICATIONDISABLED[0]; } else idlenot = row->idlenotificationenabled[0]; if (idlenotificationtime && *idlenotificationtime) { nottime = atoi(idlenotificationtime); if (nottime < IDLENOTIFICATIONTIME_MIN) nottime = row->idlenotificationtime; if (nottime > IDLENOTIFICATIONTIME_MAX) nottime = row->idlenotificationtime; } else nottime = row->idlenotificationtime; HISTORYDATEINIT(row, cd, by, code, inet); 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); res = PQexec(conn, "Begin"); 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); rescode = PQresultStatus(res); PQclear(res); if (!PGOK(rescode)) { PGLOGERR("Update", rescode, conn); res = PQexec(conn, "Rollback"); 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); rescode = PQresultStatus(res); PQclear(res); if (!PGOK(rescode)) { PGLOGERR("Insert", rescode, conn); res = PQexec(conn, "Rollback"); goto unparam; } res = PQexec(conn, "Commit"); } ok = true; unparam: PQclear(res); 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_ITEM *item; item = find_workers(userid, workername); if (item) { if (update) { workers_update(conn, item, diffdef, idlenotificationenabled, idlenotificationtime, by, code, inet, cd); } } else { // TODO: limit how many? item = workers_add(conn, userid, workername, diffdef, idlenotificationenabled, idlenotificationtime, by, code, inet, cd); } 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) { bool conned = false; K_ITEM *item; if (conn == NULL) { conn = dbconnect(); conned = true; } item = new_worker(conn, update, userid, workername, DIFFICULTYDEFAULT_DEF_STR, IDLENOTIFICATIONENABLED_DEF, IDLENOTIFICATIONTIME_DEF_STR, by, code, inet, cd); if (conned) PQfinish(conn); return item; } /* unused static K_ITEM *new_worker_find_user(PGconn *conn, bool update, char *username, char *workername, char *diffdef, char *idlenotificationenabled, char *idlenotificationtime, tv_t *now, char *by, char *code, char *inet) { K_ITEM *item; item = find_users(username); if (!item) return NULL; return new_worker(conn, update, DATA_USERS(item)->userid, workername, diffdef, idlenotificationenabled, idlenotificationtime, now, by, code, inet); } */ 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); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Select", rescode, conn); PQclear(res); return false; } n = PQnfields(res); if (n != (fields + HISTORYDATECOUNT)) { LOGERR("%s(): Invalid field count - should be %d, but is %d", __func__, fields + HISTORYDATECOUNT, n); PQclear(res); return false; } n = PQntuples(res); LOGDEBUG("%s(): tree build count %d", __func__, n); ok = true; K_WLOCK(workers_free); for (i = 0; i < n; i++) { item = k_unlink_head(workers_free); row = DATA_WORKERS(item); PQ_GET_FLD(res, i, "userid", field, ok); if (!ok) break; TXT_TO_BIGINT("userid", field, row->userid); PQ_GET_FLD(res, i, "workername", field, ok); if (!ok) break; TXT_TO_STR("workername", field, row->workername); PQ_GET_FLD(res, i, "difficultydefault", field, ok); if (!ok) break; TXT_TO_INT("difficultydefault", field, row->difficultydefault); PQ_GET_FLD(res, i, "idlenotificationenabled", field, ok); if (!ok) break; TXT_TO_STR("idlenotificationenabled", field, row->idlenotificationenabled); PQ_GET_FLD(res, i, "idlenotificationtime", field, ok); if (!ok) break; TXT_TO_INT("idlenotificationtime", field, row->idlenotificationtime); HISTORYDATEFLDS(res, i, row, ok); if (!ok) break; PQ_GET_FLD(res, i, "workerid", field, ok); if (!ok) break; TXT_TO_BIGINT("workerid", field, row->workerid); workers_root = add_to_ktree(workers_root, item, cmp_workers); k_add_head(workers_store, item); } if (!ok) k_add_head(workers_free, item); K_WUNLOCK(workers_free); PQclear(res); if (ok) { LOGDEBUG("%s(): built", __func__); LOGWARNING("%s(): loaded %d workers records", __func__, n); } return ok; } void workers_reload() { PGconn *conn = dbconnect(); K_WLOCK(workers_free); workers_root = free_ktree(workers_root, NULL); k_list_transfer_to_head(workers_store, workers_free); K_WUNLOCK(workers_free); workers_fill(conn); PQfinish(conn); } // order by userid asc,paydate asc,payaddress asc,expirydate desc static double cmp_payments(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_PAYMENTS(a)->userid - DATA_PAYMENTS(b)->userid); if (c == 0.0) { c = tvdiff(&(DATA_PAYMENTS(a)->paydate), &(DATA_PAYMENTS(b)->paydate)); if (c == 0.0) { c = strcmp(DATA_PAYMENTS(a)->payaddress, DATA_PAYMENTS(b)->payaddress); if (c == 0.0) { c = tvdiff(&(DATA_PAYMENTS(b)->expirydate), &(DATA_PAYMENTS(a)->expirydate)); } } } return c; } static bool payments_fill(PGconn *conn) { ExecStatusType rescode; PGresult *res; K_ITEM *item; int n, i; PAYMENTS *row; char *params[1]; int par; char *field; char *sel; int fields = 8; bool ok; LOGDEBUG("%s(): select", __func__); // TODO: handle selecting a subset, eg 20 per web page sel = "select " "userid,paydate,payaddress,originaltxn,amount,committxn,commitblockhash" HISTORYDATECONTROL ",paymentid from payments where expirydate=$1"; par = 0; params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); PARCHK(par, params); res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Select", rescode, conn); PQclear(res); return false; } n = PQnfields(res); if (n != (fields + HISTORYDATECOUNT)) { LOGERR("%s(): Invalid field count - should be %d, but is %d", __func__, fields + HISTORYDATECOUNT, n); PQclear(res); return false; } n = PQntuples(res); LOGDEBUG("%s(): tree build count %d", __func__, n); ok = true; K_WLOCK(payments_free); for (i = 0; i < n; i++) { item = k_unlink_head(payments_free); row = DATA_PAYMENTS(item); PQ_GET_FLD(res, i, "userid", field, ok); if (!ok) break; TXT_TO_BIGINT("userid", field, row->userid); PQ_GET_FLD(res, i, "paydate", field, ok); if (!ok) break; TXT_TO_TV("paydate", field, row->paydate); PQ_GET_FLD(res, i, "payaddress", field, ok); if (!ok) break; TXT_TO_STR("payaddress", field, row->payaddress); PQ_GET_FLD(res, i, "originaltxn", field, ok); if (!ok) break; TXT_TO_STR("originaltxn", field, row->originaltxn); PQ_GET_FLD(res, i, "amount", field, ok); if (!ok) break; TXT_TO_BIGINT("amount", field, row->amount); PQ_GET_FLD(res, i, "committxn", field, ok); if (!ok) break; TXT_TO_STR("committxn", field, row->committxn); PQ_GET_FLD(res, i, "commitblockhash", field, ok); if (!ok) break; TXT_TO_STR("commitblockhash", field, row->commitblockhash); HISTORYDATEFLDS(res, i, row, ok); if (!ok) break; PQ_GET_FLD(res, i, "paymentid", field, ok); if (!ok) break; TXT_TO_BIGINT("paymentid", field, row->paymentid); payments_root = add_to_ktree(payments_root, item, cmp_payments); k_add_head(payments_store, item); } if (!ok) k_add_head(payments_free, item); K_WUNLOCK(payments_free); PQclear(res); if (ok) { LOGDEBUG("%s(): built", __func__); LOGWARNING("%s(): loaded %d payments records", __func__, n); } return ok; } void payments_reload() { PGconn *conn = dbconnect(); K_WLOCK(payments_free); payments_root = free_ktree(payments_root, NULL); k_list_transfer_to_head(payments_store, payments_free); K_WUNLOCK(payments_free); payments_fill(conn); PQfinish(conn); } // order by workinfoid asc,expirydate asc static double cmp_workinfo(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_WORKINFO(a)->workinfoid - DATA_WORKINFO(b)->workinfoid); if (c == 0) { c = tvdiff(&(DATA_WORKINFO(a)->expirydate), &(DATA_WORKINFO(b)->expirydate)); } return c; } inline int32_t _coinbase1height(char *coinbase1, WHERE_FFL_ARGS) { int32_t height = 0; uchar *cb1; int siz; cb1 = ((uchar *)coinbase1) + 84; siz = ((hex2bin_tbl[*cb1]) << 4) + (hex2bin_tbl[*(cb1+1)]); // limit to 4 for int32_t and since ... that should last a while :) if (siz < 1 || siz > 4) { LOGERR("%s(): Invalid coinbase1 block height size (%d)" " require: 1..4" WHERE_FFL, __func__, siz, WHERE_FFL_PASS); return height; } siz *= 2; while (siz-- > 0) { height <<= 4; height += (int32_t)hex2bin_tbl[*(cb1+(siz^1)+2)]; } return height; } static double _cmp_height(char *coinbase1a, char *coinbase1b, WHERE_FFL_ARGS) { double c = (double)(_coinbase1height(coinbase1a, WHERE_FFL_PASS) - _coinbase1height(coinbase1b, WHERE_FFL_PASS)); return c; } // order by height asc,createdate asc static double cmp_workinfo_height(K_ITEM *a, K_ITEM *b) { double c = cmp_height(DATA_WORKINFO(a)->coinbase1, DATA_WORKINFO(b)->coinbase1); if (c == 0) { c = tvdiff(&(DATA_WORKINFO(a)->createdate), &(DATA_WORKINFO(b)->createdate)); } return c; } static K_ITEM *find_workinfo(int64_t workinfoid) { WORKINFO workinfo; K_TREE_CTX ctx[1]; K_ITEM look; workinfo.workinfoid = workinfoid; workinfo.expirydate.tv_sec = default_expiry.tv_sec; workinfo.expirydate.tv_usec = default_expiry.tv_usec; look.data = (void *)(&workinfo); return find_in_ktree(workinfo_root, &look, cmp_workinfo, ctx); } static int64_t workinfo_add(PGconn *conn, char *workinfoidstr, char *poolinstance, char *transactiontree, char *merklehash, char *prevhash, char *coinbase1, char *coinbase2, char *version, char *bits, char *ntime, char *reward, char *by, char *code, char *inet, tv_t *cd, bool igndup) { ExecStatusType rescode; K_TREE_CTX ctx[1]; PGresult *res; K_ITEM *item; int n; int64_t workinfoid = -1; WORKINFO *row; char *ins; char *params[11 + HISTORYDATECOUNT]; int par; LOGDEBUG("%s(): add", __func__); K_WLOCK(workinfo_free); item = k_unlink_head(workinfo_free); K_WUNLOCK(workinfo_free); row = DATA_WORKINFO(item); TXT_TO_BIGINT("workinfoid", workinfoidstr, row->workinfoid); STRNCPY(row->poolinstance, poolinstance); row->transactiontree = strdup(transactiontree); row->merklehash = strdup(merklehash); STRNCPY(row->prevhash, prevhash); STRNCPY(row->coinbase1, coinbase1); STRNCPY(row->coinbase2, coinbase2); STRNCPY(row->version, version); STRNCPY(row->bits, bits); STRNCPY(row->ntime, ntime); TXT_TO_BIGINT("reward", reward, row->reward); HISTORYDATEINIT(row, cd, by, code, inet); HISTORYDATETRANSFER(row); if (igndup && find_in_ktree(workinfo_root, item, cmp_workinfo, ctx)) { workinfoid = row->workinfoid; K_WLOCK(workinfo_free); k_add_head(workinfo_free, item); K_WUNLOCK(workinfo_free); return workinfoid; } par = 0; 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 ")"; res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Insert", rescode, conn); goto unparam; } workinfoid = row->workinfoid; unparam: PQclear(res); 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 { // Not currently needed in RAM free(row->transactiontree); row->transactiontree = strdup(EMPTY); workinfo_root = add_to_ktree(workinfo_root, item, cmp_workinfo); k_add_head(workinfo_store, item); // Remember the bc = 'cd' when the height changes if (workinfo_current) { if (cmp_height(DATA_WORKINFO(workinfo_current)->coinbase1, DATA_WORKINFO(item)->coinbase1) != 0) copy_tv(&last_bc, cd); } workinfo_current = item; } K_WUNLOCK(workinfo_free); return workinfoid; } #define sharesummary_update(_conn, _s_row, _e_row, _ss_item, _by, _code, _inet, _cd) \ _sharesummary_update(_conn, _s_row, _e_row, _ss_item, _by, _code, _inet, _cd, \ WHERE_FFL_HERE) static bool _sharesummary_update(PGconn *conn, SHARES *s_row, SHAREERRORS *e_row, K_ITEM *ss_item, char *by, char *code, char *inet, tv_t *cd, WHERE_FFL_ARGS); static double cmp_sharesummary_workinfoid(K_ITEM *a, K_ITEM *b); static double cmp_shares(K_ITEM *a, K_ITEM *b); /* N.B. a DB check can be done to find sharesummaries that were missed being * aged (and a possible problem with the aging process): * e.g. for a date D in the past of at least a few hours * select count(*) from sharesummary where createdate<'D' and complete='n'; * and can be easily corrected: * update sharesummary set complete='a' where createdate<'D' and complete='n'; * It's important to make sure the D value is far enough in the past such that * all the matching sharesummary records in ckdb have certainly completed * ckdb would need a restart to get the updated DB information though it would * not affect current ckdb code */ static bool workinfo_age(PGconn *conn, char *workinfoidstr, char *poolinstance, char *by, char *code, char *inet, tv_t *cd) { K_ITEM *wi_item, ss_look, *ss_item, s_look, *s_item, *tmp_item; K_TREE_CTX ss_ctx[1], s_ctx[1], tmp_ctx[1]; int64_t workinfoid; SHARESUMMARY sharesummary; SHARES shares; bool ok = false, conned = false, skipupdate; char error[1024]; LOGDEBUG("%s(): complete", __func__); TXT_TO_BIGINT("workinfoid", workinfoidstr, workinfoid); wi_item = find_workinfo(workinfoid); if (!wi_item) goto bye; if (strcmp(poolinstance, DATA_WORKINFO(wi_item)->poolinstance) != 0) goto bye; // Find the first matching sharesummary sharesummary.workinfoid = workinfoid; sharesummary.userid = -1; sharesummary.workername[0] = '\0'; ok = true; ss_look.data = (void *)(&sharesummary); ss_item = find_after_in_ktree(sharesummary_workinfoid_root, &ss_look, cmp_sharesummary_workinfoid, ss_ctx); while (ss_item && DATA_SHARESUMMARY(ss_item)->workinfoid == workinfoid) { error[0] = '\0'; skipupdate = false; if (reloading) { if (DATA_SHARESUMMARY(ss_item)->complete[0] == SUMMARY_AGED) skipupdate = true; } if (!skipupdate) { if (conn == NULL) { conn = dbconnect(); conned = true; } if (!sharesummary_update(conn, NULL, NULL, ss_item, by, code, inet, cd)) { LOGERR("%s(): Failed to age share summary %"PRId64"/%s/%"PRId64, __func__, DATA_SHARESUMMARY(ss_item)->userid, DATA_SHARESUMMARY(ss_item)->workername, DATA_SHARESUMMARY(ss_item)->workinfoid); ok = false; } } // Discard the shares either way shares.workinfoid = workinfoid; shares.userid = DATA_SHARESUMMARY(ss_item)->userid; strcpy(shares.workername, DATA_SHARESUMMARY(ss_item)->workername); shares.createdate.tv_sec = 0; shares.createdate.tv_usec = 0; s_look.data = (void *)(&shares); s_item = find_after_in_ktree(shares_root, &s_look, cmp_shares, s_ctx); K_WLOCK(shares_free); while (s_item) { if (DATA_SHARES(s_item)->workinfoid != workinfoid || DATA_SHARES(s_item)->userid != shares.userid || strcmp(DATA_SHARES(s_item)->workername, shares.workername) != 0) break; tmp_item = next_in_ktree(s_ctx); shares_root = remove_from_ktree(shares_root, s_item, cmp_shares, tmp_ctx); k_unlink_item(shares_store, s_item); if (reloading && skipupdate && !error[0]) { snprintf(error, sizeof(error), "reload found aged shares: %"PRId64"/%"PRId64"/%s", DATA_SHARES(s_item)->workinfoid, DATA_SHARES(s_item)->userid, DATA_SHARES(s_item)->workername); } k_add_head(shares_free, s_item); s_item = tmp_item; } K_WUNLOCK(shares_free); ss_item = next_in_ktree(ss_ctx); if (error[0]) LOGERR("%s(): %s", __func__, error); } if (conned) PQfinish(conn); bye: return ok; } static bool workinfo_fill(PGconn *conn) { ExecStatusType rescode; PGresult *res; K_ITEM *item; int n, i; WORKINFO *row; char *params[1]; int par; char *field; char *sel; int fields = 10; bool ok; LOGDEBUG("%s(): select", __func__); // TODO: select the data based on sharesummary since old data isn't needed // however, the ageing rules for workinfo will decide that also // keep the last block + current? sel = "select " // "workinfoid,poolinstance,transactiontree,merklehash,prevhash," "workinfoid,poolinstance,merklehash,prevhash," "coinbase1,coinbase2,version,bits,ntime,reward" HISTORYDATECONTROL " from workinfo where expirydate=$1"; par = 0; params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); PARCHK(par, params); res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Select", rescode, conn); PQclear(res); return false; } n = PQnfields(res); if (n != (fields + HISTORYDATECOUNT)) { LOGERR("%s(): Invalid field count - should be %d, but is %d", __func__, fields + HISTORYDATECOUNT, n); PQclear(res); return false; } n = PQntuples(res); LOGDEBUG("%s(): tree build count %d", __func__, n); ok = true; K_WLOCK(workinfo_free); for (i = 0; i < n; i++) { item = k_unlink_head(workinfo_free); row = DATA_WORKINFO(item); PQ_GET_FLD(res, i, "workinfoid", field, ok); if (!ok) break; TXT_TO_BIGINT("workinfoid", field, row->workinfoid); PQ_GET_FLD(res, i, "poolinstance", field, ok); if (!ok) break; TXT_TO_STR("poolinstance", field, row->poolinstance); /* Not currently needed in RAM PQ_GET_FLD(res, i, "transactiontree", field, ok); if (!ok) break; TXT_TO_BLOB("transactiontree", field, row->transactiontree); */ row->transactiontree = strdup(EMPTY); PQ_GET_FLD(res, i, "merklehash", field, ok); if (!ok) break; TXT_TO_BLOB("merklehash", field, row->merklehash); PQ_GET_FLD(res, i, "prevhash", field, ok); if (!ok) break; TXT_TO_STR("prevhash", field, row->prevhash); PQ_GET_FLD(res, i, "coinbase1", field, ok); if (!ok) break; TXT_TO_STR("coinbase1", field, row->coinbase1); PQ_GET_FLD(res, i, "coinbase2", field, ok); if (!ok) break; TXT_TO_STR("coinbase2", field, row->coinbase2); PQ_GET_FLD(res, i, "version", field, ok); if (!ok) break; TXT_TO_STR("version", field, row->version); PQ_GET_FLD(res, i, "bits", field, ok); if (!ok) break; TXT_TO_STR("bits", field, row->bits); PQ_GET_FLD(res, i, "ntime", field, ok); if (!ok) break; TXT_TO_STR("ntime", field, row->ntime); PQ_GET_FLD(res, i, "reward", field, ok); if (!ok) break; TXT_TO_BIGINT("reward", field, row->reward); HISTORYDATEFLDS(res, i, row, ok); if (!ok) break; workinfo_root = add_to_ktree(workinfo_root, item, cmp_workinfo); 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)); } 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 double cmp_shares(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_SHARES(a)->workinfoid - DATA_SHARES(b)->workinfoid); if (c == 0) { c = (double)(DATA_SHARES(a)->userid - DATA_SHARES(b)->userid); if (c == 0) { c = strcmp(DATA_SHARES(a)->workername, DATA_SHARES(b)->workername); if (c == 0) { c = tvdiff(&(DATA_SHARES(a)->createdate), &(DATA_SHARES(b)->createdate)); if (c == 0) { c = strcmp(DATA_SHARES(a)->nonce, DATA_SHARES(b)->nonce); if (c == 0) { c = tvdiff(&(DATA_SHARES(b)->expirydate), &(DATA_SHARES(a)->expirydate)); } } } } } return c; } static void zero_sharesummary(SHARESUMMARY *row, tv_t *cd) { 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->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(char *workinfoid, char *username, char *workername, char *clientid, char *enonce1, char *nonce2, char *nonce, char *diff, char *sdiff, char *secondaryuserid, char *by, char *code, char *inet, tv_t *cd) { K_ITEM *s_item, *u_item, *wi_item, *w_item, *ss_item; SHARES *shares; bool ok = false; LOGDEBUG("%s(): add", __func__); K_WLOCK(shares_free); s_item = k_unlink_head(shares_free); K_WUNLOCK(shares_free); shares = DATA_SHARES(s_item); // TODO: allow BTC address later? u_item = find_users(username); if (!u_item) goto unitem; shares->userid = DATA_USERS(u_item)->userid; TXT_TO_BIGINT("workinfoid", workinfoid, shares->workinfoid); STRNCPY(shares->workername, workername); TXT_TO_INT("clientid", clientid, shares->clientid); STRNCPY(shares->enonce1, enonce1); STRNCPY(shares->nonce2, nonce2); STRNCPY(shares->nonce, nonce); TXT_TO_DOUBLE("diff", diff, shares->diff); TXT_TO_DOUBLE("sdiff", sdiff, shares->sdiff); STRNCPY(shares->secondaryuserid, secondaryuserid); HISTORYDATEINIT(shares, cd, by, code, inet); HISTORYDATETRANSFER(shares); wi_item = find_workinfo(shares->workinfoid); if (!wi_item) goto unitem; w_item = new_default_worker(NULL, false, shares->userid, shares->workername, by, code, inet, cd); if (!w_item) goto unitem; if (reloading) { ss_item = find_sharesummary(shares->userid, shares->workername, shares->workinfoid); if (ss_item) { if (DATA_SHARESUMMARY(ss_item)->complete[0] != SUMMARY_NEW) { K_WLOCK(shares_free); k_add_head(shares_free, s_item); K_WUNLOCK(shares_free); return true; } if (!DATA_SHARESUMMARY(ss_item)->reset) { zero_sharesummary(DATA_SHARESUMMARY(ss_item), cd); DATA_SHARESUMMARY(ss_item)->reset = true; } } } workerstatus_update(NULL, shares, NULL, NULL); sharesummary_update(NULL, shares, NULL, NULL, by, code, inet, cd); ok = true; unitem: K_WLOCK(shares_free); if (!ok) k_add_head(shares_free, s_item); else { shares_root = add_to_ktree(shares_root, s_item, cmp_shares); k_add_head(shares_store, s_item); } K_WUNLOCK(shares_free); return ok; } static bool shares_fill() { // TODO: reload shares from workinfo from log file // and verify workinfo while doing that return true; } // order by workinfoid asc,userid asc,createdate asc,nonce asc,expirydate desc static double cmp_shareerrors(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_SHAREERRORS(a)->workinfoid - DATA_SHAREERRORS(b)->workinfoid); if (c == 0) { c = (double)(DATA_SHAREERRORS(a)->userid - DATA_SHAREERRORS(b)->userid); if (c == 0) { c = tvdiff(&(DATA_SHAREERRORS(a)->createdate), &(DATA_SHAREERRORS(b)->createdate)); if (c == 0) { c = tvdiff(&(DATA_SHAREERRORS(b)->expirydate), &(DATA_SHAREERRORS(a)->expirydate)); } } } return c; } // Memory (and log file) only // TODO: handle shareerrors that appear after a workinfoid is aged or doesn't exist? static bool shareerrors_add(char *workinfoid, char *username, char *workername, char *clientid, char *errn, char *error, char *secondaryuserid, char *by, char *code, char *inet, tv_t *cd) { K_ITEM *s_item, *u_item, *wi_item, *w_item, *ss_item; SHAREERRORS *shareerrors; bool ok = false; LOGDEBUG("%s(): add", __func__); K_WLOCK(shareerrors_free); s_item = k_unlink_head(shareerrors_free); K_WUNLOCK(shareerrors_free); shareerrors = DATA_SHAREERRORS(s_item); // TODO: allow BTC address later? u_item = find_users(username); if (!u_item) goto unitem; shareerrors->userid = DATA_USERS(u_item)->userid; TXT_TO_BIGINT("workinfoid", workinfoid, shareerrors->workinfoid); STRNCPY(shareerrors->workername, workername); TXT_TO_INT("clientid", clientid, shareerrors->clientid); TXT_TO_INT("errn", errn, shareerrors->errn); STRNCPY(shareerrors->error, error); STRNCPY(shareerrors->secondaryuserid, secondaryuserid); HISTORYDATEINIT(shareerrors, cd, by, code, inet); HISTORYDATETRANSFER(shareerrors); wi_item = find_workinfo(shareerrors->workinfoid); if (!wi_item) goto unitem; w_item = new_default_worker(NULL, false, shareerrors->userid, shareerrors->workername, by, code, inet, cd); if (!w_item) goto unitem; if (reloading) { ss_item = find_sharesummary(shareerrors->userid, shareerrors->workername, shareerrors->workinfoid); if (ss_item) { if (DATA_SHARESUMMARY(ss_item)->complete[0] != SUMMARY_NEW) { K_WLOCK(shareerrors_free); k_add_head(shareerrors_free, s_item); K_WUNLOCK(shareerrors_free); return true; } if (!DATA_SHARESUMMARY(ss_item)->reset) { zero_sharesummary(DATA_SHARESUMMARY(ss_item), cd); DATA_SHARESUMMARY(ss_item)->reset = true; } } } sharesummary_update(NULL, NULL, shareerrors, NULL, by, code, inet, cd); ok = true; unitem: K_WLOCK(shareerrors_free); if (!ok) k_add_head(shareerrors_free, s_item); else { shareerrors_root = add_to_ktree(shareerrors_root, s_item, cmp_shareerrors); k_add_head(shareerrors_store, s_item); } K_WUNLOCK(shareerrors_free); return ok; } static bool shareerrors_fill() { // TODO: reload shareerrors from workinfo from log file // and verify workinfo while doing that return true; } static void dsp_sharesummary(K_ITEM *item, FILE *stream) { char createdate_buf[DATE_BUFSIZ]; SHARESUMMARY *s = NULL; if (!item) fprintf(stream, "%s() called with (null) item\n", __func__); else { s = DATA_SHARESUMMARY(item); tv_to_buf(&(s->createdate), createdate_buf, sizeof(createdate_buf)); fprintf(stream, " uid=%"PRId64" wn='%s' wid=%"PRId64" " "da=%f ds=%f ss=%f c='%s' cd=%s\n", s->userid, s->workername, s->workinfoid, s->diffacc, s->diffsta, s->sharesta, s->complete, createdate_buf); } } // default tree order by userid asc,workername asc,workinfoid asc for reporting static double cmp_sharesummary(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_SHARESUMMARY(a)->userid - DATA_SHARESUMMARY(b)->userid); if (c == 0.0) { c = strcmp(DATA_SHARESUMMARY(a)->workername, DATA_SHARESUMMARY(b)->workername); if (c == 0.0) { c = (double)(DATA_SHARESUMMARY(a)->workinfoid - DATA_SHARESUMMARY(b)->workinfoid); } } return c; } // order by workinfoid asc,userid asc,workername asc for flagging complete static double cmp_sharesummary_workinfoid(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_SHARESUMMARY(a)->workinfoid - DATA_SHARESUMMARY(b)->workinfoid); if (c == 0.0) { c = (double)(DATA_SHARESUMMARY(a)->userid - DATA_SHARESUMMARY(b)->userid); if (c == 0.0) { c = strcmp(DATA_SHARESUMMARY(a)->workername, DATA_SHARESUMMARY(b)->workername); } } return c; } static K_ITEM *find_sharesummary(int64_t userid, char *workername, int64_t workinfoid) { SHARESUMMARY sharesummary; K_TREE_CTX ctx[1]; K_ITEM look; sharesummary.userid = userid; STRNCPY(sharesummary.workername, workername); sharesummary.workinfoid = workinfoid; look.data = (void *)(&sharesummary); return find_in_ktree(sharesummary_root, &look, cmp_sharesummary, ctx); } static bool _sharesummary_update(PGconn *conn, SHARES *s_row, SHAREERRORS *e_row, K_ITEM *ss_item, char *by, char *code, char *inet, tv_t *cd, WHERE_FFL_ARGS) { ExecStatusType rescode; PGresult *res; SHARESUMMARY *row; K_ITEM *item; char *ins, *upd; bool ok = false, new; char *params[18 + MODIFYDATECOUNT]; int n, par; int64_t userid, workinfoid; char *workername; tv_t *sharecreatedate; bool must_update = false, conned = false; LOGDEBUG("%s(): update", __func__); if (ss_item) { if (s_row || e_row) { quithere(1, "ERR: only one of s_row, e_row and " "ss_item allowed" WHERE_FFL, WHERE_FFL_PASS); } new = false; item = ss_item; row = DATA_SHARESUMMARY(item); must_update = true; row->complete[0] = SUMMARY_AGED; 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; sharecreatedate = &(s_row->createdate); } else { if (!e_row) { quithere(1, "ERR: all s_row, e_row and " "ss_item are NULL" WHERE_FFL, WHERE_FFL_PASS); } userid = e_row->userid; workername = e_row->workername; workinfoid = e_row->workinfoid; sharecreatedate = &(e_row->createdate); } item = find_sharesummary(userid, workername, workinfoid); if (item) { new = false; row = DATA_SHARESUMMARY(item); } else { new = true; K_WLOCK(sharesummary_free); item = k_unlink_head(sharesummary_free); K_WUNLOCK(sharesummary_free); row = DATA_SHARESUMMARY(item); row->userid = userid; STRNCPY(row->workername, workername); row->workinfoid = workinfoid; zero_sharesummary(row, sharecreatedate); 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; } 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; } else { char *tmp1, *tmp2; LOGERR("%s(): %s createdate (%s) is < summary lastshare (%s)", __func__, s_row ? "shares" : "shareerrors", (tmp1 = ctv_to_buf(sharecreatedate, NULL, 0)), (tmp2 = ctv_to_buf(&(row->lastshare), NULL, 0))); free(tmp2); free(tmp1); } if (row->complete[0] != SUMMARY_NEW) { LOGDEBUG("%s(): updating sharesummary not '%c' %"PRId64"/%s/%"PRId64"/%s", __func__, SUMMARY_NEW, row->userid, row->workername, row->workinfoid, row->complete); } } } if (conn == NULL) { conn = dbconnect(); conned = true; } if (new || !(row->inserted)) { MODIFYDATEINIT(row, cd, by, code, inet); 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->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++] = 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,complete" MODIFYDATECONTROL ") values (" PQPARAM26 ")"; res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0); 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_AGED) 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; 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++] = str_to_buf(row->complete, NULL, 0); MODIFYUPDATEPARAMS(params, par, row); PARCHKVAL(par, 20, 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,complete=$16" ",modifydate=$17,modifyby=$18,modifycode=$19,modifyinet=$20 " "where userid=$1 and workername=$2 and workinfoid=$3"; res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Update", rescode, conn); goto unparam; } row->countlastupdate = row->sharecount + row->errorcount; if (row->complete[0] == SUMMARY_AGED) row->saveaged = true; } else { if (!must_update) { ok = true; goto late; } else { 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->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); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("MustUpdate", rescode, conn); goto unparam; } row->countlastupdate = row->sharecount + row->errorcount; if (row->complete[0] == SUMMARY_AGED) row->saveaged = true; } } } ok = true; unparam: 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 = 18; 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,complete" MODIFYDATECONTROL " from sharesummary"; res = PQexec(conn, sel); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Select", rescode, conn); PQclear(res); return false; } n = PQnfields(res); if (n != (fields + MODIFYDATECOUNT)) { LOGERR("%s(): Invalid field count - should be %d, but is %d", __func__, fields + MODIFYDATECOUNT, n); PQclear(res); return false; } n = PQntuples(res); LOGDEBUG("%s(): tree build count %d", __func__, n); ok = true; K_WLOCK(sharesummary_free); for (i = 0; i < n; i++) { item = k_unlink_head(sharesummary_free); row = DATA_SHARESUMMARY(item); row->inserted = true; PQ_GET_FLD(res, i, "userid", field, ok); if (!ok) break; TXT_TO_BIGINT("userid", field, row->userid); PQ_GET_FLD(res, i, "workername", field, ok); if (!ok) break; TXT_TO_STR("workername", field, row->workername); PQ_GET_FLD(res, i, "workinfoid", field, ok); if (!ok) break; TXT_TO_BIGINT("workinfoid", field, row->workinfoid); PQ_GET_FLD(res, i, "diffacc", field, ok); if (!ok) break; TXT_TO_DOUBLE("diffacc", field, row->diffacc); PQ_GET_FLD(res, i, "diffsta", field, ok); if (!ok) break; TXT_TO_DOUBLE("diffsta", field, row->diffsta); PQ_GET_FLD(res, i, "diffdup", field, ok); if (!ok) break; TXT_TO_DOUBLE("diffdup", field, row->diffdup); PQ_GET_FLD(res, i, "diffhi", field, ok); if (!ok) break; TXT_TO_DOUBLE("diffhi", field, row->diffhi); PQ_GET_FLD(res, i, "diffrej", field, ok); if (!ok) break; TXT_TO_DOUBLE("diffrej", field, row->diffrej); PQ_GET_FLD(res, i, "shareacc", field, ok); if (!ok) break; TXT_TO_DOUBLE("shareacc", field, row->shareacc); PQ_GET_FLD(res, i, "sharesta", field, ok); if (!ok) break; TXT_TO_DOUBLE("sharesta", field, row->sharesta); PQ_GET_FLD(res, i, "sharedup", field, ok); if (!ok) break; TXT_TO_DOUBLE("sharedup", field, row->sharedup); PQ_GET_FLD(res, i, "sharehi", field, ok); if (!ok) break; TXT_TO_DOUBLE("sharehi", field, row->sharehi); PQ_GET_FLD(res, i, "sharerej", field, ok); if (!ok) break; TXT_TO_DOUBLE("sharerej", field, row->sharerej); PQ_GET_FLD(res, i, "sharecount", field, ok); if (!ok) break; TXT_TO_BIGINT("sharecount", field, row->sharecount); PQ_GET_FLD(res, i, "errorcount", field, ok); if (!ok) break; TXT_TO_BIGINT("errorcount", field, row->errorcount); row->countlastupdate = row->sharecount + row->errorcount; PQ_GET_FLD(res, i, "firstshare", field, ok); if (!ok) break; TXT_TO_TV("firstshare", field, row->firstshare); PQ_GET_FLD(res, i, "lastshare", field, ok); if (!ok) break; TXT_TO_TV("lastshare", field, row->lastshare); PQ_GET_FLD(res, i, "complete", field, ok); if (!ok) break; TXT_TO_STR("complete", field, row->complete); MODIFYDATEFLDS(res, i, row, ok); if (!ok) break; sharesummary_root = add_to_ktree(sharesummary_root, item, cmp_sharesummary); sharesummary_workinfoid_root = add_to_ktree(sharesummary_workinfoid_root, item, cmp_sharesummary_workinfoid); k_add_head(sharesummary_store, item); workerstatus_update(NULL, NULL, NULL, row); if (tolower(row->complete[0]) == SUMMARY_NEW && (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)); } } if (!ok) k_add_head(sharesummary_free, item); K_WUNLOCK(sharesummary_free); PQclear(res); if (ok) { LOGDEBUG("%s(): built", __func__); LOGWARNING("%s(): loaded %d sharesummary records", __func__, n); } return ok; } void sharesummary_reload() { PGconn *conn = dbconnect(); K_WLOCK(sharesummary_free); sharesummary_root = free_ktree(sharesummary_root, NULL); sharesummary_workinfoid_root = free_ktree(sharesummary_workinfoid_root, NULL); k_list_transfer_to_head(sharesummary_store, sharesummary_free); K_WUNLOCK(sharesummary_free); sharesummary_fill(conn); PQfinish(conn); } // order by height asc,blockhash asc,expirydate desc static double cmp_blocks(K_ITEM *a, K_ITEM *b) { double c = DATA_BLOCKS(a)->height - DATA_BLOCKS(b)->height; if (c == 0) { c = strcmp(DATA_BLOCKS(a)->blockhash, DATA_BLOCKS(b)->blockhash); if (c == 0) { c = tvdiff(&(DATA_BLOCKS(a)->expirydate), &(DATA_BLOCKS(b)->expirydate)); } } return c; } /* unused static K_ITEM *find_blocks(int32_t height, char *blockhash) { BLOCKS blocks; K_TREE_CTX ctx[1]; K_ITEM look; blocks.height = height; STRNCPY(blocks.blockhash, blockhash); blocks.expirydate.tv_sec = default_expiry.tv_sec; blocks.expirydate.tv_usec = default_expiry.tv_usec; look.data = (void *)(&blocks); return find_in_ktree(blocks_root, &look, cmp_blocks, ctx); } */ static bool blocks_add(PGconn *conn, char *height, char *blockhash, char *workinfoid, char *username, char *workername, char *clientid, char *enonce1, char *nonce2, char *nonce, char *reward, char *confirmed, char *by, char *code, char *inet, tv_t *cd, bool igndup) { ExecStatusType rescode; PGresult *res = NULL; K_TREE_CTX ctx[1]; K_ITEM *item, *u_item; BLOCKS *row; char *upd, *ins; char *params[11 + HISTORYDATECOUNT]; bool ok = false; int par = 0; int n; LOGDEBUG("%s(): add", __func__); K_WLOCK(blocks_free); item = k_unlink_head(blocks_free); K_WUNLOCK(blocks_free); row = DATA_BLOCKS(item); TXT_TO_INT("height", height, row->height); STRNCPY(row->blockhash, blockhash); STRNCPY(row->confirmed, confirmed); HISTORYDATEINIT(row, cd, by, code, inet); switch (confirmed[0]) { case BLOCKS_NEW: u_item = find_users(username); if (!u_item) goto early; TXT_TO_BIGINT("workinfoid", workinfoid, row->workinfoid); STRNCPY(row->workername, workername); TXT_TO_INT("clientid", clientid, row->clientid); STRNCPY(row->enonce1, enonce1); STRNCPY(row->nonce2, nonce2); STRNCPY(row->nonce, nonce); TXT_TO_BIGINT("reward", reward, row->reward); HISTORYDATETRANSFER(row); if (igndup && find_in_ktree(blocks_root, item, cmp_blocks, ctx)) { K_WLOCK(blocks_free); k_add_head(blocks_free, item); K_WUNLOCK(blocks_free); return true; } par = 0; params[par++] = int_to_buf(row->height, NULL, 0); params[par++] = str_to_buf(row->blockhash, NULL, 0); params[par++] = bigint_to_buf(row->workinfoid, NULL, 0); params[par++] = bigint_to_buf(row->userid, NULL, 0); params[par++] = str_to_buf(row->workername, NULL, 0); params[par++] = int_to_buf(row->clientid, NULL, 0); params[par++] = str_to_buf(row->enonce1, NULL, 0); params[par++] = str_to_buf(row->nonce2, NULL, 0); params[par++] = str_to_buf(row->nonce, NULL, 0); params[par++] = bigint_to_buf(row->reward, NULL, 0); params[par++] = str_to_buf(row->confirmed, NULL, 0); HISTORYDATEPARAMS(params, par, row); PARCHK(par, params); ins = "insert into blocks " "(height,blockhash,workinfoid,userid,workername," "clientid,enonce1,nonce2,nonce,reward,confirmed" HISTORYDATECONTROL ") values (" PQPARAM16 ")"; res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Insert", rescode, conn); goto unparam; } break; case BLOCKS_CONFIRM: // TODO: ignore a duplicate if igndup 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); res = PQexec(conn, "Begin"); 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); rescode = PQresultStatus(res); PQclear(res); if (!PGOK(rescode)) { PGLOGERR("Update", rescode, conn); res = PQexec(conn, "Rollback"); 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); HISTORYDATEPARAMS(params, par, row); PARCHKVAL(par, 2 + HISTORYDATECOUNT, params); // 7 as per ins ins = "insert into blocks " "(height,blockhash,workinfoid,userid,workername," "clientid,enonce1,nonce2,nonce,reward,confirmed" HISTORYDATECONTROL ") values (select " "height,blockhash,workinfoid,userid,workername," "clientid,enonce1,nonce2,nonce,reward,confirmed," "$3,$4,$5,$6,$7 from blocks where" "blockhash=$1 and expirydate=$2)"; res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); PQclear(res); if (!PGOK(rescode)) { PGLOGERR("Insert", rescode, conn); res = PQexec(conn, "Rollback"); goto unparam; } res = PQexec(conn, "Commit"); break; } ok = true; unparam: PQclear(res); for (n = 0; n < par; n++) free(params[n]); early: K_WLOCK(blocks_free); if (!ok) k_add_head(blocks_free, item); else { blocks_root = add_to_ktree(blocks_root, item, cmp_blocks); k_add_head(blocks_store, item); } K_WUNLOCK(blocks_free); return ok; } static bool blocks_fill(PGconn *conn) { ExecStatusType rescode; PGresult *res; K_ITEM *item; int n, i; BLOCKS *row; char *params[1]; int par; char *field; char *sel; int fields = 11; bool ok; LOGDEBUG("%s(): select", __func__); sel = "select " "height,blockhash,workinfoid,userid,workername," "clientid,enonce1,nonce2,nonce,reward,confirmed" HISTORYDATECONTROL " from blocks 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); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Select", rescode, conn); PQclear(res); return false; } n = PQnfields(res); if (n != (fields + HISTORYDATECOUNT)) { LOGERR("%s(): Invalid field count - should be %d, but is %d", __func__, fields + HISTORYDATECOUNT, n); PQclear(res); return false; } n = PQntuples(res); LOGDEBUG("%s(): tree build count %d", __func__, n); ok = true; K_WLOCK(blocks_free); for (i = 0; i < n; i++) { item = k_unlink_head(blocks_free); row = DATA_BLOCKS(item); PQ_GET_FLD(res, i, "height", field, ok); if (!ok) break; TXT_TO_INT("height", field, row->height); PQ_GET_FLD(res, i, "blockhash", field, ok); if (!ok) break; TXT_TO_STR("blockhash", field, row->blockhash); PQ_GET_FLD(res, i, "workinfoid", field, ok); if (!ok) break; TXT_TO_BIGINT("workinfoid", field, row->workinfoid); PQ_GET_FLD(res, i, "userid", field, ok); if (!ok) break; TXT_TO_BIGINT("userid", field, row->userid); PQ_GET_FLD(res, i, "workername", field, ok); if (!ok) break; TXT_TO_BLOB("workername", field, row->workername); PQ_GET_FLD(res, i, "clientid", field, ok); if (!ok) break; TXT_TO_INT("clientid", field, row->clientid); PQ_GET_FLD(res, i, "enonce1", field, ok); if (!ok) break; TXT_TO_STR("enonce1", field, row->enonce1); PQ_GET_FLD(res, i, "nonce2", field, ok); if (!ok) break; TXT_TO_STR("nonce2", field, row->nonce2); PQ_GET_FLD(res, i, "nonce", field, ok); if (!ok) break; TXT_TO_STR("nonce", field, row->nonce); PQ_GET_FLD(res, i, "reward", field, ok); if (!ok) break; TXT_TO_BIGINT("reward", field, row->reward); PQ_GET_FLD(res, i, "confirmed", field, ok); if (!ok) break; TXT_TO_STR("confirmed", field, row->confirmed); HISTORYDATEFLDS(res, i, row, ok); if (!ok) break; blocks_root = add_to_ktree(blocks_root, item, cmp_blocks); k_add_head(blocks_store, item); if (tv_newer(&(dbstatus.newest_createdate_blocks), &(row->createdate))) copy_tv(&(dbstatus.newest_createdate_blocks), &(row->createdate)); } if (!ok) k_add_head(blocks_free, item); K_WUNLOCK(blocks_free); PQclear(res); if (ok) { LOGDEBUG("%s(): built", __func__); LOGWARNING("%s(): loaded %d blocks records", __func__, n); } return ok; } void blocks_reload() { PGconn *conn = dbconnect(); K_WLOCK(blocks_free); blocks_root = free_ktree(blocks_root, NULL); k_list_transfer_to_head(blocks_store, blocks_free); K_WUNLOCK(blocks_free); blocks_fill(conn); PQfinish(conn); } // order by userid asc,createdate asc,authid asc,expirydate desc static double cmp_auths(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_AUTHS(a)->userid - DATA_AUTHS(b)->userid); if (c == 0) { c = tvdiff(&(DATA_AUTHS(a)->createdate), &(DATA_AUTHS(b)->createdate)); if (c == 0) { c = (double)(DATA_AUTHS(a)->authid - DATA_AUTHS(b)->authid); if (c == 0) { c = tvdiff(&(DATA_AUTHS(b)->expirydate), &(DATA_AUTHS(a)->expirydate)); } } } return c; } static char *auths_add(PGconn *conn, char *poolinstance, char *username, char *workername, char *clientid, char *enonce1, char *useragent, char *by, char *code, char *inet, tv_t *cd, bool igndup) { ExecStatusType rescode; PGresult *res; K_TREE_CTX ctx[1]; K_ITEM *a_item, *u_item; int n; AUTHS *row; char *ins; char *secuserid = NULL; char *params[7 + HISTORYDATECOUNT]; int par; LOGDEBUG("%s(): add", __func__); K_WLOCK(auths_free); a_item = k_unlink_head(auths_free); K_WUNLOCK(auths_free); row = DATA_AUTHS(a_item); u_item = find_users(username); if (!u_item) goto unitem; STRNCPY(row->poolinstance, poolinstance); row->userid = DATA_USERS(u_item)->userid; // since update=false, a dup will be ok and do nothing when igndup=true new_worker(conn, false, row->userid, workername, DIFFICULTYDEFAULT_DEF_STR, IDLENOTIFICATIONENABLED_DEF, IDLENOTIFICATIONTIME_DEF_STR, by, code, inet, cd); STRNCPY(row->workername, workername); TXT_TO_INT("clientid", clientid, row->clientid); STRNCPY(row->enonce1, enonce1); STRNCPY(row->useragent, useragent); HISTORYDATEINIT(row, cd, by, code, inet); HISTORYDATETRANSFER(row); if (igndup && find_in_ktree(auths_root, a_item, cmp_auths, ctx)) { K_WLOCK(auths_free); k_add_head(auths_free, a_item); K_WUNLOCK(auths_free); return DATA_USERS(u_item)->secondaryuserid; } // Update even if DB fails workerstatus_update(row, NULL, NULL, NULL); row->authid = nextid(conn, "authid", (int64_t)1, cd, by, code, inet); if (row->authid == 0) goto unitem; par = 0; params[par++] = bigint_to_buf(row->authid, NULL, 0); params[par++] = str_to_buf(row->poolinstance, NULL, 0); params[par++] = bigint_to_buf(row->userid, NULL, 0); params[par++] = str_to_buf(row->workername, NULL, 0); params[par++] = int_to_buf(row->clientid, NULL, 0); params[par++] = str_to_buf(row->enonce1, NULL, 0); params[par++] = str_to_buf(row->useragent, NULL, 0); HISTORYDATEPARAMS(params, par, row); PARCHK(par, params); ins = "insert into auths " "(authid,poolinstance,userid,workername,clientid,enonce1,useragent" HISTORYDATECONTROL ") values (" PQPARAM12 ")"; res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Insert", rescode, conn); goto unparam; } secuserid = DATA_USERS(u_item)->secondaryuserid; unparam: PQclear(res); for (n = 0; n < par; n++) free(params[n]); unitem: K_WLOCK(auths_free); if (!secuserid) k_add_head(auths_free, a_item); else { auths_root = add_to_ktree(auths_root, a_item, cmp_auths); k_add_head(auths_store, a_item); } K_WUNLOCK(auths_free); return secuserid; } static bool auths_fill(PGconn *conn) { ExecStatusType rescode; PGresult *res; K_ITEM *item; int n, i; AUTHS *row; char *params[1]; int par; char *field; char *sel; int fields = 6; bool ok; LOGDEBUG("%s(): select", __func__); // TODO: keep last x - since a user may login and mine for 100 days sel = "select " "authid,userid,workername,clientid,enonce1,useragent" HISTORYDATECONTROL " from auths where expirydate=$1"; par = 0; params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); PARCHK(par, params); res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Select", rescode, conn); PQclear(res); return false; } n = PQnfields(res); if (n != (fields + HISTORYDATECOUNT)) { LOGERR("%s(): Invalid field count - should be %d, but is %d", __func__, fields + HISTORYDATECOUNT, n); PQclear(res); return false; } n = PQntuples(res); LOGDEBUG("%s(): tree build count %d", __func__, n); ok = true; K_WLOCK(auths_free); for (i = 0; i < n; i++) { item = k_unlink_head(auths_free); row = DATA_AUTHS(item); PQ_GET_FLD(res, i, "authid", field, ok); if (!ok) break; TXT_TO_BIGINT("authid", field, row->authid); PQ_GET_FLD(res, i, "userid", field, ok); if (!ok) break; TXT_TO_BIGINT("userid", field, row->userid); PQ_GET_FLD(res, i, "workername", field, ok); if (!ok) break; TXT_TO_STR("workername", field, row->workername); PQ_GET_FLD(res, i, "clientid", field, ok); if (!ok) break; TXT_TO_INT("clientid", field, row->clientid); PQ_GET_FLD(res, i, "enonce1", field, ok); if (!ok) break; TXT_TO_BLOB("enonce1", field, row->enonce1); PQ_GET_FLD(res, i, "useragent", field, ok); if (!ok) break; TXT_TO_STR("useragent", field, row->useragent); HISTORYDATEFLDS(res, i, row, ok); if (!ok) break; auths_root = add_to_ktree(auths_root, item, cmp_auths); k_add_head(auths_store, item); workerstatus_update(row, NULL, NULL, NULL); if (tv_newer(&(dbstatus.newest_createdate_auths), &(row->createdate))) copy_tv(&(dbstatus.newest_createdate_auths), &(row->createdate)); } 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 double cmp_poolstats(K_ITEM *a, K_ITEM *b) { double c = (double)strcmp(DATA_POOLSTATS(a)->poolinstance, DATA_POOLSTATS(b)->poolinstance); if (c == 0) { c = tvdiff(&(DATA_POOLSTATS(a)->createdate), &(DATA_POOLSTATS(b)->createdate)); } return c; } static bool poolstats_add(PGconn *conn, bool store, char *poolinstance, char *elapsed, char *users, char *workers, char *hashrate, char *hashrate5m, char *hashrate1hr, char *hashrate24hr, char *by, char *code, char *inet, tv_t *cd, bool igndup) { ExecStatusType rescode; PGresult *res; K_TREE_CTX ctx[1]; K_ITEM *p_item; int n; POOLSTATS *row; char *ins; char *params[8 + SIMPLEDATECOUNT]; int par; bool ok = false; LOGDEBUG("%s(): add", __func__); K_WLOCK(poolstats_free); p_item = k_unlink_head(poolstats_free); K_WUNLOCK(poolstats_free); row = DATA_POOLSTATS(p_item); 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(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 ")"; res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Insert", rescode, conn); goto unparam; } } ok = true; unparam: if (store) { PQclear(res); 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); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Select", rescode, conn); PQclear(res); return false; } n = PQnfields(res); if (n != (fields + SIMPLEDATECOUNT)) { LOGERR("%s(): Invalid field count - should be %d, but is %d", __func__, fields + SIMPLEDATECOUNT, n); PQclear(res); return false; } n = PQntuples(res); LOGDEBUG("%s(): tree build count %d", __func__, n); ok = true; K_WLOCK(poolstats_free); for (i = 0; i < n; i++) { item = k_unlink_head(poolstats_free); row = DATA_POOLSTATS(item); 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)); } if (!ok) k_add_head(poolstats_free, item); K_WUNLOCK(poolstats_free); PQclear(res); if (ok) { LOGDEBUG("%s(): built", __func__); LOGWARNING("%s(): loaded %d poolstats records", __func__, n); } return ok; } void poolstats_reload() { PGconn *conn = dbconnect(); K_WLOCK(poolstats_free); poolstats_root = free_ktree(poolstats_root, NULL); k_list_transfer_to_head(poolstats_store, poolstats_free); K_WUNLOCK(poolstats_free); poolstats_fill(conn); PQfinish(conn); } static void dsp_userstats(K_ITEM *item, FILE *stream) { char statsdate_buf[DATE_BUFSIZ], createdate_buf[DATE_BUFSIZ]; USERSTATS *u = NULL; if (!item) fprintf(stream, "%s() called with (null) item\n", __func__); else { u = DATA_USERSTATS(item); tv_to_buf(&(u->statsdate), statsdate_buf, sizeof(statsdate_buf)); tv_to_buf(&(u->createdate), createdate_buf, sizeof(createdate_buf)); fprintf(stream, " pi='%s' uid=%"PRId64" w='%s' e=%"PRId64" Hs=%f " "Hs5m=%f Hs1hr=%f Hs24hr=%f sl=%s sc=%d sd=%s cd=%s\n", u->poolinstance, u->userid, u->workername, u->elapsed, u->hashrate, u->hashrate5m, u->hashrate1hr, u->hashrate24hr, u->summarylevel, u->summarycount, statsdate_buf, createdate_buf); } } /* order by userid asc,statsdate asc,poolinstance asc,workername asc as per required for userstats homepage summarisation */ static double cmp_userstats(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_USERSTATS(a)->userid - DATA_USERSTATS(b)->userid); if (c == 0) { c = tvdiff(&(DATA_USERSTATS(a)->statsdate), &(DATA_USERSTATS(b)->statsdate)); if (c == 0) { c = (double)strcmp(DATA_USERSTATS(a)->poolinstance, DATA_USERSTATS(b)->poolinstance); if (c == 0) { c = (double)strcmp(DATA_USERSTATS(a)->workername, DATA_USERSTATS(b)->workername); } } } return c; } /* order by userid asc,workername asc temporary tree for summing userstats when sending user homepage info */ static double cmp_userstats_workername(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_USERSTATS(a)->userid - DATA_USERSTATS(b)->userid); if (c == 0) { c = (double)strcmp(DATA_USERSTATS(a)->workername, DATA_USERSTATS(b)->workername); } return c; } /* order by statsdate,userid asc,statsdate asc,workername asc,poolinstance asc as per required for DB summarisation */ static double cmp_userstats_statsdate(K_ITEM *a, K_ITEM *b) { double c = tvdiff(&(DATA_USERSTATS(a)->statsdate), &(DATA_USERSTATS(b)->statsdate)); if (c == 0) { c = (double)(DATA_USERSTATS(a)->userid - DATA_USERSTATS(b)->userid); if (c == 0) { c = (double)strcmp(DATA_USERSTATS(a)->workername, DATA_USERSTATS(b)->workername); if (c == 0) { c = (double)strcmp(DATA_USERSTATS(a)->poolinstance, DATA_USERSTATS(b)->poolinstance); } } } return c; } /* order by userid asc,workername asc,statsdate asc,poolinstance asc built during data load to update workerstatus at the end of the load */ static double cmp_userstats_workerstatus(K_ITEM *a, K_ITEM *b) { double c = (double)(DATA_USERSTATS(a)->userid - DATA_USERSTATS(b)->userid); if (c == 0) { c = (double)strcmp(DATA_USERSTATS(a)->workername, DATA_USERSTATS(b)->workername); if (c == 0) { c = tvdiff(&(DATA_USERSTATS(a)->statsdate), &(DATA_USERSTATS(b)->statsdate)); if (c == 0) { c = (double)strcmp(DATA_USERSTATS(a)->poolinstance, DATA_USERSTATS(b)->poolinstance); } } } return c; } static bool userstats_add_db(PGconn *conn, USERSTATS *row) { ExecStatusType rescode; 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 ")"; res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0); 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]); 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_ITEM *us_item, *u_item, *us_match, *us_next, *db_match; K_TREE_CTX ctx[1]; USERSTATS *row; tv_t eosdate; LOGDEBUG("%s(): add", __func__); K_WLOCK(userstats_free); us_item = k_unlink_head(userstats_free); K_WUNLOCK(userstats_free); row = DATA_USERSTATS(us_item); STRNCPY(row->poolinstance, poolinstance); TXT_TO_BIGINT("elapsed", elapsed, row->elapsed); u_item = find_users(username); if (!u_item) return false; row->userid = DATA_USERS(u_item)->userid; TXT_TO_STR("workername", workername, row->workername); TXT_TO_DOUBLE("hashrate", hashrate, row->hashrate); TXT_TO_DOUBLE("hashrate5m", hashrate5m, row->hashrate5m); TXT_TO_DOUBLE("hashrate1hr", hashrate1hr, row->hashrate1hr); TXT_TO_DOUBLE("hashrate24hr", hashrate24hr, row->hashrate24hr); row->idle = idle; row->summarylevel[0] = SUMMARY_NONE; row->summarylevel[1] = '\0'; row->summarycount = 1; SIMPLEDATEINIT(row, cd, by, code, inet); SIMPLEDATETRANSFER(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; } if (reloading) { /* If the db load said the statsdate for this userid+workername * is already summarised then we discard it */ db_match = find_in_ktree(userstats_db_root, us_item, cmp_userstats_workername, ctx); if (db_match && !tv_newer(&(DATA_USERSTATS(db_match)->statsdate), cd)) { K_WLOCK(userstats_free); k_add_head(userstats_free, us_item); K_WUNLOCK(userstats_free); /* If this was an eos record and eos_store has data, * it means we need to process the eos_store */ if (eos && userstats_eos_store->count > 0) goto advancetogo; return true; } } workerstatus_update(NULL, NULL, row, NULL); /* group at full key: userid,createdate,poolinstance,workername i.e. ignore instance and group together down at workername */ us_match = userstats_eos_store->head; while (us_match && cmp_userstats(us_item, us_match) != 0.0) us_match = us_match->next; if (us_match) { DATA_USERSTATS(us_match)->hashrate += row->hashrate; DATA_USERSTATS(us_match)->hashrate5m += row->hashrate5m; DATA_USERSTATS(us_match)->hashrate1hr += row->hashrate1hr; DATA_USERSTATS(us_match)->hashrate24hr += row->hashrate24hr; // Minimum elapsed of the data set if (DATA_USERSTATS(us_match)->elapsed > row->elapsed) DATA_USERSTATS(us_match)->elapsed = row->elapsed; // Unused K_WLOCK(userstats_free); k_add_head(userstats_free, us_item); K_WUNLOCK(userstats_free); } else { // New worker K_WLOCK(userstats_free); k_add_head(userstats_eos_store, us_item); K_WUNLOCK(userstats_free); } if (eos) { advancetogo: K_WLOCK(userstats_free); us_next = userstats_eos_store->head; while (us_next) { if (tvdiff(&DATA_USERSTATS(us_next)->createdate, &eosdate) != 0.0) { char date_buf[DATE_BUFSIZ]; LOGERR("userstats != eos '%s' discarded: %s/%"PRId64"/%s", tv_to_buf(&eosdate, date_buf, DATE_BUFSIZ), DATA_USERSTATS(us_next)->poolinstance, DATA_USERSTATS(us_next)->userid, DATA_USERSTATS(us_next)->workername); us_next = us_next->next; } else { us_match = us_next; us_next = us_match->next; k_unlink_item(userstats_eos_store, us_match); userstats_root = add_to_ktree(userstats_root, us_match, cmp_userstats); userstats_statsdate_root = add_to_ktree(userstats_statsdate_root, us_match, cmp_userstats_statsdate); if (!startup_complete) { userstats_workerstatus_root = add_to_ktree(userstats_workerstatus_root, us_match, cmp_userstats_workerstatus); } k_add_head(userstats_store, us_match); } } // Discard them if (userstats_eos_store->count > 0) k_list_transfer_to_head(userstats_eos_store, userstats_free); K_WUNLOCK(userstats_free); } return true; } // Requires K_WLOCK(userstats_free) static void userstats_update_ccl(USERSTATS *row) { USERSTATS userstats, *tmp; K_TREE_CTX ctx[1]; K_ITEM look, *item; char buf[DATE_BUFSIZ+1]; userstats.userid = row->userid; STRNCPY(userstats.workername, row->workername); copy_tv(&(userstats.statsdate), &(row->statsdate)); // Start of this timeband switch (row->summarylevel[0]) { case SUMMARY_DB: userstats.statsdate.tv_sec -= userstats.statsdate.tv_sec % USERSTATS_DB_S; userstats.statsdate.tv_usec = 0; break; case SUMMARY_FULL: userstats.statsdate.tv_sec -= userstats.statsdate.tv_sec % USERSTATS_DB_DS; userstats.statsdate.tv_usec = 0; break; default: tv_to_buf(&(row->statsdate), buf, sizeof(buf)); // Bad userstats are not fatal LOGERR("Unknown userstats summarylevel '%c' " "userid "PRId64" workername %s statsdate %s", row->summarylevel[0], row->userid, row->workername, buf); return; } look.data = (void *)(&userstats); item = find_in_ktree(userstats_db_root, &look, cmp_userstats_workername, ctx); if (item) { tmp = DATA_USERSTATS(item); if (tv_newer(&(tmp->statsdate), &(userstats.statsdate))) copy_tv(&(tmp->statsdate), &(userstats.statsdate)); } else { item = k_unlink_head(userstats_free); tmp = DATA_USERSTATS(item); bzero(tmp, sizeof(*tmp)); tmp->userid = userstats.userid; STRNCPY(tmp->workername, userstats.workername); copy_tv(&(tmp->statsdate), &(userstats.statsdate)); userstats_db_root = add_to_ktree(userstats_db_root, item, cmp_userstats_workername); k_add_head(userstats_db, item); } } // TODO: data selection - only require ? static bool userstats_fill(PGconn *conn) { ExecStatusType rescode; PGresult *res; K_ITEM *item; int n, i; USERSTATS *row; 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); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Select", rescode, conn); PQclear(res); return false; } n = PQnfields(res); if (n != (fields + SIMPLEDATECOUNT)) { LOGERR("%s(): Invalid field count - should be %d, but is %d", __func__, fields + SIMPLEDATECOUNT, n); PQclear(res); return false; } n = PQntuples(res); LOGDEBUG("%s(): tree build count %d", __func__, n); ok = true; K_WLOCK(userstats_free); for (i = 0; i < n; i++) { item = k_unlink_head(userstats_free); row = DATA_USERSTATS(item); // Not a DB field row->poolinstance[0] = '\0'; PQ_GET_FLD(res, i, "userid", field, ok); if (!ok) break; TXT_TO_BIGINT("userid", field, row->userid); PQ_GET_FLD(res, i, "workername", field, ok); if (!ok) break; TXT_TO_STR("workername", field, row->workername); PQ_GET_FLD(res, i, "elapsed", field, ok); if (!ok) break; TXT_TO_BIGINT("elapsed", field, row->elapsed); PQ_GET_FLD(res, i, "hashrate", field, ok); if (!ok) break; TXT_TO_DOUBLE("hashrate", field, row->hashrate); PQ_GET_FLD(res, i, "hashrate5m", field, ok); if (!ok) break; TXT_TO_DOUBLE("hashrate5m", field, row->hashrate5m); PQ_GET_FLD(res, i, "hashrate1hr", field, ok); if (!ok) break; TXT_TO_DOUBLE("hashrate1hr", field, row->hashrate1hr); PQ_GET_FLD(res, i, "hashrate24hr", field, ok); if (!ok) break; TXT_TO_DOUBLE("hashrate24hr", field, row->hashrate24hr); PQ_GET_FLD(res, i, "summarylevel", field, ok); if (!ok) break; TXT_TO_STR("summarylevel", field, row->summarylevel); PQ_GET_FLD(res, i, "summarycount", field, ok); if (!ok) break; TXT_TO_INT("summarycount", field, row->summarycount); PQ_GET_FLD(res, i, "statsdate", field, ok); if (!ok) break; TXT_TO_TV("statsdate", field, row->statsdate); // From DB - 1hr means it must have been idle > 10m if (row->hashrate5m == 0.0 && row->hashrate1hr == 0.0) row->idle = true; else row->idle = false; SIMPLEDATEFLDS(res, i, row, ok); if (!ok) break; userstats_root = add_to_ktree(userstats_root, item, cmp_userstats); userstats_statsdate_root = add_to_ktree(userstats_statsdate_root, item, cmp_userstats_statsdate); userstats_workerstatus_root = add_to_ktree(userstats_workerstatus_root, item, cmp_userstats_workerstatus); k_add_head(userstats_store, item); workerstatus_update(NULL, NULL, row, NULL); userstats_update_ccl(row); } 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); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGEMERG("Select", rescode, conn); PQclear(res); return false; } n = PQnfields(res); if (n != fields) { LOGEMERG("%s(): Invalid field count - should be %d, but is %d", __func__, fields, n); PQclear(res); return false; } n = PQntuples(res); if (n != 1) { LOGEMERG("%s(): Invalid record count - should be %d, but is %d", __func__, 1, n); PQclear(res); return false; } ok = true; PQ_GET_FLD(res, 0, "vlock", field, ok); if (!ok) { LOGEMERG("%s(): Missing field vlock", __func__); PQclear(res); return false; } if (strcmp(field, DB_VLOCK)) { LOGEMERG("%s(): incorrect vlock '%s' - should be '%s'", __func__, field, DB_VLOCK); PQclear(res); return false; } ok = true; PQ_GET_FLD(res, 0, "version", field, ok); if (!ok) { LOGEMERG("%s(): Missing field version", __func__); PQclear(res); return false; } if (strcmp(field, DB_VERSION)) { LOGEMERG("%s(): incorrect version '%s' - should be '%s'", __func__, field, DB_VERSION); PQclear(res); return false; } PQclear(res); LOGWARNING("%s(): DB version (%s) correct", __func__, DB_VERSION); return true; } static bool getdata() { 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 (!(ok = payments_fill(conn))) goto matane; if (!(ok = workinfo_fill(conn))) goto matane; if (!(ok = shares_fill())) goto matane; if (!(ok = shareerrors_fill())) goto matane; if (!(ok = sharesummary_fill(conn))) goto matane; if (!(ok = blocks_fill(conn))) goto matane; if (!(ok = auths_fill(conn))) goto matane; if (!(ok = poolstats_fill(conn))) goto matane; ok = userstats_fill(conn); matane: PQfinish(conn); return ok; } static bool reload_from(tv_t *start); static bool reload() { char buf[DATE_BUFSIZ+1]; K_ITEM *ccl; tv_t start; bool ok; 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_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); ccl = userstats_db->head; // oldest in ccl while (ccl) { if (dbstatus.userstats.tv_sec == 0 || !tv_newer(&(dbstatus.userstats), &(DATA_USERSTATS(ccl)->statsdate))) copy_tv(&(dbstatus.userstats), &(DATA_USERSTATS(ccl)->statsdate)); ccl = ccl->next; } tv_to_buf(&(dbstatus.userstats), buf, sizeof(buf)); LOGWARNING("%s(): %s oldest new DB userstats", __func__, buf); tv_to_buf(&(dbstatus.newest_createdate_blocks), buf, sizeof(buf)); LOGWARNING("%s(): %s newest DB blocks", __func__, buf); copy_tv(&start, &(dbstatus.oldest_sharesummary_firstshare_n)); if (!tv_newer(&start, &(dbstatus.newest_createdate_workinfo))) copy_tv(&start, &(dbstatus.newest_createdate_workinfo)); if (!tv_newer(&start, &(dbstatus.newest_createdate_auths))) copy_tv(&start, &(dbstatus.newest_createdate_auths)); if (!tv_newer(&start, &(dbstatus.newest_createdate_poolstats))) copy_tv(&start, &(dbstatus.newest_createdate_poolstats)); if (!tv_newer(&start, &(dbstatus.userstats))) copy_tv(&start, &(dbstatus.userstats)); if (start.tv_sec < DATE_BEGIN) { start.tv_sec = DATE_BEGIN; start.tv_usec = 0L; } ok = reload_from(&start); free_ktree(userstats_db_root, NULL); k_list_transfer_to_head(userstats_db, userstats_free); return ok; } /* 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, "r"); 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, "w"); 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 bool setup_data() { K_TREE_CTX ctx[1]; K_ITEM look, *found; WORKINFO wi; transfer_free = k_new_list("Transfer", sizeof(TRANSFER), ALLOC_TRANSFER, LIMIT_TRANSFER, true); transfer_store = k_new_store(transfer_free); transfer_root = new_ktree(); transfer_free->dsp_func = dsp_transfer; users_free = k_new_list("Users", sizeof(USERS), ALLOC_USERS, LIMIT_USERS, true); users_store = k_new_store(users_free); users_root = new_ktree(); userid_root = new_ktree(); workers_free = k_new_list("Workers", sizeof(WORKERS), ALLOC_WORKERS, LIMIT_WORKERS, true); workers_store = k_new_store(workers_free); workers_root = new_ktree(); payments_free = k_new_list("Payments", sizeof(PAYMENTS), ALLOC_PAYMENTS, LIMIT_PAYMENTS, true); payments_store = k_new_store(payments_free); payments_root = new_ktree(); idcontrol_free = k_new_list("IDControl", sizeof(IDCONTROL), ALLOC_IDCONTROL, LIMIT_IDCONTROL, true); idcontrol_store = k_new_store(idcontrol_free); workinfo_free = k_new_list("WorkInfo", sizeof(WORKINFO), ALLOC_WORKINFO, LIMIT_WORKINFO, true); workinfo_store = k_new_store(workinfo_free); workinfo_root = new_ktree(); workinfo_height_root = new_ktree(); shares_free = k_new_list("Shares", sizeof(SHARES), ALLOC_SHARES, LIMIT_SHARES, true); shares_store = k_new_store(shares_free); shares_root = new_ktree(); shareerrors_free = k_new_list("ShareErrors", sizeof(SHAREERRORS), ALLOC_SHAREERRORS, LIMIT_SHAREERRORS, true); shareerrors_store = k_new_store(shareerrors_free); shareerrors_root = new_ktree(); sharesummary_free = k_new_list("ShareSummary", sizeof(SHARESUMMARY), ALLOC_SHARESUMMARY, LIMIT_SHARESUMMARY, true); sharesummary_store = k_new_store(sharesummary_free); sharesummary_root = new_ktree(); sharesummary_workinfoid_root = new_ktree(); sharesummary_free->dsp_func = dsp_sharesummary; blocks_free = k_new_list("Blocks", sizeof(BLOCKS), ALLOC_BLOCKS, LIMIT_BLOCKS, true); blocks_store = k_new_store(blocks_free); blocks_root = new_ktree(); auths_free = k_new_list("Auths", sizeof(AUTHS), ALLOC_AUTHS, LIMIT_AUTHS, true); auths_store = k_new_store(auths_free); auths_root = new_ktree(); poolstats_free = k_new_list("PoolStats", sizeof(POOLSTATS), ALLOC_POOLSTATS, LIMIT_POOLSTATS, true); poolstats_store = k_new_store(poolstats_free); poolstats_root = new_ktree(); userstats_free = k_new_list("UserStats", sizeof(USERSTATS), ALLOC_USERSTATS, LIMIT_USERSTATS, true); userstats_store = k_new_store(userstats_free); userstats_eos_store = k_new_store(userstats_free); userstats_summ = k_new_store(userstats_free); userstats_db = 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; userstats_db_root = new_ktree(); workerstatus_free = k_new_list("WorkerStatus", sizeof(WORKERSTATUS), ALLOC_WORKERSTATUS, LIMIT_WORKERSTATUS, true); workerstatus_store = k_new_store(workerstatus_free); workerstatus_root = new_ktree(); if (!getdata()) return false; if (!reload()) return false; workerstatus_ready(); workinfo_current = last_in_ktree(workinfo_height_root, ctx); if (workinfo_current) { STRNCPY(wi.coinbase1, DATA_WORKINFO(workinfo_current)->coinbase1); wi.createdate.tv_sec = 0L; wi.createdate.tv_usec = 0L; look.data = (void *)(&wi); // Find the first workinfo for this height found = find_after_in_ktree(workinfo_height_root, &look, cmp_workinfo_height, ctx); if (found) copy_tv(&last_bc, &(DATA_WORKINFO(found)->createdate)); // No longer needed workinfo_height_root = free_ktree(workinfo_height_root, NULL); } return true; } static char *cmd_adduser(char *cmd, char *id, tv_t *now, char *by, char *code, char *inet, __maybe_unused tv_t *notcd) { char reply[1024] = ""; size_t siz = sizeof(reply); K_ITEM *i_username, *i_emailaddress, *i_passwordhash; PGconn *conn; bool ok; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name("username", 3, (char *)userpatt, reply, siz); if (!i_username) return strdup(reply); i_emailaddress = require_name("emailaddress", 7, (char *)mailpatt, reply, siz); if (!i_emailaddress) return strdup(reply); i_passwordhash = require_name("passwordhash", 64, (char *)hashpatt, reply, siz); if (!i_passwordhash) return strdup(reply); conn = dbconnect(); ok = users_add(conn, DATA_TRANSFER(i_username)->data, DATA_TRANSFER(i_emailaddress)->data, DATA_TRANSFER(i_passwordhash)->data, now, by, code, inet); PQfinish(conn); if (!ok) { LOGERR("%s.failed.DBE", id); return strdup("failed.DBE"); } LOGDEBUG("%s.ok.added %s", id, DATA_TRANSFER(i_username)->data); snprintf(reply, siz, "ok.added %s", DATA_TRANSFER(i_username)->data); return strdup(reply); } static char *cmd_chkpass(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_ITEM *i_username, *i_passwordhash, *u_item; char reply[1024] = ""; size_t siz = sizeof(reply); bool ok; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name("username", 3, (char *)userpatt, reply, siz); if (!i_username) return strdup(reply); i_passwordhash = require_name("passwordhash", 64, (char *)hashpatt, reply, siz); if (!i_passwordhash) return strdup(reply); u_item = find_users(DATA_TRANSFER(i_username)->data); if (!u_item) ok = false; else { if (strcasecmp(DATA_TRANSFER(i_passwordhash)->data, DATA_USERS(u_item)->passwordhash) == 0) ok = true; else ok = false; } if (!ok) { LOGERR("%s.failed.%s", id, DATA_TRANSFER(i_username)->data); return strdup("failed."); } LOGDEBUG("%s.ok.%s", id, DATA_TRANSFER(i_username)->data); return strdup("ok."); } static char *cmd_poolstats_do(char *cmd, char *id, char *by, char *code, char *inet, tv_t *cd, bool igndup) { char reply[1024] = ""; size_t siz = sizeof(reply); K_TREE_CTX ctx[1]; PGconn *conn; bool store; // log to logfile K_ITEM *i_poolinstance, *i_elapsed, *i_users, *i_workers; K_ITEM *i_hashrate, *i_hashrate5m, *i_hashrate1hr, *i_hashrate24hr; K_ITEM look, *ps; POOLSTATS row; bool ok = false; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_poolinstance = require_name("poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) return strdup(reply); i_elapsed = optional_name("elapsed", 1, NULL); if (!i_elapsed) i_elapsed = &poolstats_elapsed; i_users = require_name("users", 1, NULL, reply, siz); if (!i_users) return strdup(reply); i_workers = require_name("workers", 1, NULL, reply, siz); if (!i_workers) return strdup(reply); i_hashrate = require_name("hashrate", 1, NULL, reply, siz); if (!i_hashrate) return strdup(reply); i_hashrate5m = require_name("hashrate5m", 1, NULL, reply, siz); if (!i_hashrate5m) return strdup(reply); i_hashrate1hr = require_name("hashrate1hr", 1, NULL, reply, siz); if (!i_hashrate1hr) return strdup(reply); i_hashrate24hr = require_name("hashrate24hr", 1, NULL, reply, siz); if (!i_hashrate24hr) return strdup(reply); STRNCPY(row.poolinstance, DATA_TRANSFER(i_poolinstance)->data); row.createdate.tv_sec = date_eot.tv_sec; row.createdate.tv_usec = date_eot.tv_usec; look.data = (void *)(&row); ps = find_before_in_ktree(poolstats_root, &look, cmp_poolstats, ctx); if (!ps) store = true; else { if (tvdiff(cd, &(DATA_POOLSTATS(ps)->createdate)) > STATS_PER) store = true; else store = false; } conn = dbconnect(); ok = poolstats_add(conn, store, DATA_TRANSFER(i_poolinstance)->data, DATA_TRANSFER(i_elapsed)->data, DATA_TRANSFER(i_users)->data, DATA_TRANSFER(i_workers)->data, DATA_TRANSFER(i_hashrate)->data, DATA_TRANSFER(i_hashrate5m)->data, DATA_TRANSFER(i_hashrate1hr)->data, DATA_TRANSFER(i_hashrate24hr)->data, by, code, inet, cd, igndup); PQfinish(conn); if (!ok) { LOGERR("%s.failed.DBE", id); return strdup("failed.DBE"); } LOGDEBUG("%s.ok.", id); snprintf(reply, siz, "ok."); return strdup(reply); } static char *cmd_poolstats(char *cmd, char *id, __maybe_unused tv_t *notnow, char *by, char *code, char *inet, tv_t *cd) { bool igndup = false; 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(cmd, id, by, code, inet, cd, igndup); } static char *cmd_userstats(char *cmd, char *id, __maybe_unused tv_t *notnow, char *by, char *code, char *inet, tv_t *cd) { 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("poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) return strdup(reply); i_elapsed = optional_name("elapsed", 1, NULL); if (!i_elapsed) i_elapsed = &userstats_elapsed; i_username = require_name("username", 1, NULL, reply, siz); if (!i_username) return strdup(reply); i_workername = optional_name("workername", 1, NULL); if (!i_workername) i_workername = &userstats_workername; i_hashrate = require_name("hashrate", 1, NULL, reply, siz); if (!i_hashrate) return strdup(reply); i_hashrate5m = require_name("hashrate5m", 1, NULL, reply, siz); if (!i_hashrate5m) return strdup(reply); i_hashrate1hr = require_name("hashrate1hr", 1, NULL, reply, siz); if (!i_hashrate1hr) return strdup(reply); i_hashrate24hr = require_name("hashrate24hr", 1, NULL, reply, siz); if (!i_hashrate24hr) return strdup(reply); i_idle = optional_name("idle", 1, NULL); if (!i_idle) i_idle = &userstats_idle; idle = (strcasecmp(DATA_TRANSFER(i_idle)->data, TRUE_STR) == 0); i_eos = optional_name("eos", 1, NULL); if (!i_eos) i_eos = &userstats_eos; eos = (strcasecmp(DATA_TRANSFER(i_eos)->data, TRUE_STR) == 0); ok = userstats_add(DATA_TRANSFER(i_poolinstance)->data, DATA_TRANSFER(i_elapsed)->data, DATA_TRANSFER(i_username)->data, DATA_TRANSFER(i_workername)->data, DATA_TRANSFER(i_hashrate)->data, DATA_TRANSFER(i_hashrate5m)->data, DATA_TRANSFER(i_hashrate1hr)->data, DATA_TRANSFER(i_hashrate24hr)->data, idle, eos, by, code, inet, cd); if (!ok) { LOGERR("%s.failed.DATA", id); return strdup("failed.DATA"); } LOGDEBUG("%s.ok.", id); snprintf(reply, siz, "ok."); return strdup(reply); } static char *cmd_newid(char *cmd, char *id, tv_t *now, char *by, char *code, char *inet, __maybe_unused tv_t *cd) { 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; PGresult *res; PGconn *conn; char *ins; int n; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_idname = require_name("idname", 3, (char *)idpatt, reply, siz); if (!i_idname) return strdup(reply); i_idvalue = require_name("idvalue", 1, (char *)intpatt, reply, siz); if (!i_idvalue) return strdup(reply); K_WLOCK(idcontrol_free); look = k_unlink_head(idcontrol_free); K_WUNLOCK(idcontrol_free); row = DATA_IDCONTROL(look); STRNCPY(row->idname, DATA_TRANSFER(i_idname)->data); TXT_TO_BIGINT("idvalue", DATA_TRANSFER(i_idvalue)->data, row->lastid); MODIFYDATEINIT(row, now, by, code, inet); par = 0; params[par++] = str_to_buf(row->idname, NULL, 0); params[par++] = bigint_to_buf(row->lastid, NULL, 0); MODIFYDATEPARAMS(params, par, row); PARCHK(par, params); ins = "insert into idcontrol " "(idname,lastid" MODIFYDATECONTROL ") values (" PQPARAM10 ")"; conn = dbconnect(); res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Insert", rescode, conn); goto foil; } ok = true; foil: PQclear(res); 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.failed.DBE", id); return strdup("failed.DBE"); } LOGDEBUG("%s.ok.added %s %"PRId64, id, DATA_TRANSFER(i_idname)->data, row->lastid); snprintf(reply, siz, "ok.added %s %"PRId64, DATA_TRANSFER(i_idname)->data, row->lastid); return strdup(reply); } static char *cmd_payments(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_ITEM *i_username, look, *u_item, *p_item; K_TREE_CTX ctx[1]; PAYMENTS payments; char reply[1024] = ""; char tmp[1024]; size_t siz = sizeof(reply); char *buf; size_t len, off; int rows; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name("username", 3, (char *)userpatt, reply, siz); if (!i_username) return strdup(reply); u_item = find_users(DATA_TRANSFER(i_username)->data); if (!u_item) return strdup("bad"); payments.userid = DATA_USERS(u_item)->userid; payments.paydate.tv_sec = 0; payments.paydate.tv_usec = 0; look.data = (void *)(&payments); p_item = find_after_in_ktree(payments_root, &look, cmp_payments, ctx); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); rows = 0; while (p_item && DATA_PAYMENTS(p_item)->userid == DATA_USERS(u_item)->userid) { tv_to_buf(&(DATA_PAYMENTS(p_item)->paydate), reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "paydate%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); str_to_buf(DATA_PAYMENTS(p_item)->payaddress, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "payaddress%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(DATA_PAYMENTS(p_item)->amount, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "amount%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; p_item = next_in_ktree(ctx); } snprintf(tmp, sizeof(tmp), "rows=%d", rows); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.ok.%s", id, DATA_TRANSFER(i_username)->data); return buf; } static char *cmd_workers(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_ITEM *i_username, *i_stats, wlook, *u_item, *w_item, uslook, *us_item, *ws_item; K_TREE_CTX w_ctx[1], us_ctx[1]; WORKERS workers; USERSTATS userstats; char reply[1024] = ""; char tmp[1024]; size_t siz = sizeof(reply); char *buf; size_t len, off; bool stats; int rows; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name("username", 3, (char *)userpatt, reply, siz); if (!i_username) return strdup(reply); u_item = find_users(DATA_TRANSFER(i_username)->data); if (!u_item) return strdup("bad"); i_stats = optional_name("stats", 1, NULL); if (!i_stats) stats = false; else stats = (strcasecmp(DATA_TRANSFER(i_stats)->data, TRUE_STR) == 0); workers.userid = DATA_USERS(u_item)->userid; workers.workername[0] = '\0'; workers.expirydate.tv_sec = 0; workers.expirydate.tv_usec = 0; wlook.data = (void *)(&workers); w_item = find_after_in_ktree(workers_root, &wlook, cmp_workers, w_ctx); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); rows = 0; while (w_item && DATA_WORKERS(w_item)->userid == DATA_USERS(u_item)->userid) { if (tvdiff(&(DATA_WORKERS(w_item)->expirydate), (tv_t *)&default_expiry) == 0.0) { str_to_buf(DATA_WORKERS(w_item)->workername, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "workername%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); int_to_buf(DATA_WORKERS(w_item)->difficultydefault, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "difficultydefault%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); str_to_buf(DATA_WORKERS(w_item)->idlenotificationenabled, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "idlenotificationenabled%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); int_to_buf(DATA_WORKERS(w_item)->idlenotificationtime, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "idlenotificationtime%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); if (stats) { K_TREE *userstats_workername_root = new_ktree(); K_TREE_CTX usw_ctx[1]; double w_hashrate5m, w_hashrate1hr; int64_t w_elapsed; tv_t w_lastshare; w_hashrate5m = w_hashrate1hr = 0.0; w_elapsed = -1; w_lastshare.tv_sec = 0; ws_item = find_workerstatus(DATA_USERS(u_item)->userid, DATA_WORKERS(w_item)->workername); if (ws_item) w_lastshare.tv_sec = DATA_WORKERSTATUS(ws_item)->last_share.tv_sec; // find last stored userid record userstats.userid = DATA_USERS(u_item)->userid; userstats.statsdate.tv_sec = date_eot.tv_sec; userstats.statsdate.tv_usec = date_eot.tv_usec; // find/cmp doesn't get to here userstats.poolinstance[0] = '\0'; userstats.workername[0] = '\0'; uslook.data = (void *)(&userstats); K_RLOCK(userstats_free); us_item = find_before_in_ktree(userstats_root, &uslook, cmp_userstats, us_ctx); while (us_item && DATA_USERSTATS(us_item)->userid == userstats.userid) { if (strcmp(DATA_USERSTATS(us_item)->workername, DATA_WORKERS(w_item)->workername) == 0) { if (tvdiff(now, &(DATA_USERSTATS(us_item)->statsdate)) < USERSTATS_PER_S) { // TODO: add together the latest per pool instance (this is the latest per worker) if (!find_in_ktree(userstats_workername_root, us_item, cmp_userstats_workername, usw_ctx)) { w_hashrate5m += DATA_USERSTATS(us_item)->hashrate5m; w_hashrate1hr += DATA_USERSTATS(us_item)->hashrate1hr; if (w_elapsed == -1 || w_elapsed > DATA_USERSTATS(us_item)->elapsed) w_elapsed = DATA_USERSTATS(us_item)->elapsed; userstats_workername_root = add_to_ktree(userstats_workername_root, us_item, cmp_userstats_workername); } } else break; } us_item = prev_in_ktree(us_ctx); } double_to_buf(w_hashrate5m, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_hashrate5m%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_hashrate1hr, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_hashrate1hr%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(w_elapsed, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_elapsed%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); int_to_buf((int)(w_lastshare.tv_sec), reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_lastshare%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); userstats_workername_root = free_ktree(userstats_workername_root, NULL); K_RUNLOCK(userstats_free); } rows++; } w_item = next_in_ktree(w_ctx); } snprintf(tmp, sizeof(tmp), "rows=%d", rows); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.ok.%s", id, DATA_TRANSFER(i_username)->data); return buf; } static char *cmd_allusers(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 *userstats_workername_root = new_ktree(); K_ITEM *us_item, *usw_item, *tmp_item, *u_item; K_TREE_CTX us_ctx[1], usw_ctx[1]; char reply[1024] = ""; char tmp[1024]; char *buf; size_t len, off; int rows; int64_t userid = -1; double u_hashrate1hr = 0.0; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); // Find last records for each user/worker in ALLUSERS_LIMIT_S // TODO: include pool_instance K_WLOCK(userstats_free); us_item = last_in_ktree(userstats_statsdate_root, us_ctx); while (us_item && tvdiff(now, &(DATA_USERSTATS(us_item)->statsdate)) < ALLUSERS_LIMIT_S) { usw_item = find_in_ktree(userstats_workername_root, us_item, cmp_userstats_workername, usw_ctx); if (!usw_item) { usw_item = k_unlink_head(userstats_free); DATA_USERSTATS(usw_item)->userid = DATA_USERSTATS(us_item)->userid; strcpy(DATA_USERSTATS(usw_item)->workername, DATA_USERSTATS(us_item)->workername); DATA_USERSTATS(usw_item)->hashrate1hr = DATA_USERSTATS(us_item)->hashrate1hr; userstats_workername_root = add_to_ktree(userstats_workername_root, usw_item, cmp_userstats_workername); } us_item = prev_in_ktree(us_ctx); } APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); rows = 0; // Add up per user usw_item = first_in_ktree(userstats_workername_root, usw_ctx); while (usw_item) { if (DATA_USERSTATS(usw_item)->userid != userid) { if (userid != -1) { u_item = find_userid(userid); if (!u_item) { LOGERR("%s() userid %"PRId64" ignored - userstats but not users", __func__, userid); } else { str_to_buf(DATA_USERS(u_item)->username, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "username%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(userid, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "userid%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(u_hashrate1hr, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "u_hashrate1hr%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; } } userid = DATA_USERSTATS(usw_item)->userid; u_hashrate1hr = 0; } u_hashrate1hr += DATA_USERSTATS(usw_item)->hashrate1hr; tmp_item = usw_item; usw_item = next_in_ktree(usw_ctx); k_add_head(userstats_free, tmp_item); } if (userid != -1) { u_item = find_userid(userid); if (!u_item) { LOGERR("%s() userid %"PRId64" ignored - userstats but not users", __func__, userid); } else { str_to_buf(DATA_USERS(u_item)->username, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "username%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(userid, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "userid%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(u_hashrate1hr, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "u_hashrate1hr%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; } } userstats_workername_root = free_ktree(userstats_workername_root, NULL); K_WUNLOCK(userstats_free); snprintf(tmp, sizeof(tmp), "rows=%d", rows); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.ok.allusers", id); return buf; } static char *cmd_sharelog(char *cmd, char *id, __maybe_unused tv_t *notnow, char *by, char *code, char *inet, tv_t *cd) { char reply[1024] = ""; size_t siz = sizeof(reply); PGconn *conn; // 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; int64_t workinfoid; bool igndup = false; if (reloading) { 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("workinfoid", 1, NULL, reply, siz); if (!i_workinfoid) return strdup(reply); i_poolinstance = require_name("poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) return strdup(reply); i_transactiontree = require_name("transactiontree", 0, NULL, reply, siz); if (!i_transactiontree) return strdup(reply); i_merklehash = require_name("merklehash", 0, NULL, reply, siz); if (!i_merklehash) return strdup(reply); i_prevhash = require_name("prevhash", 1, NULL, reply, siz); if (!i_prevhash) return strdup(reply); i_coinbase1 = require_name("coinbase1", 1, NULL, reply, siz); if (!i_coinbase1) return strdup(reply); i_coinbase2 = require_name("coinbase2", 1, NULL, reply, siz); if (!i_coinbase2) return strdup(reply); i_version = require_name("version", 1, NULL, reply, siz); if (!i_version) return strdup(reply); i_bits = require_name("bits", 1, NULL, reply, siz); if (!i_bits) return strdup(reply); i_ntime = require_name("ntime", 1, NULL, reply, siz); if (!i_ntime) return strdup(reply); i_reward = require_name("reward", 1, NULL, reply, siz); if (!i_reward) return strdup(reply); conn = dbconnect(); workinfoid = workinfo_add(conn, DATA_TRANSFER(i_workinfoid)->data, DATA_TRANSFER(i_poolinstance)->data, DATA_TRANSFER(i_transactiontree)->data, DATA_TRANSFER(i_merklehash)->data, DATA_TRANSFER(i_prevhash)->data, DATA_TRANSFER(i_coinbase1)->data, DATA_TRANSFER(i_coinbase2)->data, DATA_TRANSFER(i_version)->data, DATA_TRANSFER(i_bits)->data, DATA_TRANSFER(i_ntime)->data, DATA_TRANSFER(i_reward)->data, by, code, inet, cd, igndup); PQfinish(conn); if (workinfoid == -1) { LOGERR("%s.failed.DBE", id); return strdup("failed.DBE"); } LOGDEBUG("%s.ok.added %"PRId64, id, workinfoid); snprintf(reply, siz, "ok.%"PRId64, workinfoid); return strdup(reply); } else if (strcasecmp(cmd, STR_SHARES) == 0) { K_ITEM *i_workinfoid, *i_username, *i_workername, *i_clientid, *i_enonce1; K_ITEM *i_nonce2, *i_nonce, *i_diff, *i_sdiff, *i_secondaryuserid; bool ok; // This just excludes the shares we certainly don't need if (reloading) { if (tv_newer(cd, &(dbstatus.oldest_sharesummary_firstshare_n))) return NULL; } i_workinfoid = require_name("workinfoid", 1, NULL, reply, siz); if (!i_workinfoid) return strdup(reply); i_username = require_name("username", 1, NULL, reply, siz); if (!i_username) return strdup(reply); i_workername = require_name("workername", 1, NULL, reply, siz); if (!i_workername) return strdup(reply); i_clientid = require_name("clientid", 1, NULL, reply, siz); if (!i_clientid) return strdup(reply); i_enonce1 = require_name("enonce1", 1, NULL, reply, siz); if (!i_enonce1) return strdup(reply); i_nonce2 = require_name("nonce2", 1, NULL, reply, siz); if (!i_nonce2) return strdup(reply); i_nonce = require_name("nonce", 1, NULL, reply, siz); if (!i_nonce) return strdup(reply); i_diff = require_name("diff", 1, NULL, reply, siz); if (!i_diff) return strdup(reply); i_sdiff = require_name("sdiff", 1, NULL, reply, siz); if (!i_sdiff) return strdup(reply); i_secondaryuserid = require_name("secondaryuserid", 1, NULL, reply, siz); if (!i_secondaryuserid) return strdup(reply); ok = shares_add(DATA_TRANSFER(i_workinfoid)->data, DATA_TRANSFER(i_username)->data, DATA_TRANSFER(i_workername)->data, DATA_TRANSFER(i_clientid)->data, DATA_TRANSFER(i_enonce1)->data, DATA_TRANSFER(i_nonce2)->data, DATA_TRANSFER(i_nonce)->data, DATA_TRANSFER(i_diff)->data, DATA_TRANSFER(i_sdiff)->data, DATA_TRANSFER(i_secondaryuserid)->data, by, code, inet, cd); if (!ok) { LOGERR("%s.failed.DATA", id); return strdup("failed.DATA"); } LOGDEBUG("%s.ok.added %s", id, DATA_TRANSFER(i_nonce)->data); snprintf(reply, siz, "ok.added %s", DATA_TRANSFER(i_nonce)->data); return strdup(reply); } else if (strcasecmp(cmd, STR_SHAREERRORS) == 0) { K_ITEM *i_workinfoid, *i_username, *i_workername, *i_clientid, *i_errn; K_ITEM *i_error, *i_secondaryuserid; bool ok; // This just excludes the shareerrors we certainly don't need if (reloading) { if (tv_newer(cd, &(dbstatus.oldest_sharesummary_firstshare_n))) return NULL; } i_workinfoid = require_name("workinfoid", 1, NULL, reply, siz); if (!i_workinfoid) return strdup(reply); i_username = require_name("username", 1, NULL, reply, siz); if (!i_username) return strdup(reply); i_workername = require_name("workername", 1, NULL, reply, siz); if (!i_workername) return strdup(reply); i_clientid = require_name("clientid", 1, NULL, reply, siz); if (!i_clientid) return strdup(reply); i_errn = require_name("errn", 1, NULL, reply, siz); if (!i_errn) return strdup(reply); i_error = require_name("error", 1, NULL, reply, siz); if (!i_error) return strdup(reply); i_secondaryuserid = require_name("secondaryuserid", 1, NULL, reply, siz); if (!i_secondaryuserid) return strdup(reply); ok = shareerrors_add(DATA_TRANSFER(i_workinfoid)->data, DATA_TRANSFER(i_username)->data, DATA_TRANSFER(i_workername)->data, DATA_TRANSFER(i_clientid)->data, DATA_TRANSFER(i_errn)->data, DATA_TRANSFER(i_error)->data, DATA_TRANSFER(i_secondaryuserid)->data, by, code, inet, cd); if (!ok) { LOGERR("%s.failed.DATA", id); return strdup("failed.DATA"); } LOGDEBUG("%s.ok.added %s", id, DATA_TRANSFER(i_username)->data); snprintf(reply, siz, "ok.added %s", DATA_TRANSFER(i_username)->data); return strdup(reply); } else if (strcasecmp(cmd, STR_AGEWORKINFO) == 0) { K_ITEM *i_workinfoid, *i_poolinstance; bool ok; if (reloading) { if (tv_newer(cd, &(dbstatus.oldest_sharesummary_firstshare_n))) return NULL; } i_workinfoid = require_name("workinfoid", 1, NULL, reply, siz); if (!i_workinfoid) return strdup(reply); i_poolinstance = require_name("poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) return strdup(reply); ok = workinfo_age(NULL, DATA_TRANSFER(i_workinfoid)->data, DATA_TRANSFER(i_poolinstance)->data, by, code, inet, cd); if (!ok) { LOGERR("%s.failed.DATA", id); return strdup("failed.DATA"); } LOGDEBUG("%s.ok.aged %.*s", id, BIGINT_BUFSIZ, DATA_TRANSFER(i_workinfoid)->data); snprintf(reply, siz, "ok.%.*s", BIGINT_BUFSIZ, DATA_TRANSFER(i_workinfoid)->data); return strdup(reply); } LOGERR("%s.bad.cmd %s", cmd); return strdup("bad.cmd"); } // TODO: the confirm update: identify block changes from workinfo height? static char *cmd_blocks_do(char *cmd, char *id, char *by, char *code, char *inet, tv_t *cd, bool igndup) { char reply[1024] = ""; size_t siz = sizeof(reply); PGconn *conn; K_ITEM *i_height, *i_blockhash, *i_confirmed, *i_workinfoid, *i_username; K_ITEM *i_workername, *i_clientid, *i_enonce1, *i_nonce2, *i_nonce, *i_reward; char *msg; bool ok; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_height = require_name("height", 1, NULL, reply, siz); if (!i_height) return strdup(reply); i_blockhash = require_name("blockhash", 1, NULL, reply, siz); if (!i_blockhash) return strdup(reply); i_confirmed = require_name("confirmed", 1, NULL, reply, siz); if (!i_confirmed) return strdup(reply); DATA_TRANSFER(i_confirmed)->data[0] = tolower(DATA_TRANSFER(i_confirmed)->data[0]); switch(DATA_TRANSFER(i_confirmed)->data[0]) { case BLOCKS_NEW: i_workinfoid = require_name("workinfoid", 1, NULL, reply, siz); if (!i_workinfoid) return strdup(reply); i_username = require_name("username", 1, NULL, reply, siz); if (!i_username) return strdup(reply); i_workername = require_name("workername", 1, NULL, reply, siz); if (!i_workername) return strdup(reply); i_clientid = require_name("clientid", 1, NULL, reply, siz); if (!i_clientid) return strdup(reply); i_enonce1 = require_name("enonce1", 1, NULL, reply, siz); if (!i_enonce1) return strdup(reply); i_nonce2 = require_name("nonce2", 1, NULL, reply, siz); if (!i_nonce2) return strdup(reply); i_nonce = require_name("nonce", 1, NULL, reply, siz); if (!i_nonce) return strdup(reply); i_reward = require_name("reward", 1, NULL, reply, siz); if (!i_reward) return strdup(reply); msg = "added"; conn = dbconnect(); ok = blocks_add(conn, DATA_TRANSFER(i_height)->data, DATA_TRANSFER(i_blockhash)->data, DATA_TRANSFER(i_confirmed)->data, DATA_TRANSFER(i_workinfoid)->data, DATA_TRANSFER(i_username)->data, DATA_TRANSFER(i_workername)->data, DATA_TRANSFER(i_clientid)->data, DATA_TRANSFER(i_enonce1)->data, DATA_TRANSFER(i_nonce2)->data, DATA_TRANSFER(i_nonce)->data, DATA_TRANSFER(i_reward)->data, by, code, inet, cd, igndup); break; case BLOCKS_CONFIRM: msg = "confirmed"; conn = dbconnect(); ok = blocks_add(conn, DATA_TRANSFER(i_height)->data, DATA_TRANSFER(i_blockhash)->data, DATA_TRANSFER(i_confirmed)->data, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, by, code, inet, cd, igndup); break; default: LOGERR("%s.failed.invalid conf='%s'", id, DATA_TRANSFER(i_confirmed)->data); return strdup("failed.DATA"); } PQfinish(conn); if (!ok) { LOGERR("%s.failed.DBE", 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(char *cmd, char *id, __maybe_unused tv_t *notnow, char *by, char *code, char *inet, tv_t *cd) { bool igndup = false; 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(cmd, id, by, code, inet, cd, igndup); } static char *cmd_auth_do(char *cmd, char *id, __maybe_unused tv_t *now, char *by, char *code, char *inet, tv_t *cd, bool igndup) { char reply[1024] = ""; size_t siz = sizeof(reply); PGconn *conn; K_ITEM *i_poolinstance, *i_username, *i_workername, *i_clientid; K_ITEM *i_enonce1, *i_useragent; char *secuserid; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_poolinstance = optional_name("poolinstance", 1, NULL); if (!i_poolinstance) i_poolinstance = &auth_poolinstance; i_username = require_name("username", 1, NULL, reply, siz); if (!i_username) return strdup(reply); i_workername = require_name("workername", 1, NULL, reply, siz); if (!i_workername) return strdup(reply); i_clientid = require_name("clientid", 1, NULL, reply, siz); if (!i_clientid) return strdup(reply); i_enonce1 = require_name("enonce1", 1, NULL, reply, siz); if (!i_enonce1) return strdup(reply); i_useragent = require_name("useragent", 0, NULL, reply, siz); if (!i_useragent) return strdup(reply); conn = dbconnect(); secuserid = auths_add(conn, DATA_TRANSFER(i_poolinstance)->data, DATA_TRANSFER(i_username)->data, DATA_TRANSFER(i_workername)->data, DATA_TRANSFER(i_clientid)->data, DATA_TRANSFER(i_enonce1)->data, DATA_TRANSFER(i_useragent)->data, by, code, inet, cd, igndup); PQfinish(conn); if (!secuserid) { LOGDEBUG("%s.failed.DBE", 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(char *cmd, char *id, tv_t *now, char *by, char *code, char *inet, tv_t *cd) { bool igndup = false; 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(cmd, id, now, by, code, inet, cd, igndup); } static char *cmd_homepage(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_ITEM *i_username, *u_item, *b_item, *p_item, *us_item, look; double u_hashrate5m, u_hashrate1hr; char reply[1024], tmp[1024], *buf; USERSTATS userstats; int64_t u_elapsed; K_TREE_CTX ctx[1], w_ctx[1]; size_t len, off; bool has_uhr; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = optional_name("username", 1, NULL); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); if (last_bc.tv_sec) { tvs_to_buf(&last_bc, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "lastbc=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } else { snprintf(tmp, sizeof(tmp), "lastbc=?%c", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } // TODO: handle orphans b_item = last_in_ktree(blocks_root, ctx); if (b_item) { tvs_to_buf(&(DATA_BLOCKS(b_item)->createdate), reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "lastblock=%s%cconfirmed=%s%c", reply, FLDSEP, DATA_BLOCKS(b_item)->confirmed, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } else { snprintf(tmp, sizeof(tmp), "lastblock=?%cconfirmed=?%c", FLDSEP, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } // TODO: assumes only one poolinstance (for now) p_item = last_in_ktree(poolstats_root, ctx); if (p_item) { int_to_buf(DATA_POOLSTATS(p_item)->users, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "users=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); int_to_buf(DATA_POOLSTATS(p_item)->workers, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "workers=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(DATA_POOLSTATS(p_item)->hashrate5m, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "p_hashrate5m=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(DATA_POOLSTATS(p_item)->hashrate1hr, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "p_hashrate1hr=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(DATA_POOLSTATS(p_item)->elapsed, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "p_elapsed=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } else { snprintf(tmp, sizeof(tmp), "users=?%cworkers=?%cp_hashrate5m=?%c" "p_hashrate1hr=?%cp_elapsed=?%c", FLDSEP, FLDSEP, FLDSEP, FLDSEP, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } u_item = NULL; if (i_username) u_item = find_users(DATA_TRANSFER(i_username)->data); has_uhr = false; if (p_item && u_item) { K_TREE *userstats_workername_root = new_ktree(); u_hashrate5m = u_hashrate1hr = 0.0; u_elapsed = -1; // find last stored userid record userstats.userid = DATA_USERS(u_item)->userid; userstats.statsdate.tv_sec = date_eot.tv_sec; userstats.statsdate.tv_usec = date_eot.tv_usec; // find/cmp doesn't get to here STRNCPY(userstats.poolinstance, EMPTY); STRNCPY(userstats.workername, EMPTY); look.data = (void *)(&userstats); K_RLOCK(userstats_free); us_item = find_before_in_ktree(userstats_root, &look, cmp_userstats, ctx); while (us_item && DATA_USERSTATS(us_item)->userid == userstats.userid && tvdiff(now, &(DATA_USERSTATS(us_item)->statsdate)) < USERSTATS_PER_S) { // TODO: add the latest per pool instance (this is the latest per worker) // Ignore summarised data from the DB, it should be old so irrelevant if (DATA_USERSTATS(us_item)->poolinstance[0] && !find_in_ktree(userstats_workername_root, us_item, cmp_userstats_workername, w_ctx)) { u_hashrate5m += DATA_USERSTATS(us_item)->hashrate5m; u_hashrate1hr += DATA_USERSTATS(us_item)->hashrate1hr; if (u_elapsed == -1 || u_elapsed > DATA_USERSTATS(us_item)->elapsed) u_elapsed = DATA_USERSTATS(us_item)->elapsed; has_uhr = true; userstats_workername_root = add_to_ktree(userstats_workername_root, us_item, cmp_userstats_workername); } us_item = prev_in_ktree(ctx); } userstats_workername_root = free_ktree(userstats_workername_root, NULL); K_RUNLOCK(userstats_free); } if (has_uhr) { double_to_buf(u_hashrate5m, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "u_hashrate5m=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(u_hashrate1hr, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "u_hashrate1hr=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(u_elapsed, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "u_elapsed=%s", reply); APPEND_REALLOC(buf, off, len, tmp); } else { snprintf(tmp, sizeof(tmp), "u_hashrate5m=?%cu_hashrate1hr=?%cu_elapsed=?", FLDSEP, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } LOGDEBUG("%s.ok.home,user=%s", id, i_username ? DATA_TRANSFER(i_username)->data : "N"); return buf; } static char *cmd_dsp(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_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("file", 1, NULL, reply, siz); if (!i_file) return strdup(reply); dsp_ktree(transfer_free, transfer_root, DATA_TRANSFER(i_file)->data, NULL); dsp_ktree(sharesummary_free, sharesummary_root, DATA_TRANSFER(i_file)->data, NULL); dsp_ktree(userstats_free, userstats_root, DATA_TRANSFER(i_file)->data, NULL); LOGDEBUG("%s.ok.dsp.file='%s'", id, DATA_TRANSFER(i_file)->data); return strdup("ok.dsp"); */ } static char *cmd_stats(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) { char tmp[1024], *buf; size_t len, off; uint64_t ram, tot = 0; K_LIST *klist; int i = 0; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); APPEND_REALLOC(buf, off, len, cmd); // Doesn't include blob memory // - average transactiontree length of ~119k I have is ~28k (>3.3GB) #define USEINFO(_obj, _stores, _trees) \ klist = _obj ## _free; \ ram = sizeof(K_LIST) + _stores * sizeof(K_STORE) + \ klist->allocate * klist->item_mem_count * klist->siz + \ sizeof(K_TREE) * (klist->total - klist->count) * _trees; \ snprintf(tmp, sizeof(tmp), \ "%cname%d=" #_obj "%callocated%d=%d%cstore%d=%d" \ "%ctrees%d=%d%cram%d=%"PRIu64, \ i ? FLDSEP : '.', i, \ FLDSEP, i, klist->total, \ FLDSEP, i, klist->total - klist->count, \ FLDSEP, i, _trees, \ FLDSEP, i, ram); \ APPEND_REALLOC(buf, off, len, tmp); \ tot += ram; \ i++; USEINFO(users, 1, 2); USEINFO(workers, 1, 1); USEINFO(payments, 1, 1); USEINFO(idcontrol, 1, 0); USEINFO(workinfo, 1, 1); USEINFO(shares, 1, 1); USEINFO(shareerrors, 1, 1); USEINFO(sharesummary, 1, 2); USEINFO(blocks, 1, 1); USEINFO(auths, 1, 1); USEINFO(poolstats, 1, 1); USEINFO(userstats, 4, 2); USEINFO(workerstatus, 1, 1); snprintf(tmp, sizeof(tmp), "%ctotalram=%"PRIu64, FLDSEP, tot); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.ok.%s...", id, cmd); return buf; } enum cmd_values { CMD_UNSET, CMD_REPLY, // Means something was wrong - send back reply CMD_SHUTDOWN, CMD_PING, CMD_SHARELOG, CMD_AUTH, CMD_ADDUSER, CMD_CHKPASS, CMD_POOLSTAT, CMD_USERSTAT, CMD_BLOCK, CMD_NEWID, CMD_PAYMENTS, CMD_WORKERS, CMD_ALLUSERS, CMD_HOMEPAGE, CMD_DSP, CMD_STATS, CMD_END }; // TODO: limit access #define ACCESS_POOL "p" #define ACCESS_SYSTEM "s" #define ACCESS_WEB "w" #define ACCESS_PROXY "x" #define ACCESS_CKDB "c" 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)(char *, char *, tv_t *, char *, char *, char *, tv_t *); char *access; } cmds[] = { { CMD_SHUTDOWN, "shutdown", true, false, NULL, ACCESS_SYSTEM }, { CMD_PING, "ping", true, false, NULL, ACCESS_SYSTEM ACCESS_POOL ACCESS_WEB }, { CMD_SHARELOG, STR_WORKINFO, false, true, cmd_sharelog, ACCESS_POOL }, { CMD_SHARELOG, STR_SHARES, false, true, cmd_sharelog, ACCESS_POOL }, { CMD_SHARELOG, STR_SHAREERRORS, false, true, cmd_sharelog, ACCESS_POOL }, { CMD_SHARELOG, STR_AGEWORKINFO, false, true, cmd_sharelog, ACCESS_POOL }, { CMD_AUTH, "authorise", false, true, cmd_auth, ACCESS_POOL }, { CMD_ADDUSER, "adduser", false, false, cmd_adduser, ACCESS_WEB }, { CMD_CHKPASS, "chkpass", false, false, cmd_chkpass, ACCESS_WEB }, { CMD_POOLSTAT, "poolstats", false, true, cmd_poolstats, ACCESS_POOL }, { CMD_USERSTAT, "userstats", false, true, cmd_userstats, ACCESS_POOL }, { CMD_BLOCK, "block", false, true, cmd_blocks, ACCESS_POOL }, { CMD_NEWID, "newid", false, false, cmd_newid, ACCESS_SYSTEM }, { CMD_PAYMENTS, "payments", false, false, cmd_payments, ACCESS_WEB }, { CMD_WORKERS, "workers", false, false, cmd_workers, ACCESS_WEB }, { CMD_ALLUSERS, "allusers", false, false, cmd_allusers, ACCESS_WEB }, { CMD_HOMEPAGE, "homepage", false, false, cmd_homepage, ACCESS_WEB }, { CMD_DSP, "dsp", false, false, cmd_dsp, ACCESS_SYSTEM }, { CMD_STATS, "stats", true, false, cmd_stats, ACCESS_SYSTEM }, { CMD_END, NULL, false, false, NULL, NULL } }; static enum cmd_values breakdown(char *buf, int *which_cmds, char *cmd, char *id, tv_t *cd) { char reply[1024] = ""; K_TREE_CTX ctx[1]; K_ITEM *item; char *cmdptr, *idptr, *data, *next, *eq; bool noid = false; *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; } 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) { LOGERR("Json decode error from command: '%s'", cmd); free(cmdptr); return CMD_REPLY; } json_iter = json_object_iter(json_data); K_WLOCK(transfer_free); while (json_iter) { json_key = json_object_iter_key(json_iter); json_value = json_object_iter_value(json_iter); item = k_unlink_head(transfer_free); ok = true; json_typ = json_typeof(json_value); switch (json_typ) { case JSON_STRING: json_str = json_string_value(json_value); siz = strlen(json_str); if (siz >= sizeof(DATA_TRANSFER(item)->value)) DATA_TRANSFER(item)->data = strdup(json_str); else { STRNCPY(DATA_TRANSFER(item)->value, json_str); DATA_TRANSFER(item)->data = DATA_TRANSFER(item)->value; } break; case JSON_REAL: snprintf(DATA_TRANSFER(item)->value, sizeof(DATA_TRANSFER(item)->value), "%f", json_real_value(json_value)); DATA_TRANSFER(item)->data = DATA_TRANSFER(item)->value; break; case JSON_INTEGER: snprintf(DATA_TRANSFER(item)->value, sizeof(DATA_TRANSFER(item)->value), "%"PRId64, (int64_t)json_integer_value(json_value)); DATA_TRANSFER(item)->data = DATA_TRANSFER(item)->value; break; case JSON_TRUE: case JSON_FALSE: snprintf(DATA_TRANSFER(item)->value, sizeof(DATA_TRANSFER(item)->value), "%s", (json_typ == JSON_TRUE) ? TRUE_STR : FALSE_STR); DATA_TRANSFER(item)->data = DATA_TRANSFER(item)->value; break; case JSON_ARRAY: { /* only one level array of strings for now (merkletree) * ignore other data */ size_t i, len, off, count = json_array_size(json_value); json_t *json_element; bool first = true; APPEND_REALLOC_INIT(DATA_TRANSFER(item)->data, off, len); for (i = 0; i < count; i++) { json_element = json_array_get(json_value, i); if (json_is_string(json_element)) { json_str = json_string_value(json_element); siz = strlen(json_str); if (first) first = false; else { APPEND_REALLOC(DATA_TRANSFER(item)->data, off, len, " "); } APPEND_REALLOC(DATA_TRANSFER(item)->data, off, len, json_str); } else LOGERR("%s() unhandled json type %d in array %s" " in cmd %s", __func__, json_typ, json_key, cmd); } } break; default: LOGERR("%s() unhandled json type %d in cmd %s", __func__, json_typ, cmd); ok = false; break; } if (ok) STRNCPY(DATA_TRANSFER(item)->name, json_key); if (!ok || find_in_ktree(transfer_root, item, cmp_transfer, ctx)) { if (DATA_TRANSFER(item)->data != DATA_TRANSFER(item)->value) free(DATA_TRANSFER(item)->data); k_add_head(transfer_free, item); } else { transfer_root = add_to_ktree(transfer_root, item, cmp_transfer); k_add_head(transfer_store, item); } json_iter = json_object_iter_next(json_data, json_iter); } K_WUNLOCK(transfer_free); json_decref(json_data); } else { K_WLOCK(transfer_free); while (next && *next) { data = next; next = strchr(data, FLDSEP); if (next) *(next++) = '\0'; eq = strchr(data, '='); if (!eq) eq = EMPTY; else *(eq++) = '\0'; item = k_unlink_head(transfer_free); STRNCPY(DATA_TRANSFER(item)->name, data); STRNCPY(DATA_TRANSFER(item)->value, eq); DATA_TRANSFER(item)->data = DATA_TRANSFER(item)->value; if (find_in_ktree(transfer_root, item, cmp_transfer, ctx)) { if (DATA_TRANSFER(item)->data != DATA_TRANSFER(item)->value) free(DATA_TRANSFER(item)->data); k_add_head(transfer_free, item); } else { transfer_root = add_to_ktree(transfer_root, item, cmp_transfer); k_add_head(transfer_store, item); } } K_WUNLOCK(transfer_free); } if (cmds[*which_cmds].createdate) { item = require_name("createdate", 10, NULL, reply, sizeof(reply)); if (!item) return CMD_REPLY; txt_to_ctv("createdate", DATA_TRANSFER(item)->data, cd, sizeof(*cd)); if (cd->tv_sec == 0) { LOGERR("%s(): failed, %s has invalid createdate '%s'", __func__, cmdptr, DATA_TRANSFER(item)->data); free(cmdptr); return CMD_REPLY; } } free(cmdptr); return cmds[*which_cmds].cmd_val; } static void summarise_poolstats() { // TODO } // TODO: daily // TODO: consider limiting how much/how long this processes each time static void summarise_userstats() { K_TREE_CTX ctx[1], ctx2[1]; K_ITEM *first, *new, *next, *tmp; USERSTATS *userstats; double statrange, factor; bool locked, upgrade; tv_t now, 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); first = first_in_ktree(userstats_statsdate_root, ctx); // Oldest non DB stat // TODO: make the index start with summarylevel? so can find faster while (first && DATA_USERSTATS(first)->summarylevel[0] != SUMMARY_NONE) first = next_in_ktree(ctx); if (!first) break; statrange = tvdiff(&now, &(DATA_USERSTATS(first)->statsdate)); // Is there data ready for summarising? if (statrange <= USERSTATS_AGE) break; copy_tv(&when, &(DATA_USERSTATS(first)->statsdate)); /* Convert when to the start of the timeframe after the one it is in * assume timeval ignores leapseconds ... */ when.tv_sec = when.tv_sec - (when.tv_sec % USERSTATS_DB_S) + USERSTATS_DB_S; when.tv_usec = 0; // Is the whole timerange up to before 'when' ready for summarising? statrange = tvdiff(&now, &when); if (statrange < USERSTATS_AGE) break; next = next_in_ktree(ctx); upgrade = true; K_ULOCK(userstats_free); new = k_unlink_head(userstats_free); userstats = DATA_USERSTATS(new); memcpy(userstats, DATA_USERSTATS(first), sizeof(USERSTATS)); userstats_root = remove_from_ktree(userstats_root, first, cmp_userstats, ctx2); userstats_statsdate_root = remove_from_ktree(userstats_statsdate_root, first, cmp_userstats_statsdate, ctx2); k_unlink_item(userstats_store, first); k_add_head(userstats_summ, first); count = 1; while (next) { statrange = tvdiff(&when, &(DATA_USERSTATS(next)->statsdate)); if (statrange <= 0) break; tmp = next_in_ktree(ctx); if (DATA_USERSTATS(next)->summarylevel[0] == SUMMARY_NONE && DATA_USERSTATS(next)->userid == userstats->userid && strcmp(DATA_USERSTATS(next)->workername, userstats->workername) == 0) { count++; userstats->hashrate += DATA_USERSTATS(next)->hashrate; userstats->hashrate5m += DATA_USERSTATS(next)->hashrate5m; userstats->hashrate1hr += DATA_USERSTATS(next)->hashrate1hr; userstats->hashrate24hr += DATA_USERSTATS(next)->hashrate24hr; if (userstats->elapsed > DATA_USERSTATS(next)->elapsed) userstats->elapsed = DATA_USERSTATS(next)->elapsed; userstats->summarycount += DATA_USERSTATS(next)->summarycount; userstats_root = remove_from_ktree(userstats_root, next, cmp_userstats, ctx2); userstats_statsdate_root = remove_from_ktree(userstats_statsdate_root, next, cmp_userstats_statsdate, ctx2); k_unlink_item(userstats_store, next); k_add_head(userstats_summ, next); } next = tmp; } 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(); // TODO: Consider releasing the lock for the DB insert? 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 */ 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" %d userstats records discarded " "from %s to %s", userstats->userid, count, tvbuf1, tvbuf2); } 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); if (upgrade) K_WUNLOCK(userstats_free); else K_IUNLOCK(userstats_free); upgrade = false; locked = 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()); while (!summarizer_die && !startup_complete) cksleep_ms(42); while (!summarizer_die) { sleep(19); if (!summarizer_die) summarise_poolstats(); if (!summarizer_die) summarise_userstats(); } return NULL; } // TODO: zzzz // auth ?!? what about invalid ?!? // At the end we need to wait for catch up of the ckpool data to our last read item // BUT this would require replaying all the replies ... // NEED to add to ckpool a request for the next message before opening the socket // to get at start of processing, and stop the reload when this is found static bool reload_line(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_ITEM *item; tv_t now, cd; bool ok = false; // 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); goto jilted; } else { LOGFILE(buf); cmdnum = breakdown(buf, &which_cmds, cmd, id, &cd); switch (cmdnum) { // Ignore case CMD_REPLY: // Shouldn't be there, but ignore also case CMD_SHUTDOWN: case CMD_PING: // Non pool commands, shouldn't be there, ignore case CMD_ADDUSER: case CMD_CHKPASS: case CMD_NEWID: case CMD_PAYMENTS: case CMD_WORKERS: case CMD_ALLUSERS: case CMD_HOMEPAGE: case CMD_DSP: case CMD_STATS: break; default: ans = cmds[which_cmds].func(cmd, id, &now, (char *)"code", (char *)__func__, (char *)"127.0.0.1", &cd); if (ans) free(ans); break; } } char ch = status_chars[count & 0x3]; putchar(ch); putchar('\r'); fflush(stdout); K_WLOCK(transfer_free); transfer_root = free_ktree(transfer_root, NULL); item = transfer_store->head; while (item) { if (DATA_TRANSFER(item)->data != DATA_TRANSFER(item)->value) free(DATA_TRANSFER(item)->data); item = item->next; } k_list_transfer_to_head(transfer_store, transfer_free); K_WUNLOCK(transfer_free); ok = true; jilted: return ok; } // Log files are every ... #define ROLL_S 3600 // 10Mb for now #define MAX_READ (10 * 1024 * 1024) // TODO: be able to specify a start time on the command line /* To handle a new database with no data: * touch the filename reported in "Failed to open 'filename'" * when ckdb aborts */ static bool reload_from(tv_t *start) { char buf[DATE_BUFSIZ+1], run[DATE_BUFSIZ+1]; size_t rflen = strlen(restorefrom); char *missing, *missing2; int missing_count; int processing; bool ok = true; char *filename; char data[MAX_READ]; uint64_t count, total; tv_t now; FILE *fp; reloading = true; tv_to_buf(start, buf, sizeof(buf)); LOGWARNING("%s(): from %s", __func__, buf); filename = rotating_filename(restorefrom, start->tv_sec); fp = fopen(filename, "r"); if (!fp) quithere(1, "Failed to open '%s'", filename); setnow(&now); tvs_to_buf(&now, run, sizeof(run)); snprintf(data, sizeof(data), "reload.%s.0", run); LOGFILE(data); total = 0; processing = 0; while (ok) { LOGWARNING("%s(): processing %s", __func__, filename); processing++; count = 0; while (ok && fgets_unlocked(data, MAX_READ, fp)) ok = reload_line(++count, data); if (ok) { if (ferror(fp)) { int err = errno; quithere(1, "Read failed on %s (%d) '%s'", filename, err, strerror(err)); } LOGWARNING("%s(): read %"PRIu64" lines from %s", __func__, count, filename); total += count; } fclose(fp); if (ok) { free(filename); start->tv_sec += ROLL_S; filename = rotating_filename(restorefrom, start->tv_sec); fp = fopen(filename, "r"); if (!fp) { missing = filename; filename = NULL; missing_count = 1; setnow(&now); now.tv_sec += ROLL_S; while (42) { start->tv_sec += ROLL_S; if (!tv_newer(start, &now)) { ok = false; break; } filename = rotating_filename(restorefrom, start->tv_sec); fp = fopen(filename, "r"); if (fp) break; if (missing_count++ > 1) free(missing2); missing2 = filename; } if (missing_count == 1) LOGWARNING("%s(): skipped %s", __func__, missing+rflen); else { LOGWARNING("%s(): skipped %d files from %s to %s", __func__, missing_count, missing+rflen, missing2+rflen); free(missing2); } free(missing); } } } if (filename) free(filename); snprintf(data, sizeof(data), "reload.%s.%"PRIu64, run, total); LOGFILE(data); LOGWARNING("%s(): %d files, total %"PRIu64" lines", __func__, processing, total); reloading = false; return ok; } // TODO: equivalent of api_allow static void *listener(void *arg) { proc_instance_t *pi = (proc_instance_t *)arg; unixsock_t *us = &pi->us; char *end, *ans, *rep, *buf = NULL, *dot; char *last_msg = NULL, *last_reply = NULL; char cmd[CMD_SIZ+1], id[ID_SIZ+1], reply[1024+1]; // Minimise the size in case of garbage char duptype[CMD_SIZ+1]; enum cmd_values cmdnum, last_cmd = 9001; int sockd, which_cmds; pthread_t summzer; uint64_t counter = 0; K_ITEM *item; size_t siz; tv_t now, cd; bool dup; create_pthread(&summzer, summariser, NULL); rename_proc(pi->sockname); if (!setup_data()) { LOGEMERG("ABORTING"); return NULL; } LOGWARNING("%s(): ckdb ready", __func__); startup_complete = true; while (true) { dealloc(buf); sockd = accept(us->sockd, NULL, NULL); if (sockd < 0) { LOGERR("Failed to accept on socket in listener"); break; } cmdnum = CMD_UNSET; 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: * System: shutdown and ping are always processed, * so for any others, send a ping between them * if you need to send the same message twice * Web: if the pool didn't do anything since the original * then the reply could be wrong for any reply that * changes over time ... however if the pool is busy * and we get the same request repeatedly, this will * reduce the load - thus always send the same reply * Pool: must not process it, must send back the same reply * TODO: remember last message+reply per source */ if (last_msg && strcmp(last_msg, buf) == 0) { dup = true; // This means an exact duplicate of the last non-dup snprintf(reply, sizeof(reply), "%s%ld,%ld", LOGDUP, now.tv_sec, now.tv_usec); LOGFILE(reply); cmdnum = last_cmd; STRNCPY(duptype, buf); dot = strchr(duptype, '.'); if (dot) *dot = '\0'; LOGWARNING("Duplicate '%s' message received", duptype); } else { dup = false; LOGFILE(buf); cmdnum = breakdown(buf, &which_cmds, cmd, id, &cd); last_cmd = cmdnum; } switch (cmdnum) { case CMD_REPLY: if (dup) send_unix_msg(sockd, last_reply); else { snprintf(reply, sizeof(reply), "%s.%ld.?.", id, now.tv_sec); if (last_reply) free(last_reply); last_reply = strdup(reply); 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); break; case CMD_PING: LOGDEBUG("Listener received ping request"); // Generate a new reply each time even on dup snprintf(reply, sizeof(reply), "%s.%ld.ok.pong", id, now.tv_sec); send_unix_msg(sockd, reply); break; default: if (dup) send_unix_msg(sockd, last_reply); else { ans = cmds[which_cmds].func(cmd, id, &now, (char *)"code", (char *)__func__, (char *)"127.0.0.1", &cd); siz = strlen(ans) + strlen(id) + 32; rep = malloc(siz); snprintf(rep, siz, "%s.%ld.%s", id, now.tv_sec, ans); free(ans); ans = NULL; if (last_reply) free(last_reply); last_reply = strdup(rep); send_unix_msg(sockd, rep); free(rep); rep = NULL; } break; } } close(sockd); char ch = status_chars[(counter++) & 0x3]; putchar(ch); putchar('\r'); fflush(stdout); if (cmdnum == CMD_SHUTDOWN) break; K_WLOCK(transfer_free); transfer_root = free_ktree(transfer_root, NULL); item = transfer_store->head; while (item) { if (DATA_TRANSFER(item)->data != DATA_TRANSFER(item)->value) free(DATA_TRANSFER(item)->data); item = item->next; } k_list_transfer_to_head(transfer_store, transfer_free); K_WUNLOCK(transfer_free); } dealloc(buf); if (last_reply) free(last_reply); close_unix_socket(us->sockd, us->path); return NULL; } #define RELOADFILES "ckdb" static void check_restore_dir() { struct stat statbuf; if (!restorefrom) quit(1, "ERR: '-r dir' required to specify the ckpool hourly ckdb log dir"); if (!(*restorefrom)) quit(1, "ERR: '-r dir' can't be empty"); trail_slash(&restorefrom); if (stat(restorefrom, &statbuf)) quit(1, "ERR: -r '%s' directory doesn't exist", restorefrom); restorefrom = realloc(restorefrom, strlen(restorefrom)+sizeof(RELOADFILES)); if (!restorefrom) quithere(1, "OOM"); strcat(restorefrom, RELOADFILES); } int main(int argc, char **argv) { struct sigaction handler; char buf[512]; ckpool_t ckp; int c, ret; char *kill; tv_t now; feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW); global_ckp = &ckp; memset(&ckp, 0, sizeof(ckp)); ckp.loglevel = LOG_NOTICE; while ((c = getopt(argc, argv, "c:kl:n:p:r:s:u:")) != -1) { switch(c) { case 'c': ckp.config = optarg; break; case 'k': ckp.killold = true; break; case 'n': ckp.name = strdup(optarg); break; case 'l': ckp.loglevel = atoi(optarg); if (ckp.loglevel < LOG_EMERG || ckp.loglevel > LOG_DEBUG) { quit(1, "Invalid loglevel (range %d - %d): %d", LOG_EMERG, LOG_DEBUG, ckp.loglevel); } break; case 'r': restorefrom = strdup(optarg); break; case 's': ckp.socket_dir = strdup(optarg); break; case 'u': db_user = strdup(optarg); kill = optarg; while (*kill) *(kill++) = ' '; break; case 'p': db_pass = strdup(optarg); kill = optarg; if (*kill) *(kill++) = ' '; while (*kill) *(kill++) = '\0'; break; } } check_restore_dir(); // if (!db_pass) // zzz if (!db_user) db_user = "postgres"; if (!ckp.name) ckp.name = "ckdb"; snprintf(buf, 15, "%s", ckp.name); prctl(PR_SET_NAME, buf, 0, 0, 0); memset(buf, 0, 15); if (!ckp.config) { ckp.config = strdup(ckp.name); realloc_strcat(&ckp.config, ".conf"); } if (!ckp.socket_dir) { // ckp.socket_dir = strdup("/tmp/"); 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, 0700); if (ret && errno != EEXIST) quit(1, "Failed to make directory %s", ckp.socket_dir); // parse_config(&ckp); if (!ckp.logdir) ckp.logdir = strdup("logs"); /* 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.log", ckp.logdir, ckp.name); ckp.logfp = fopen(buf, "a"); if (!ckp.logfp) quit(1, "Failed to open log file %s", buf); ckp.logfd = fileno(ckp.logfp); snprintf(logname, sizeof(logname), "%s%s", ckp.logdir, ckp.name); ckp.main.ckp = &ckp; ckp.main.processname = strdup("main"); ckp.main.sockname = strdup("listener"); write_namepid(&ckp.main); create_process_unixsock(&ckp.main); setnow(&now); srand((unsigned int)(now.tv_usec * 4096 + now.tv_sec % 4096)); create_pthread(&ckp.pth_listener, listener, &ckp.main); handler.sa_flags = 0; sigemptyset(&handler.sa_mask); sigaction(SIGTERM, &handler, NULL); sigaction(SIGINT, &handler, NULL); /* Shutdown from here if the listener is sent a shutdown message */ join_pthread(ckp.pth_listener); clean_up(&ckp); return 0; }