You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3639 lines
92 KiB

/*
* Copyright 2003-2014 Andrew Smith
* Copyright 2014 Con Kolivas
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version. See COPYING for more details.
*/
#include "config.h"
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <jansson.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <regex.h>
#ifdef HAVE_LIBPQ_FE_H
#include <libpq-fe.h>
#elif defined (HAVE_POSTGRESQL_LIBPQ_FE_H)
#include <postgresql/libpq-fe.h>
#endif
#include "ckpool.h"
#include "libckpool.h"
#include "klist.h"
#include "ktree.h"
// TODO: a lot of the tree access isn't locked
// will need to be if threading is required
static char *db_user;
static char *db_pass;
// size limit on the command string
#define ID_SIZ 31
#define TXT_BIG 256
#define TXT_MED 128
#define TXT_SML 64
#define TXT_FLAG 1
#define FLDSEP 0x02
// 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 PGOK(_res) ((_res) == PGRES_COMMAND_OK || \
(_res) == PGRES_TUPLES_OK || \
(_res) == PGRES_EMPTY_QUERY)
#define PGLOG(__LOG, __str, __rescode, __conn) do { \
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 = ' '; \
__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)
/* 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 APPEND_REALLOC(_dst, _dstoff, _dstsiz, _src) do { \
size_t _srclen = strlen(_src); \
if ((_dstoff) + _srclen >= (_dstsiz)) { \
_dstsiz += 1024; \
_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_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_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 };
#define HISTORYDATEINIT(_row, _now, _by, _code, _inet) do { \
_row->createdate.tv_sec = (_now)->tv_sec; \
_row->createdate.tv_usec = (_now)->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("createdate", 10, NULL); \
if (item) { \
long sec, usec; \
int n; \
n = sscanf(DATA_TRANSFER(item)->data, "%ld,%ld", &sec, &usec); \
if (n > 0) { \
_row->createdate.tv_sec = (time_t)sec; \
if (n > 1) \
_row->createdate.tv_usec = usec; \
else \
_row->createdate.tv_usec = 0L; \
} \
} \
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 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 MODIFYDATEINIT(_row, _now, _by, _code, _inet) do { \
_row->createdate.tv_sec = (_now)->tv_sec; \
_row->createdate.tv_usec = (_now)->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)
// 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 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)
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)
// JSON Methods
#define METHOD_WORKINFO "workinfo"
#define METHOD_SHARES "shares"
#define METHOD_SHAREERRORS "shareerror"
#define METHOD_AUTH "authorise"
// LOGFILE codes - should be in libckpool.h ... with the file logging code
#define CODE_WORKINFO "W"
#define CODE_SHARES "S"
#define CODE_SHAREERRORSS "E"
// 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 1024
#define LIMIT_TRANSFER 0
#define DATA_TRANSFER(_item) ((TRANSFER *)(_item->data))
static K_TREE *transfer_root;
static K_LIST *transfer_list;
static K_STORE *transfer_store;
// 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_list;
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_list;
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_list;
static K_STORE *workers_store;
#define STRINT(x) STRINT2(x)
#define STRINT2(x) #x
#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_list;
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_list;
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_list;
static K_STORE *accountbalance_store;
// ACCOUNTADJUSTMENT
typedef struct accountbalance {
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 *accountbalance_root;
static K_LIST *accountbalance_list;
static K_STORE *accountbalance_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_list;
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_list;
static K_STORE *optioncontrol_store;
*/
// TODO: aging/discarding workinfo,shares
// WORKINFO id.sharelog.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;
static K_LIST *workinfo_list;
static K_STORE *workinfo_store;
// SHARES id.sharelog.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_list;
static K_STORE *shares_store;
// SHAREERRORS id.sharelog.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_list;
static K_STORE *shareerrors_store;
/*
// SHARESUMMARY
typedef struct sharesummary {
int64_t userid;
char workername[TXT_BIG+1];
int64_t workinfoid;
int64_t diff_acc;
int64_t diff_sta;
int64_t diff_dup;
int64_t diff_low;
int64_t diff_rej;
int64_t share_acc;
int64_t share_sta;
int64_t share_dup;
int64_t share_low;
int64_t share_rej;
tv_t first_share;
tv_t last_share;
char complete[TXT_FLAG+1];
MODIFYDATECONTROLFIELDS;
} SHARESUMMARY;
#define ALLOC_SHARESUMMARY 10000
#define LIMIT_SHARESUMMARY 0
#define DATA_SHARESUMMARY(_item) ((SHARESUMMARY *)(_item->data))
static K_TREE *sharesummary_root;
static K_LIST *sharesummary_list;
static K_STORE *sharesummary_store;
// BLOCKSUMMARY
typedef struct blocksummary {
int32_t height;
char blockhash[TXT_BIG+1];
int64_t userid;
char workername[TXT_BIG+1];
int64_t diff_acc;
int64_t diff_sta;
int64_t diff_dup;
int64_t diff_low;
int64_t diff_rej;
int64_t share_acc;
int64_t share_sta;
int64_t share_dup;
int64_t share_low;
int64_t share_rej;
tv_t first_share;
tv_t last_share;
char complete[TXT_FLAG+1];
MODIFYDATECONTROLFIELDS;
} BLOCKSUMMARY;
#define ALLOC_BLOCKSUMMARY 10000
#define LIMIT_BLOCKSUMMARY 0
#define DATA_BLOCKSUMMARY(_item) ((BLOCKSUMMARY *)(_item->data))
static K_TREE *blocksummary_root;
static K_LIST *blocksummary_list;
static K_STORE *blocksummary_store;
// BLOCKS
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 10000
#define LIMIT_BLOCKS 0
#define DATA_BLOCKS ((BLOCKS *)(_item->data))
static K_TREE *blocks_root;
static K_LIST *blocks_list;
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_list;
static K_STORE *miningpayouts_store;
// EVENTLOG
typedef struct eventlog {
int64_t eventlogid;
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_list;
static K_STORE *eventlog_store;
*/
// AUTHS
typedef struct auths {
int64_t authid;
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_list;
static K_STORE *auths_store;
/*
// POOLSTATS
// TODO: not in DB yet - design incomplete
// poll pool(s) every 10min - no - get every 1m: pool sending it
// so web page is kept up to date
// Store every 10m?
typedef struct poolstats {
char poolinstance[TXT_BIG+1];
int32_t users;
int32_t workers;
int64_t hashrate; // ... etc
int64_t hashrate5m;
int64_t hashrate1hr;
int64_t hashrate24hr;
HISTORYDATECONTROLFIELDS;
} 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_list;
static K_STORE *poolstats_store;
*/
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 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;
}
static K_ITEM *require_name(char *name, int len, char *patt, char *reply, size_t siz)
{
K_ITEM *item;
char *value;
regex_t re;
int ret;
item = find_transfer(name);
if (!item) {
snprintf(reply, siz, "failed.missing %s", name);
return NULL;
}
value = DATA_TRANSFER(item)->data;
if (!*value || (int)strlen(value) < len) {
snprintf(reply, siz, "failed.short %s", name);
return NULL;
}
if (patt) {
if (regcomp(&re, patt, REG_NOSUB) != 0) {
snprintf(reply, siz, "failed.REC %s", name);
return NULL;
}
ret = regexec(&re, value, (size_t)0, NULL, 0);
regfree(&re);
if (ret != 0) {
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)
{
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",
nam, (int)siz, (int)strlen(fld)+1);
}
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",
nam, (int)siz, (int)sizeof(int64_t));
}
*((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",
nam, (int)siz, (int)sizeof(int32_t));
}
*((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",
nam, (int)siz, (int)sizeof(tv_t));
}
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)",
nam, fld, n);
}
}
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_BLOB:
tmp = strdup(fld);
if (!tmp)
quithere(1, "Field %s (%d) OOM", nam, (int)strlen(fld));
*((char **)data) = tmp;
break;
case TYPE_DOUBLE:
if (siz != sizeof(double)) {
quithere(1, "Field %s int incorrect structure size %d - should be %d",
nam, (int)siz, (int)sizeof(double));
}
*((double *)data) = atof(fld);
break;
default:
quithere(1, "Unknown field %s (%d) to convert", nam, (int)typ);
break;
}
}
// 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)
{
txt_to_data(TYPE_STR, nam, fld, (void *)data, siz);
}
static void txt_to_bigint(char *nam, char *fld, int64_t *data, size_t siz)
{
txt_to_data(TYPE_BIGINT, nam, fld, (void *)data, siz);
}
static void txt_to_int(char *nam, char *fld, int32_t *data, size_t siz)
{
txt_to_data(TYPE_INT, nam, fld, (void *)data, siz);
}
static void txt_to_tv(char *nam, char *fld, tv_t *data, size_t siz)
{
txt_to_data(TYPE_TV, nam, fld, (void *)data, siz);
}
static void txt_to_blob(char *nam, char *fld, char *data)
{
txt_to_data(TYPE_BLOB, nam, fld, (void *)(&data), 0);
}
static void txt_to_double(char *nam, char *fld, double *data, size_t siz)
{
txt_to_data(TYPE_DOUBLE, nam, fld, (void *)data, siz);
}
static char *data_to_buf(enum data_type typ, void *data, char *buf, size_t siz)
{
struct tm tm;
char *buf2;
if (!buf) {
switch (typ) {
case TYPE_STR:
case TYPE_BLOB:
siz = strlen((char *)data) + 1;
break;
case TYPE_BIGINT:
case TYPE_INT:
case TYPE_TV:
case TYPE_DOUBLE:
siz = 64; // More than big enough
break;
default:
quithere(1, "Unknown field (%d) to convert", (int)typ);
break;
}
buf = malloc(siz);
if (!buf)
quithere(1, "OOM (%d)", (int)siz);
}
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)", (int)siz);
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_DOUBLE:
snprintf(buf, siz, "%f", *((double *)data));
break;
}
return buf;
}
static char *str_to_buf(char data[], char *buf, size_t siz)
{
return data_to_buf(TYPE_STR, (void *)data, buf, siz);
}
static char *bigint_to_buf(int64_t data, char *buf, size_t siz)
{
return data_to_buf(TYPE_BIGINT, (void *)(&data), buf, siz);
}
static char *int_to_buf(int32_t data, char *buf, size_t siz)
{
return data_to_buf(TYPE_INT, (void *)(&data), buf, siz);
}
static char *tv_to_buf(tv_t *data, char *buf, size_t siz)
{
return data_to_buf(TYPE_TV, (void *)data, buf, siz);
}
/* unused yet
static char *blob_to_buf(char *data, char *buf, size_t siz)
{
return data_to_buf(TYPE_BLOB, (void *)data, buf, siz);
}
static char *double_to_buf(double data, char *buf, size_t siz)
{
return data_to_buf(TYPE_DOUBLE, (void *)(&data), buf, siz);
}
*/
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'", PQerrorMessage(conn));
return conn;
}
static int64_t nextid(PGconn *conn, char *idname, int64_t increment,
tv_t *now, 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(now, 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;
}
// 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) -
(double)(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);
}
/* unused
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_list);
item = k_unlink_head(users_list);
K_WUNLOCK(users_list);
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_list);
if (!ok)
k_add_head(users_list, 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_list);
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_list);
for (i = 0; i < n; i++) {
item = k_unlink_head(users_list);
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_list, item);
K_WUNLOCK(users_list);
PQclear(res);
if (ok)
LOGDEBUG("%s(): built", __func__);
return true;
}
void users_reload()
{
PGconn *conn = dbconnect();
K_WLOCK(users_list);
users_root = free_ktree(users_root, NULL);
userid_root = free_ktree(userid_root, NULL);
k_list_transfer_to_head(users_store, users_list);
K_WUNLOCK(users_list);
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) -
(double)(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, tv_t *now, char *by,
char *code, char *inet)
{
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_list);
item = k_unlink_head(workers_list);
K_WUNLOCK(workers_list);
row = DATA_WORKERS(item);
row->workerid = nextid(conn, "workerid", (int64_t)1, now, 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, now, 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_list);
if (!ret)
k_add_head(workers_list, item);
else {
workers_root = add_to_ktree(workers_root, item, cmp_workers);
k_add_head(workers_store, item);
}
K_WUNLOCK(workers_list);
return ret;
}
static bool workers_update(PGconn *conn, K_ITEM *item, char *difficultydefault,
char *idlenotificationenabled, char *idlenotificationtime,
tv_t *now, char *by, char *code, char *inet)
{
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, now, by, code, inet);
if (diffdef != row->difficultydefault ||
idlenot != row->idlenotificationenabled[0] ||
nottime != row->idlenotificationtime) {
upd = "update workers set expirydate=$1 where workerid=$2 and expirydate=$3";
par = 0;
params[par++] = tv_to_buf(now, NULL, 0);
params[par++] = bigint_to_buf(row->workerid, NULL, 0);
params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0);
// Not the full size of params[] so no PARCHK()
res = PQexec(conn, "Begin");
rescode = PQresultStatus(res);
if (!PGOK(rescode)) {
PGLOGERR("Begin", rescode, conn);
PQclear(res);
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("Insert", rescode, conn);
res = PQexec(conn, "Rollback");
PQclear(res);
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);
// This one should be the full size
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");
PQclear(res);
goto unparam;
}
res = PQexec(conn, "Commit");
PQclear(res);
}
ok = true;
unparam:
for (n = 0; n < par; n++)
free(params[n]);
return ok;
}
static K_ITEM *new_worker(PGconn *conn, bool update, int64_t userid, char *workername,
char *diffdef, char *idlenotificationenabled,
char *idlenotificationtime, tv_t *now, char *by,
char *code, char *inet)
{
K_ITEM *item;
item = find_workers(userid, workername);
if (item) {
if (update) {
workers_update(conn, item, diffdef, idlenotificationenabled,
idlenotificationtime, now, by, code, inet);
}
} else {
item = workers_add(conn, userid, workername, diffdef,
idlenotificationenabled, idlenotificationtime,
now, by, code, inet);
}
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_list);
for (i = 0; i < n; i++) {
item = k_unlink_head(workers_list);
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_list, item);
K_WUNLOCK(workers_list);
PQclear(res);
if (ok)
LOGDEBUG("%s(): built", __func__);
return true;
}
void workers_reload()
{
PGconn *conn = dbconnect();
K_WLOCK(workers_list);
workers_root = free_ktree(workers_root, NULL);
k_list_transfer_to_head(workers_store, workers_list);
K_WUNLOCK(workers_list);
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) -
(double)(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_list);
for (i = 0; i < n; i++) {
item = k_unlink_head(payments_list);
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_list, item);
K_WUNLOCK(payments_list);
PQclear(res);
if (ok)
LOGDEBUG("%s(): built", __func__);
return true;
}
void payments_reload()
{
PGconn *conn = dbconnect();
K_WLOCK(payments_list);
payments_root = free_ktree(payments_root, NULL);
k_list_transfer_to_head(payments_store, payments_list);
K_WUNLOCK(payments_list);
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) -
(double)(DATA_WORKINFO(b)->workinfoid);
if (c == 0) {
c = tvdiff(&(DATA_WORKINFO(b)->expirydate),
&(DATA_WORKINFO(a)->expirydate));
}
return c;
}
static K_ITEM *find_workinfo(int64_t workinfoid)
{
WORKINFO workinfo;
K_TREE_CTX ctx[1];
K_ITEM look;
workinfo.workinfoid = workinfoid;
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,
tv_t *now, char *by, char *code, char *inet)
{
ExecStatusType rescode;
PGresult *res;
K_ITEM *item;
int n;
int64_t workinfoid = -1;
WORKINFO *row;
char *ins;
char *params[10 + HISTORYDATECOUNT];
int par;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(workinfo_list);
item = k_unlink_head(workinfo_list);
K_WUNLOCK(workinfo_list);
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, now, by, code, inet);
HISTORYDATETRANSFER(row);
par = 0;
params[par++] = bigint_to_buf(row->workinfoid, 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,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_list);
if (workinfoid == -1) {
free(row->transactiontree);
free(row->merklehash);
k_add_head(workinfo_list, item);
} else {
workinfo_root = add_to_ktree(workinfo_root, item, cmp_workinfo);
k_add_head(workinfo_store, item);
}
K_WUNLOCK(workinfo_list);
return workinfoid;
}
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 = 11;
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,"
"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_list);
for (i = 0; i < n; i++) {
item = k_unlink_head(workinfo_list);
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);
PQ_GET_FLD(res, i, "transactiontree", field, ok);
if (!ok)
break;
TXT_TO_BLOB("transactiontree", field, row->transactiontree);
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);
k_add_head(workinfo_store, item);
}
if (!ok)
k_add_head(workinfo_list, item);
K_WUNLOCK(workinfo_list);
PQclear(res);
if (ok)
LOGDEBUG("%s(): built", __func__);
return true;
}
void workinfo_reload()
{
// TODO: ??? a bad idea?
/*
PGconn *conn = dbconnect();
K_WLOCK(workinfo_list);
workinfo_root = free_ktree(workinfo_root, ???); free transactiontree and merklehash
k_list_transfer_to_head(workinfo_store, workinfo_list);
K_WUNLOCK(workinfo_list);
workinfo_fill(conn);
PQfinish(conn);
*/
}
// order by workinfoid asc, userid asc, createdate asc, nonce asc, expirydate desc
static double cmp_shares(K_ITEM *a, K_ITEM *b)
{
double c = (double)(DATA_SHARES(a)->workinfoid) -
(double)(DATA_SHARES(b)->workinfoid);
if (c == 0) {
c = (double)(DATA_SHARES(b)->userid) -
(double)(DATA_SHARES(a)->userid);
if (c == 0) {
c = tvdiff(&(DATA_SHARES(b)->createdate),
&(DATA_SHARES(a)->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;
}
// 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, tv_t *now, char *by, char *code, char *inet)
{
K_ITEM *s_item, *u_item, *w_item;
SHARES *shares;
bool ok = false;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(shares_list);
s_item = k_unlink_head(shares_list);
K_WUNLOCK(shares_list);
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, now, by, code, inet);
HISTORYDATETRANSFER(shares);
w_item = find_workinfo(shares->workinfoid);
if (!w_item)
goto unitem;
w_item = find_workers(shares->userid, shares->workername);
if (!w_item)
goto unitem;
// TODO: update stats - log file load will have to do all these checks also
ok = true;
unitem:
K_WLOCK(shares_list);
if (!ok)
k_add_head(shares_list, s_item);
else {
shares_root = add_to_ktree(shares_root, s_item, cmp_shares);
k_add_head(shares_store, s_item);
}
K_WUNLOCK(shares_list);
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) -
(double)(DATA_SHAREERRORS(b)->workinfoid);
if (c == 0) {
c = (double)(DATA_SHAREERRORS(b)->userid) -
(double)(DATA_SHAREERRORS(a)->userid);
if (c == 0) {
c = tvdiff(&(DATA_SHAREERRORS(b)->createdate),
&(DATA_SHAREERRORS(a)->createdate));
if (c == 0) {
c = tvdiff(&(DATA_SHAREERRORS(b)->expirydate),
&(DATA_SHAREERRORS(a)->expirydate));
}
}
}
return c;
}
// Memory (and log file) only
static bool shareerrors_add(char *workinfoid, char *username, char *workername,
char *clientid, char *errn, char *error, char *secondaryuserid,
tv_t *now, char *by, char *code, char *inet)
{
K_ITEM *s_item, *u_item, *w_item;
SHAREERRORS *shareerrors;
bool ok = false;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(shareerrors_list);
s_item = k_unlink_head(shareerrors_list);
K_WUNLOCK(shareerrors_list);
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("errno", errn, shareerrors->errn);
STRNCPY(shareerrors->error, error);
STRNCPY(shareerrors->secondaryuserid, secondaryuserid);
HISTORYDATEINIT(shareerrors, now, by, code, inet);
HISTORYDATETRANSFER(shareerrors);
w_item = find_workinfo(shareerrors->workinfoid);
if (!w_item)
goto unitem;
w_item = find_workers(shareerrors->userid, shareerrors->workername);
if (!w_item)
goto unitem;
// TODO: update stats - log file load will have to do all these checks also
ok = true;
unitem:
K_WLOCK(shareerrors_list);
if (!ok)
k_add_head(shareerrors_list, s_item);
else {
shareerrors_root = add_to_ktree(shareerrors_root, s_item, cmp_shareerrors);
k_add_head(shareerrors_store, s_item);
}
K_WUNLOCK(shareerrors_list);
return ok;
}
static bool shareerrors_fill()
{
// TODO: reload shareerrors from workinfo from log file
// and verify workinfo while doing that
return true;
}
static double cmp_auths(K_ITEM *a, K_ITEM *b)
{
double c = (double)(DATA_SHAREERRORS(a)->workinfoid) -
(double)(DATA_SHAREERRORS(b)->workinfoid);
if (c == 0) {
c = (double)(DATA_SHAREERRORS(b)->userid) -
(double)(DATA_SHAREERRORS(a)->userid);
if (c == 0) {
c = tvdiff(&(DATA_SHAREERRORS(b)->createdate),
&(DATA_SHAREERRORS(a)->createdate));
if (c == 0) {
c = tvdiff(&(DATA_SHAREERRORS(b)->expirydate),
&(DATA_SHAREERRORS(a)->expirydate));
}
}
}
return c;
}
static char *auths_add(PGconn *conn, char *username, char *workername,
char *clientid, char *enonce1, char *useragent,
tv_t *now, char *by, char *code, char *inet)
{
ExecStatusType rescode;
PGresult *res;
K_ITEM *a_item, *u_item;
int n;
AUTHS *row;
char *ins;
char *secuserid = NULL;
char *params[6 + HISTORYDATECOUNT];
int par;
LOGDEBUG("%s(): add", __func__);
K_WLOCK(auths_list);
a_item = k_unlink_head(auths_list);
K_WUNLOCK(auths_list);
row = DATA_AUTHS(a_item);
u_item = find_users(username);
if (!u_item)
goto unitem;
row->userid = DATA_USERS(u_item)->userid;
new_worker(conn, false, row->userid, workername, DIFFICULTYDEFAULT_DEF_STR,
IDLENOTIFICATIONENABLED_DEF, IDLENOTIFICATIONTIME_DEF_STR, now,
by, code, inet);
STRNCPY(row->workername, workername);
TXT_TO_INT("clientid", clientid, row->clientid);
STRNCPY(row->enonce1, enonce1);
STRNCPY(row->useragent, useragent);
HISTORYDATEINIT(row, now, by, code, inet);
HISTORYDATETRANSFER(row);
row->authid = nextid(conn, "authid", (int64_t)1, now, by, code, inet);
if (row->authid == 0)
goto unitem;
par = 0;
params[par++] = bigint_to_buf(row->authid, 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,userid,workername,clientid,enonce1,useragent"
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;
}
secuserid = DATA_USERS(u_item)->secondaryuserid;
unparam:
PQclear(res);
for (n = 0; n < par; n++)
free(params[n]);
unitem:
K_WLOCK(auths_list);
if (!secuserid)
k_add_head(auths_list, a_item);
else {
auths_root = add_to_ktree(auths_root, a_item, cmp_auths);
k_add_head(auths_store, a_item);
}
K_WUNLOCK(auths_list);
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_list);
for (i = 0; i < n; i++) {
item = k_unlink_head(auths_list);
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);
}
if (!ok)
k_add_head(auths_list, item);
K_WUNLOCK(auths_list);
PQclear(res);
if (ok)
LOGDEBUG("%s(): built", __func__);
return true;
}
void auths_reload()
{
PGconn *conn = dbconnect();
K_WLOCK(auths_list);
auths_root = free_ktree(auths_root, NULL);
k_list_transfer_to_head(auths_store, auths_list);
K_WUNLOCK(auths_list);
auths_fill(conn);
PQfinish(conn);
}
static void getdata()
{
PGconn *conn = dbconnect();
users_fill(conn);
workers_fill(conn);
payments_fill(conn);
workinfo_fill(conn);
shares_fill();
shareerrors_fill();
auths_fill(conn);
PQfinish(conn);
}
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 void setup_data()
{
transfer_list = k_new_list("Transfer", sizeof(TRANSFER), ALLOC_TRANSFER, LIMIT_TRANSFER, true);
transfer_store = k_new_store(transfer_list);
transfer_root = new_ktree();
users_list = k_new_list("Users", sizeof(USERS), ALLOC_USERS, LIMIT_USERS, true);
users_store = k_new_store(users_list);
users_root = new_ktree();
userid_root = new_ktree();
workers_list = k_new_list("Workers", sizeof(WORKERS), ALLOC_WORKERS, LIMIT_WORKERS, true);
workers_store = k_new_store(workers_list);
workers_root = new_ktree();
payments_list = k_new_list("Payments", sizeof(PAYMENTS), ALLOC_PAYMENTS, LIMIT_PAYMENTS, true);
payments_store = k_new_store(payments_list);
payments_root = new_ktree();
idcontrol_list = k_new_list("IDControl", sizeof(IDCONTROL), ALLOC_IDCONTROL, LIMIT_IDCONTROL, true);
idcontrol_store = k_new_store(idcontrol_list);
workinfo_list = k_new_list("WorkInfo", sizeof(WORKINFO), ALLOC_WORKINFO, LIMIT_WORKINFO, true);
workinfo_store = k_new_store(workinfo_list);
workinfo_root = new_ktree();
shares_list = k_new_list("Shares", sizeof(SHARES), ALLOC_SHARES, LIMIT_SHARES, true);
shares_store = k_new_store(shares_list);
shares_root = new_ktree();
shareerrors_list = k_new_list("Shareerrors", sizeof(SHAREERRORS), ALLOC_SHAREERRORS, LIMIT_SHAREERRORS, true);
shareerrors_store = k_new_store(shareerrors_list);
shareerrors_root = new_ktree();
auths_list = k_new_list("Auths", sizeof(AUTHS), ALLOC_AUTHS, LIMIT_AUTHS, true);
auths_store = k_new_store(auths_list);
auths_root = new_ktree();
getdata();
}
static char *cmd_adduser(char *id, tv_t *now, char *by, char *code, char *inet)
{
char reply[1024] = "";
size_t siz = sizeof(reply);
K_ITEM *i_username, *i_emailaddress, *i_passwordhash;
PGconn *conn;
bool ok;
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) {
STRNCPY(reply, "failed.DBE");
return strdup(reply);
}
LOGDEBUG("%s.added.%s", id, DATA_TRANSFER(i_username)->data);
snprintf(reply, siz, "added.%s", DATA_TRANSFER(i_username)->data);
return strdup(reply);
}
static char *cmd_chkpass(char *id, __maybe_unused tv_t *now, __maybe_unused char *by,
__maybe_unused char *code, __maybe_unused char *inet)
{
K_ITEM *i_username, *i_passwordhash, *u_item;
char reply[1024] = "";
size_t siz = sizeof(reply);
bool ok;
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)
return strdup("bad");
LOGDEBUG("%s.login.%s", id, DATA_TRANSFER(i_username)->data);
return strdup("ok");
}
static char *cmd_poolstats(char *id, __maybe_unused tv_t *now, __maybe_unused char *by,
__maybe_unused char *code, __maybe_unused char *inet)
{
char reply[1024] = "";
size_t siz = sizeof(reply);
LOGDEBUG("%s.stats.ok", id);
printf("%s.stats", id);
snprintf(reply, siz, "stats.ok");
return strdup(reply);
}
static char *cmd_newid(char *id, tv_t *now, char *by, char *code, char *inet)
{
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(): add", __func__);
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_list);
look = k_unlink_head(idcontrol_list);
K_WUNLOCK(idcontrol_list);
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_list);
k_add_head(idcontrol_list, look);
K_WUNLOCK(idcontrol_list);
if (!ok) {
snprintf(reply, siz, "failed.DBE");
return strdup(reply);
}
LOGDEBUG("%s.added.%s", id, DATA_TRANSFER(i_idname)->data);
snprintf(reply, siz, "added.%s", DATA_TRANSFER(i_idname)->data);
return strdup(reply);
}
static char *cmd_payments(char *id, __maybe_unused tv_t *now, __maybe_unused char *by,
__maybe_unused char *code, __maybe_unused char *inet)
{
K_ITEM *i_username, *look, *u_item, *p_item;
K_TREE_CTX ctx[1];
PAYMENTS *row;
char reply[1024] = "";
char tmp[1024];
size_t siz = sizeof(reply);
char *buf;
size_t len, off;
int rows;
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");
K_WLOCK(payments_list);
look = k_unlink_head(payments_list);
K_WUNLOCK(payments_list);
row = DATA_PAYMENTS(look);
row->userid = DATA_USERS(u_item)->userid;
row->paydate.tv_sec = 0;
row->paydate.tv_usec = 0;
p_item = find_after_in_ktree(payments_root, look, cmp_payments, ctx);
len = 1024;
buf = malloc(len);
if (!buf)
quithere(1, "malloc buf (%d) OOM", (int)len);
strcpy(buf, "ok.");
off = strlen(buf);
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);
K_WLOCK(payments_list);
k_add_head(payments_list, look);
K_WUNLOCK(payments_list);
LOGDEBUG("%s.payments.%s", id, DATA_TRANSFER(i_username)->data);
return buf;
}
static char *cmd_sharelog(char *id, tv_t *now, char *by, char *code, char *inet)
{
char reply[1024] = "";
size_t siz = sizeof(reply);
K_ITEM *i_method;
PGconn *conn;
// log to logfile with processing success/failure code
i_method = require_name("method", 1, NULL, reply, siz);
if (!i_method)
return strdup(reply);
if (strcasecmp(DATA_TRANSFER(i_method)->data, METHOD_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;
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", 1, NULL, reply, siz);
if (!i_transactiontree)
return strdup(reply);
i_merklehash = require_name("merklehash", 1, 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,
now, by, code, inet);
PQfinish(conn);
if (workinfoid == -1) {
STRNCPY(reply, "bad.DBE");
return strdup(reply);
}
LOGDEBUG("added.%s.%"PRId64, DATA_TRANSFER(i_method)->data, workinfoid);
snprintf(reply, siz, "%s.added.%"PRId64, id, workinfoid);
return strdup(reply);
} else if (strcasecmp(DATA_TRANSFER(i_method)->data, METHOD_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;
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,
now, by, code, inet);
if (!ok) {
STRNCPY(reply, "bad.DATA");
return strdup(reply);
}
LOGDEBUG("added.%s.%s", DATA_TRANSFER(i_method)->data,
DATA_TRANSFER(i_nonce)->data);
snprintf(reply, siz, "%s.added.%s", id, DATA_TRANSFER(i_nonce)->data);
return strdup(reply);
} else if (strcasecmp(DATA_TRANSFER(i_method)->data, METHOD_SHAREERRORS) == 0) {
K_ITEM *i_workinfoid, *i_username, *i_workername, *i_clientid, *i_errn;
K_ITEM *i_error, *i_secondaryuserid;
bool ok;
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("errno", 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,
now, by, code, inet);
if (!ok) {
STRNCPY(reply, "bad.DATA");
return strdup(reply);
}
LOGDEBUG("added.%s.%s", DATA_TRANSFER(i_method)->data,
DATA_TRANSFER(i_username)->data);
snprintf(reply, siz, "%s.added.%s", id, DATA_TRANSFER(i_username)->data);
return strdup(reply);
}
STRNCPY(reply, "bad.method");
return strdup(reply);
}
static char *cmd_auth(char *id, tv_t *now, char *by, char *code, char *inet)
{
char reply[1024] = "";
size_t siz = sizeof(reply);
K_ITEM *i_method;
PGconn *conn;
i_method = require_name("method", 1, NULL, reply, siz);
if (!i_method)
return strdup(reply);
if (strcasecmp(DATA_TRANSFER(i_method)->data, METHOD_AUTH) == 0) {
K_ITEM *i_username, *i_workername, *i_clientid, *i_enonce1, *i_useragent;
char *secuserid;
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", 1, NULL, reply, siz);
if (!i_useragent)
return strdup(reply);
conn = dbconnect();
secuserid = auths_add(conn, 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,
now, by, code, inet);
PQfinish(conn);
if (!secuserid) {
STRNCPY(reply, "bad.DBE");
return strdup(reply);
}
LOGDEBUG("added.%s.%s", DATA_TRANSFER(i_method)->data, secuserid);
snprintf(reply, siz, "%s.added.%s", id, secuserid);
return strdup(reply);
}
STRNCPY(reply, "bad.method");
return strdup(reply);
}
enum cmd_values {
CMD_UNSET,
CMD_REPLY, // Means something was wrong - send back reply
CMD_SHUTDOWN,
CMD_PING,
CMD_LOGSHARE,
CMD_AUTH,
CMD_ADDUSER,
CMD_CHKPASS,
CMD_POOLSTAT,
CMD_NEWID,
CMD_PAYMENTS,
CMD_END
};
#define ACCESS_POOL "p"
#define ACCESS_SYSTEM "s"
#define ACCESS_WEB "w"
#define ACCESS_PROXY "x"
static struct CMDS {
enum cmd_values cmd_val;
char *cmd_str;
char *(*func)(char *, tv_t *, char *, char *, char *);
char *access;
} cmds[] = {
{ CMD_SHUTDOWN, "shutdown", NULL, ACCESS_SYSTEM },
{ CMD_PING, "ping", NULL, ACCESS_SYSTEM ACCESS_WEB },
// Workinfo, Shares and Shareerrors
{ CMD_LOGSHARE, "sharelog", cmd_sharelog, ACCESS_POOL },
{ CMD_AUTH, "authorise", cmd_auth, ACCESS_POOL },
{ CMD_ADDUSER, "adduser", cmd_adduser, ACCESS_WEB },
{ CMD_CHKPASS, "chkpass", cmd_chkpass, ACCESS_WEB },
{ CMD_POOLSTAT, "poolstats", cmd_poolstats, ACCESS_WEB },
{ CMD_NEWID, "newid", cmd_newid, ACCESS_SYSTEM },
{ CMD_PAYMENTS, "payments", cmd_payments, ACCESS_WEB },
{ CMD_END, NULL, NULL, NULL }
};
// TODO: size limits?
static enum cmd_values breakdown(char *buf, int *which_cmds, char *id)
{
K_TREE_CTX ctx[1];
K_ITEM *item;
char *copy, *cmd, *data, *next, *eq;
*which_cmds = CMD_UNSET;
copy = strdup(buf);
cmd = strchr(copy, '.');
if (!cmd || !*cmd) {
STRNCPYSIZ(id, copy, ID_SIZ);
LOGINFO("Listener received invalid message: '%s'", buf);
free(copy);
return CMD_REPLY;
}
*(cmd++) = '\0';
STRNCPYSIZ(id, copy, ID_SIZ);
data = strchr(cmd, '.');
if (data)
*(data++) = '\0';
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) {
LOGINFO("Listener received unknown command: '%s'", buf);
free(copy);
return CMD_REPLY;
}
next = data;
if (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;
size_t siz;
next += JSON_TRANSFER_LEN;
json_data = json_loads(next, JSON_DISABLE_EOF_CHECK, &err_val);
if (!json_data) {
LOGINFO("Json decode error from command: '%s'", cmd);
free(copy);
return CMD_REPLY;
}
json_iter = json_object_iter(json_data);
K_WLOCK(transfer_list);
while (json_iter) {
json_key = json_object_iter_key(json_iter);
json_value = json_object_iter_value(json_iter);
if (json_is_string(json_value) ||
json_is_integer(json_value) ||
json_is_real(json_value) ||
json_is_array(json_value)) {
item = k_unlink_head(transfer_list);
STRNCPY(DATA_TRANSFER(item)->name, json_key);
if (json_is_string(json_value)) {
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;
}
} else if (json_is_integer(json_value)) {
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;
} else if (json_is_real(json_value)) {
snprintf(DATA_TRANSFER(item)->value,
sizeof(DATA_TRANSFER(item)->value),
"%f", json_real_value(json_value));
DATA_TRANSFER(item)->data = DATA_TRANSFER(item)->value;
} else {
/* 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;
len = 1024;
DATA_TRANSFER(item)->data = malloc(len);
if (!(DATA_TRANSFER(item)->data))
quithere(1, "malloc data (%d) OOM", (int)len);
off = 0;
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);
}
}
}
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_list, 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_list);
json_decref(json_data);
} else {
K_WLOCK(transfer_list);
while (next && *next) {
data = next;
next = strchr(data, 0x02);
if (next)
*(next++) = '\0';
eq = strchr(data, '=');
if (!eq)
eq = "";
else
*(eq++) = '\0';
item = k_unlink_head(transfer_list);
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_list, item);
} else {
transfer_root = add_to_ktree(transfer_root, item, cmp_transfer);
k_add_head(transfer_store, item);
}
}
K_WUNLOCK(transfer_list);
}
free(copy);
return cmds[*which_cmds].cmd_val;
}
// 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;
char id[ID_SIZ+1], reply[1024+1];
enum cmd_values cmd;
int sockd, which_cmds;
K_ITEM *item;
size_t siz;
tv_t now;
rename_proc(pi->sockname);
setup_data();
while (true) {
dealloc(buf);
sockd = accept(us->sockd, NULL, NULL);
if (sockd < 0) {
LOGERR("Failed to accept on socket in listener");
break;
}
cmd = 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 {
cmd = breakdown(buf, &which_cmds, id);
switch (cmd) {
case CMD_REPLY:
snprintf(reply, sizeof(reply), "%s.%ld.?", id, now.tv_sec);
send_unix_msg(sockd, reply);
break;
case CMD_SHUTDOWN:
LOGWARNING("Listener received shutdown message, terminating ckdb");
snprintf(reply, sizeof(reply), "%s.%d.exiting", id, (int)now.tv_sec);
send_unix_msg(sockd, reply);
break;
case CMD_PING:
LOGDEBUG("Listener received ping request");
snprintf(reply, sizeof(reply), "%s.%ld.pong", id, now.tv_sec);
send_unix_msg(sockd, reply);
break;
default:
// TODO: optionally get by/code/inet from transfer here instead?
ans = cmds[which_cmds].func(id, &now, (char *)"code",
(char *)__func__,
(char *)"127.0.0.1");
siz = strlen(ans) + strlen(id) + 32;
rep = malloc(siz);
snprintf(rep, siz, "%s.%ld.%s", id, now.tv_sec, ans);
free(ans);
ans = NULL;
send_unix_msg(sockd, rep);
free(rep);
rep = NULL;
break;
}
}
close(sockd);
if (cmd == CMD_SHUTDOWN)
break;
K_WLOCK(transfer_list);
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_list);
K_WUNLOCK(transfer_list);
}
dealloc(buf);
close_unix_socket(us->sockd, us->path);
return NULL;
}
int main(int argc, char **argv)
{
struct sigaction handler;
char buf[512];
ckpool_t ckp;
int c, ret;
char *kill;
memset(&ckp, 0, sizeof(ckp));
ckp.loglevel = LOG_NOTICE;
while ((c = getopt(argc, argv, "c:kl:n:p: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 '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;
}
}
// 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);
ckp.main.ckp = &ckp;
ckp.main.processname = strdup("main");
ckp.main.sockname = strdup("listener");
write_namepid(&ckp.main);
create_process_unixsock(&ckp.main);
srand((unsigned int)time(NULL));
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;
}