diff --git a/src/ckdb.c b/src/ckdb.c index b7c3a57f..6e9d13fb 100644 --- a/src/ckdb.c +++ b/src/ckdb.c @@ -53,6 +53,7 @@ #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; @@ -635,7 +636,7 @@ static K_STORE *optioncontrol_store; */ // TODO: aging/discarding workinfo,shares -// WORKINFO id.sharelog.json={...} +// WORKINFO workinfo.id.json={...} typedef struct workinfo { int64_t workinfoid; char poolinstance[TXT_BIG+1]; @@ -667,7 +668,7 @@ static K_ITEM *workinfo_current; // TODO: have it's own memory? static tv_t *last_bc; -// SHARES id.sharelog.json={...} +// SHARES shares.id.json={...} typedef struct shares { int64_t workinfoid; int64_t userid; @@ -692,7 +693,7 @@ static K_TREE *shares_root; static K_LIST *shares_list; static K_STORE *shares_store; -// SHAREERRORS id.sharelog.json={...} +// SHAREERRORS shareerrors.id.json={...} typedef struct shareerrorss { int64_t workinfoid; int64_t userid; @@ -771,8 +772,9 @@ typedef struct blocksummary { static K_TREE *blocksummary_root; static K_LIST *blocksummary_list; static K_STORE *blocksummary_store; +*/ -// BLOCKS +// BLOCKS block.id.json={...} typedef struct blocks { int32_t height; char blockhash[TXT_BIG+1]; @@ -788,14 +790,18 @@ typedef struct blocks { HISTORYDATECONTROLFIELDS; } BLOCKS; -#define ALLOC_BLOCKS 10000 +#define ALLOC_BLOCKS 100 #define LIMIT_BLOCKS 0 -#define DATA_BLOCKS ((BLOCKS *)(_item->data)) +#define DATA_BLOCKS(_item) ((BLOCKS *)(_item->data)) + +#define BLOCKS_NEW 'n' +#define BLOCKS_CONFIRM '1' static K_TREE *blocks_root; static K_LIST *blocks_list; static K_STORE *blocks_store; +/* // MININGPAYOUTS typedef struct miningpayouts { int64_t miningpayoutid; @@ -831,7 +837,7 @@ static K_LIST *eventlog_list; static K_STORE *eventlog_store; */ -// AUTHS +// AUTHS authorise.id.json={...} typedef struct auths { int64_t authid; int64_t userid; @@ -850,10 +856,7 @@ static K_TREE *auths_root; static K_LIST *auths_list; static K_STORE *auths_store; -// POOLSTATS -// TODO: get every 1m: pool sending it -// so web page is kept up to date - +// POOLSTATS poolstats.id.json={...} // Store every > 9.5m? #define STATS_PER (9.5*60.0) @@ -877,7 +880,7 @@ static K_TREE *poolstats_root; static K_LIST *poolstats_list; static K_STORE *poolstats_store; -// USERSTATS +// USERSTATS userstats.id.json={...} // Pool sends each user (staggered) once per 10m // TODO: When to discard? typedef struct userstats { @@ -1884,7 +1887,6 @@ static bool workers_update(PGconn *conn, K_ITEM *item, char *difficultydefault, rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Begin", rescode, conn); - PQclear(res); goto unparam; } PQclear(res); @@ -1893,9 +1895,8 @@ static bool workers_update(PGconn *conn, K_ITEM *item, char *difficultydefault, rescode = PQresultStatus(res); PQclear(res); if (!PGOK(rescode)) { - PGLOGERR("Insert", rescode, conn); + PGLOGERR("Update", rescode, conn); res = PQexec(conn, "Rollback"); - PQclear(res); goto unparam; } @@ -1929,16 +1930,15 @@ static bool workers_update(PGconn *conn, K_ITEM *item, char *difficultydefault, if (!PGOK(rescode)) { PGLOGERR("Insert", rescode, conn); res = PQexec(conn, "Rollback"); - PQclear(res); goto unparam; } res = PQexec(conn, "Commit"); - PQclear(res); } ok = true; unparam: + PQclear(res); for (n = 0; n < par; n++) free(params[n]); @@ -2260,8 +2260,8 @@ 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)); + c = tvdiff(&(DATA_WORKINFO(a)->expirydate), + &(DATA_WORKINFO(b)->expirydate)); } return c; } @@ -2305,8 +2305,8 @@ 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(b)->createdate), - &(DATA_WORKINFO(a)->createdate)); + c = tvdiff(&(DATA_WORKINFO(a)->createdate), + &(DATA_WORKINFO(b)->createdate)); } return c; } @@ -2745,6 +2745,317 @@ static bool shareerrors_fill() return true; } +// 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, + tv_t *now, char *by, char *code, char *inet) +{ + ExecStatusType rescode; + PGresult *res; + K_ITEM *item, *u_item; + BLOCKS *row; + char *upd, *ins; + char *params[11 + HISTORYDATECOUNT]; + bool ok = false; + int par; + int n; + + LOGDEBUG("%s(): add", __func__); + + K_WLOCK(blocks_list); + item = k_unlink_head(blocks_list); + K_WUNLOCK(blocks_list); + + row = DATA_BLOCKS(item); + + TXT_TO_INT("height", height, row->height); + STRNCPY(row->blockhash, blockhash); + STRNCPY(row->confirmed, confirmed); + + HISTORYDATEINIT(row, now, by, code, inet); + + switch (confirmed[0]) { + case BLOCKS_NEW: + u_item = find_users(username); + if (!u_item) + goto unparam; + + 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); + + 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: + upd = "update blocks set expirydate=$1 where blockhash=$2 and expirydate=$3"; + par = 0; + params[par++] = tv_to_buf(now, NULL, 0); + params[par++] = str_to_buf(row->blockhash, 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); + 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++] = int_to_buf(row->height, NULL, 0); + params[par++] = str_to_buf(row->blockhash, NULL, 0); + params[par++] = str_to_buf(row->confirmed, NULL, 0); + HISTORYDATEPARAMS(params, par, row); + + 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); + 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]); + + K_WLOCK(blocks_list); + if (!ok) + k_add_head(blocks_list, item); + else { + blocks_root = add_to_ktree(blocks_root, item, cmp_blocks); + k_add_head(blocks_store, item); + } + K_WUNLOCK(blocks_list); + + 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_list); + for (i = 0; i < n; i++) { + item = k_unlink_head(blocks_list); + 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 (!ok) + k_add_head(blocks_list, item); + + K_WUNLOCK(blocks_list); + PQclear(res); + + if (ok) + LOGDEBUG("%s(): built", __func__); + + return true; +} + +void blocks_reload() +{ + PGconn *conn = dbconnect(); + + K_WLOCK(blocks_list); + blocks_root = free_ktree(blocks_root, NULL); + k_list_transfer_to_head(blocks_store, blocks_list); + K_WUNLOCK(blocks_list); + + 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) { @@ -3452,6 +3763,10 @@ static void setup_data() shareerrors_store = k_new_store(shareerrors_list); shareerrors_root = new_ktree(); + blocks_list = k_new_list("Blocks", sizeof(BLOCKS), ALLOC_BLOCKS, LIMIT_BLOCKS, true); + blocks_store = k_new_store(blocks_list); + blocks_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(); @@ -3514,7 +3829,7 @@ static char *cmd_adduser(char *cmd, char *id, tv_t *now, char *by, char *code, c PQfinish(conn); if (!ok) { - LOGDEBUG("%s.failed.DBE", id); + LOGERR("%s.failed.DBE", id); return strdup("failed.DBE"); } LOGDEBUG("%s.ok.added %s", id, DATA_TRANSFER(i_username)->data); @@ -3552,7 +3867,7 @@ static char *cmd_chkpass(char *cmd, char *id, __maybe_unused tv_t *now, __maybe_ } if (!ok) { - LOGDEBUG("%s.failed.%s", id, DATA_TRANSFER(i_username)->data); + LOGERR("%s.failed.%s", id, DATA_TRANSFER(i_username)->data); return strdup("failed."); } LOGDEBUG("%s.ok.%s", id, DATA_TRANSFER(i_username)->data); @@ -3641,7 +3956,7 @@ static char *cmd_poolstats(char *cmd, __maybe_unused char *id, tv_t *now, char * PQfinish(conn); if (!ok) { - LOGDEBUG("%s.failed.DBE", id); + LOGERR("%s.failed.DBE", id); return strdup("failed.DBE"); } LOGDEBUG("%s.ok.", id); @@ -3712,7 +4027,7 @@ static char *cmd_userstats(char *cmd, __maybe_unused char *id, tv_t *now, char * eos, now, by, code, inet); if (!ok) { - LOGDEBUG("%s.failed.DATA", id); + LOGERR("%s.failed.DATA", id); return strdup("failed.DATA"); } LOGDEBUG("%s.ok.", id); @@ -3785,7 +4100,7 @@ foil: K_WUNLOCK(idcontrol_list); if (!ok) { - LOGDEBUG("%s.failed.DBE", id); + LOGERR("%s.failed.DBE", id); return strdup("failed.DBE"); } LOGDEBUG("%s.ok.added %s %"PRId64, id, DATA_TRANSFER(i_idname)->data, row->lastid); @@ -3935,7 +4250,7 @@ static char *cmd_sharelog(char *cmd, char *id, tv_t *now, char *by, char *code, PQfinish(conn); if (workinfoid == -1) { - LOGDEBUG("%s.failed.DBE", id); + LOGERR("%s.failed.DBE", id); return strdup("failed.DBE"); } LOGDEBUG("%s.ok.added %"PRId64, id, workinfoid); @@ -3999,7 +4314,7 @@ static char *cmd_sharelog(char *cmd, char *id, tv_t *now, char *by, char *code, now, by, code, inet); if (!ok) { - LOGDEBUG("%s.failed.DATA", id); + LOGERR("%s.failed.DATA", id); return strdup("failed.DATA"); } LOGDEBUG("%s.ok.added %s", id, DATA_TRANSFER(i_nonce)->data); @@ -4047,17 +4362,119 @@ static char *cmd_sharelog(char *cmd, char *id, tv_t *now, char *by, char *code, DATA_TRANSFER(i_secondaryuserid)->data, now, by, code, inet); if (!ok) { - LOGDEBUG("%s.failed.DATA", id); + 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); } - LOGDEBUG("%s.bad.cmd %s", cmd); + LOGERR("%s.bad.cmd %s", cmd); return strdup("bad.cmd"); } +static char *cmd_blocks(char *cmd, char *id, tv_t *now, char *by, char *code, char *inet) +{ + 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, + now, by, code, inet); + 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, + now, by, code, inet); + 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_auth(char *cmd, char *id, tv_t *now, char *by, char *code, char *inet) { char reply[1024] = ""; @@ -4281,7 +4698,7 @@ enum cmd_values { CMD_CHKPASS, CMD_POOLSTAT, CMD_USERSTAT, -// CMD_BLOCK, + CMD_BLOCK, CMD_NEWID, CMD_PAYMENTS, CMD_HOMEPAGE, @@ -4311,7 +4728,7 @@ static struct CMDS { { CMD_CHKPASS, "chkpass", cmd_chkpass, ACCESS_WEB }, { CMD_POOLSTAT, "poolstats", cmd_poolstats, ACCESS_POOL }, { CMD_USERSTAT, "userstats", cmd_userstats, ACCESS_POOL }, -// TODO { CMD_BLOCK, "block", cmd_block, ACCESS_POOL }, + { CMD_BLOCK, "block", cmd_blocks, ACCESS_POOL }, { CMD_NEWID, "newid", cmd_newid, ACCESS_SYSTEM }, { CMD_PAYMENTS, "payments", cmd_payments, ACCESS_WEB }, { CMD_HOMEPAGE, "homepage", cmd_homepage, ACCESS_WEB },