/* * Copyright 1995-2015 Andrew Smith * * 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 "ckdb.h" static char *cmd_adduser(PGconn *conn, char *cmd, char *id, tv_t *now, char *by, char *code, char *inet, __maybe_unused tv_t *notcd, K_TREE *trf_root) { char reply[1024] = ""; size_t siz = sizeof(reply); K_ITEM *i_username, *i_emailaddress, *i_passwordhash, *u_item = NULL; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz); if (!i_username) return strdup(reply); /* If a username added from the web site looks like an address * then disallow it - a false positive is not an issue * Allowing it will create a security issue - someone could create * an account with someone else's, as yet unused, payout address * and redirect the payout to another payout address. * ... and the person who owns the payout address can't check that * in advance, they'll just find out with their first payout not * arriving at their payout address */ if (!like_address(transfer_data(i_username))) { i_emailaddress = require_name(trf_root, "emailaddress", 7, (char *)mailpatt, reply, siz); if (!i_emailaddress) return strdup(reply); i_passwordhash = require_name(trf_root, "passwordhash", 64, (char *)hashpatt, reply, siz); if (!i_passwordhash) return strdup(reply); u_item = users_add(conn, transfer_data(i_username), transfer_data(i_emailaddress), transfer_data(i_passwordhash), by, code, inet, now, trf_root); } if (!u_item) { LOGERR("%s() %s.failed.DBE", __func__, id); return strdup("failed.DBE"); } LOGDEBUG("%s.ok.added %s", id, transfer_data(i_username)); snprintf(reply, siz, "ok.added %s", transfer_data(i_username)); return strdup(reply); } static char *cmd_newpass(__maybe_unused PGconn *conn, char *cmd, char *id, tv_t *now, char *by, char *code, char *inet, __maybe_unused tv_t *cd, K_TREE *trf_root) { K_ITEM *i_username, *i_oldhash, *i_newhash, *u_item; char reply[1024] = ""; size_t siz = sizeof(reply); bool ok = true; char *oldhash; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz); if (!i_username) return strdup(reply); i_oldhash = optional_name(trf_root, "oldhash", 64, (char *)hashpatt, reply, siz); if (i_oldhash) oldhash = transfer_data(i_oldhash); else { // fail if the oldhash is invalid if (*reply) ok = false; oldhash = EMPTY; } if (ok) { i_newhash = require_name(trf_root, "newhash", 64, (char *)hashpatt, reply, siz); if (!i_newhash) return strdup(reply); K_RLOCK(users_free); u_item = find_users(transfer_data(i_username)); K_RUNLOCK(users_free); if (u_item) { ok = users_update(NULL, u_item, oldhash, transfer_data(i_newhash), NULL, by, code, inet, now, trf_root, NULL); } else ok = false; } if (!ok) { LOGERR("%s.failed.%s", id, transfer_data(i_username)); return strdup("failed."); } LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); return strdup("ok."); } static char *cmd_chkpass(__maybe_unused PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, __maybe_unused tv_t *notcd, K_TREE *trf_root) { K_ITEM *i_username, *i_passwordhash, *u_item; char reply[1024] = ""; size_t siz = sizeof(reply); USERS *users; bool ok; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz); if (!i_username) return strdup(reply); i_passwordhash = require_name(trf_root, "passwordhash", 64, (char *)hashpatt, reply, siz); if (!i_passwordhash) return strdup(reply); K_RLOCK(users_free); u_item = find_users(transfer_data(i_username)); K_RUNLOCK(users_free); if (!u_item) ok = false; else { DATA_USERS(users, u_item); ok = check_hash(users, transfer_data(i_passwordhash)); } if (!ok) { LOGERR("%s.failed.%s", id, transfer_data(i_username)); return strdup("failed."); } LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); return strdup("ok."); } static char *cmd_userset(PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, __maybe_unused tv_t *notcd, K_TREE *trf_root) { K_ITEM *i_username, *i_passwordhash, *i_rows, *i_address, *i_ratio; K_ITEM *i_email, *u_item, *pa_item, *old_pa_item; char *email, *address; char reply[1024] = ""; size_t siz = sizeof(reply); char tmp[1024]; PAYMENTADDRESSES *row, *pa; K_STORE *pa_store = NULL; K_TREE_CTX ctx[1]; USERS *users; char *reason = NULL; char *answer = NULL; char *ret = NULL; size_t len, off; int32_t ratio; int rows, i; bool ok; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz); if (!i_username) { // For web this message is detailed enough reason = "System error"; goto struckout; } K_RLOCK(users_free); u_item = find_users(transfer_data(i_username)); K_RUNLOCK(users_free); if (!u_item) { reason = "Unknown user"; goto struckout; } else { DATA_USERS(users, u_item); i_passwordhash = optional_name(trf_root, "passwordhash", 64, (char *)hashpatt, reply, siz); if (*reply) { reason = "Invalid data"; goto struckout; } if (!i_passwordhash) { APPEND_REALLOC_INIT(answer, off, len); snprintf(tmp, sizeof(tmp), "email=%s%c", users->emailaddress, FLDSEP); APPEND_REALLOC(answer, off, len, tmp); K_RLOCK(paymentaddresses_free); pa_item = find_paymentaddresses(users->userid, ctx); rows = 0; if (pa_item) { DATA_PAYMENTADDRESSES(row, pa_item); while (pa_item && CURRENT(&(row->expirydate)) && row->userid == users->userid) { snprintf(tmp, sizeof(tmp), "addr:%d=%s%c", rows, row->payaddress, FLDSEP); APPEND_REALLOC(answer, off, len, tmp); snprintf(tmp, sizeof(tmp), "ratio:%d=%d%c", rows, row->payratio, FLDSEP); APPEND_REALLOC(answer, off, len, tmp); rows++; pa_item = prev_in_ktree(ctx); DATA_PAYMENTADDRESSES_NULL(row, pa_item); } } K_RUNLOCK(paymentaddresses_free); snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "addr,ratio", FLDSEP); APPEND_REALLOC(answer, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "PaymentAddresses", FLDSEP, ""); APPEND_REALLOC(answer, off, len, tmp); } else { if (!check_hash(users, transfer_data(i_passwordhash))) { reason = "Incorrect password"; goto struckout; } i_email = optional_name(trf_root, "email", 1, (char *)mailpatt, reply, siz); if (i_email) email = transfer_data(i_email); else { if (*reply) { reason = "Invalid email"; goto struckout; } email = NULL; } // address rows i_rows = optional_name(trf_root, "rows", 1, (char *)intpatt, reply, siz); if (!i_rows && *reply) { // Exists, but invalid reason = "System error"; goto struckout; } if (i_rows) { rows = atoi(transfer_data(i_rows)); if (rows < 0) { reason = "System error"; goto struckout; } if (rows > 0) { pa_store = k_new_store(paymentaddresses_free); K_WLOCK(paymentaddresses_free); for (i = 0; i < rows; i++) { snprintf(tmp, sizeof(tmp), "ratio:%d", i); i_ratio = optional_name(trf_root, tmp, 1, (char *)intpatt, reply, siz); if (*reply) { K_WUNLOCK(paymentaddresses_free); reason = "Invalid ratio"; goto struckout; } if (i_ratio) ratio = atoi(transfer_data(i_ratio)); else ratio = PAYRATIODEF; /* 0 = expire/remove the address * intpatt means it will be >= 0 */ if (ratio == 0) continue; snprintf(tmp, sizeof(tmp), "address:%d", i); i_address = require_name(trf_root, tmp, ADDR_MIN_LEN, (char *)addrpatt, reply, siz); if (!i_address) { K_WUNLOCK(paymentaddresses_free); reason = "Invalid address"; goto struckout; } address = transfer_data(i_address); pa_item = pa_store->head; while (pa_item) { DATA_PAYMENTADDRESSES(row, pa_item); if (strcmp(row->payaddress, address) == 0) { K_WUNLOCK(paymentaddresses_free); reason = "Duplicate address"; goto struckout; } pa_item = pa_item->next; } pa_item = k_unlink_head(paymentaddresses_free); DATA_PAYMENTADDRESSES(row, pa_item); bzero(row, sizeof(*row)); STRNCPY(row->payaddress, address); row->payratio = ratio; k_add_head(pa_store, pa_item); } K_WUNLOCK(paymentaddresses_free); } } /* If all addresses have a ratio of zero * pa_store->count will be 0 */ if ((email == NULL || *email == '\0') && (pa_store == NULL || pa_store->count == 0)) { reason = "Missing/Invalid value"; goto struckout; } if (pa_store && pa_store->count > 0) { pa_item = pa_store->head; while (pa_item) { DATA_PAYMENTADDRESSES(row, pa_item); // Only EVER validate addresses once ... for now old_pa_item = find_any_payaddress(row->payaddress); if (old_pa_item) { /* This test effectively means that * two users can never add the same * payout address */ DATA_PAYMENTADDRESSES(pa, old_pa_item); if (pa->userid != users->userid) { reason = "Unavailable BTC address"; goto struckout; } } else if (!btc_valid_address(row->payaddress)) { reason = "Invalid BTC address"; goto struckout; } pa_item = pa_item->next; } } if (email && *email) { ok = users_update(conn, u_item, NULL, NULL, email, by, code, inet, now, trf_root, NULL); if (!ok) { reason = "email error"; goto struckout; } } if (pa_store && pa_store->count > 0) { ok = paymentaddresses_set(conn, users->userid, pa_store, by, code, inet, now, trf_root); if (!ok) { reason = "address error"; goto struckout; } } answer = strdup("updated"); } } struckout: if (pa_store) { if (pa_store->count) { K_WLOCK(paymentaddresses_free); k_list_transfer_to_head(pa_store, paymentaddresses_free); K_WUNLOCK(paymentaddresses_free); } k_free_store(pa_store); pa_store = NULL; } if (reason) { snprintf(reply, siz, "ERR.%s", reason); LOGERR("%s.%s.%s", cmd, id, reply); return strdup(reply); } APPEND_REALLOC_INIT(ret, off, len); APPEND_REALLOC(ret, off, len, "ok."); APPEND_REALLOC(ret, off, len, answer); free(answer); LOGDEBUG("%s.%s", id, ret); return ret; } static char *cmd_workerset(PGconn *conn, char *cmd, char *id, tv_t *now, char *by, char *code, char *inet, __maybe_unused tv_t *notcd, K_TREE *trf_root) { K_ITEM *i_username, *i_workername, *i_diffdef, *u_item, *w_item; HEARTBEATQUEUE *heartbeatqueue; K_ITEM *hq_item; char workername_buf[32]; // 'workername:' + digits char diffdef_buf[32]; // 'difficultydefault:' + digits char reply[1024] = ""; size_t siz = sizeof(reply); WORKERS *workers; USERS *users; int32_t difficultydefault; char *reason = NULL; char *answer = NULL; int workernum; bool ok; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz); if (!i_username) { // For web this message is detailed enough reason = "System error"; goto struckout; } K_RLOCK(users_free); u_item = find_users(transfer_data(i_username)); K_RUNLOCK(users_free); if (!u_item) { reason = "Unknown user"; goto struckout; } else { DATA_USERS(users, u_item); // Default answer if no problems answer = strdup("updated"); // Loop through the list of workers and do any changes for (workernum = 0; workernum < 9999; workernum++) { snprintf(workername_buf, sizeof(workername_buf), "workername:%d", workernum); i_workername = optional_name(trf_root, workername_buf, 1, NULL, reply, siz); if (!i_workername) break; w_item = find_workers(users->userid, transfer_data(i_workername)); // Abort if any dont exist if (!w_item) { reason = "Unknown worker"; break; } DATA_WORKERS(workers, w_item); snprintf(diffdef_buf, sizeof(diffdef_buf), "difficultydefault:%d", workernum); i_diffdef = optional_name(trf_root, diffdef_buf, 1, (char *)intpatt, reply, siz); // Abort if any are invalid if (*reply) { reason = "Invalid diff"; break; } if (!i_diffdef) continue; difficultydefault = atoi(transfer_data(i_diffdef)); if (difficultydefault != 0) { if (difficultydefault < DIFFICULTYDEFAULT_MIN) difficultydefault = DIFFICULTYDEFAULT_MIN; if (difficultydefault > DIFFICULTYDEFAULT_MAX) difficultydefault = DIFFICULTYDEFAULT_MAX; } if (workers->difficultydefault != difficultydefault) { /* This uses a seperate txn per update thus will update all up to a failure Since the web then re-gets the values, it will show what was updated */ workers->difficultydefault = difficultydefault; ok = workers_update(conn, w_item, NULL, NULL, NULL, by, code, inet, now, trf_root, false); if (!ok) { reason = "DB error"; break; } /* workerset is not from a log file, so always queue it */ K_WLOCK(heartbeatqueue_free); hq_item = k_unlink_head(heartbeatqueue_free); K_WUNLOCK(heartbeatqueue_free); DATA_HEARTBEATQUEUE(heartbeatqueue, hq_item); STRNCPY(heartbeatqueue->workername, workers->workername); heartbeatqueue->difficultydefault = workers->difficultydefault; copy_tv(&(heartbeatqueue->createdate), now); K_WLOCK(heartbeatqueue_free); k_add_tail(heartbeatqueue_store, hq_item); K_WUNLOCK(heartbeatqueue_free); } } } struckout: if (reason) { if (answer) free(answer); snprintf(reply, siz, "ERR.%s", reason); LOGERR("%s.%s.%s", cmd, id, reply); return strdup(reply); } snprintf(reply, siz, "ok.%s", answer); LOGDEBUG("%s.%s", id, answer); free(answer); return strdup(reply); } static char *cmd_poolstats_do(PGconn *conn, char *cmd, char *id, char *by, char *code, char *inet, tv_t *cd, bool igndup, K_TREE *trf_root) { char reply[1024] = ""; size_t siz = sizeof(reply); K_TREE_CTX ctx[1]; bool store; // log to logfile K_ITEM *i_poolinstance, *i_elapsed, *i_users, *i_workers; K_ITEM *i_hashrate, *i_hashrate5m, *i_hashrate1hr, *i_hashrate24hr; K_ITEM look, *ps; POOLSTATS row, *poolstats; bool ok = false; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) return strdup(reply); i_elapsed = optional_name(trf_root, "elapsed", 1, NULL, reply, siz); if (!i_elapsed) i_elapsed = &poolstats_elapsed; i_users = require_name(trf_root, "users", 1, NULL, reply, siz); if (!i_users) return strdup(reply); i_workers = require_name(trf_root, "workers", 1, NULL, reply, siz); if (!i_workers) return strdup(reply); i_hashrate = require_name(trf_root, "hashrate", 1, NULL, reply, siz); if (!i_hashrate) return strdup(reply); i_hashrate5m = require_name(trf_root, "hashrate5m", 1, NULL, reply, siz); if (!i_hashrate5m) return strdup(reply); i_hashrate1hr = require_name(trf_root, "hashrate1hr", 1, NULL, reply, siz); if (!i_hashrate1hr) return strdup(reply); i_hashrate24hr = require_name(trf_root, "hashrate24hr", 1, NULL, reply, siz); if (!i_hashrate24hr) return strdup(reply); STRNCPY(row.poolinstance, transfer_data(i_poolinstance)); row.createdate.tv_sec = date_eot.tv_sec; row.createdate.tv_usec = date_eot.tv_usec; INIT_POOLSTATS(&look); look.data = (void *)(&row); ps = find_before_in_ktree(poolstats_root, &look, cmp_poolstats, ctx); if (!ps) store = true; else { DATA_POOLSTATS(poolstats, ps); // Find last stored matching the poolinstance and less than STATS_PER old while (ps && !poolstats->stored && strcmp(row.poolinstance, poolstats->poolinstance) == 0 && tvdiff(cd, &(poolstats->createdate)) < STATS_PER) { ps = prev_in_ktree(ctx); DATA_POOLSTATS_NULL(poolstats, ps); } if (!ps || !poolstats->stored || strcmp(row.poolinstance, poolstats->poolinstance) != 0 || tvdiff(cd, &(poolstats->createdate)) >= STATS_PER) store = true; else store = false; } ok = poolstats_add(conn, store, transfer_data(i_poolinstance), transfer_data(i_elapsed), transfer_data(i_users), transfer_data(i_workers), transfer_data(i_hashrate), transfer_data(i_hashrate5m), transfer_data(i_hashrate1hr), transfer_data(i_hashrate24hr), by, code, inet, cd, igndup, trf_root); if (!ok) { LOGERR("%s() %s.failed.DBE", __func__, id); return strdup("failed.DBE"); } LOGDEBUG("%s.ok.", id); snprintf(reply, siz, "ok."); return strdup(reply); } static char *cmd_poolstats(PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *notnow, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root) { bool igndup = false; // confirm_summaries() doesn't call this if (reloading) { if (tv_equal(cd, &(dbstatus.newest_createdate_blocks))) igndup = true; else if (tv_newer(cd, &(dbstatus.newest_createdate_blocks))) return NULL; } return cmd_poolstats_do(conn, cmd, id, by, code, inet, cd, igndup, trf_root); } static char *cmd_userstats(__maybe_unused PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *notnow, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root) { char reply[1024] = ""; size_t siz = sizeof(reply); // log to logfile K_ITEM *i_poolinstance, *i_elapsed, *i_username, *i_workername; K_ITEM *i_hashrate, *i_hashrate5m, *i_hashrate1hr, *i_hashrate24hr; K_ITEM *i_eos, *i_idle; bool ok = false, idle, eos; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) return strdup(reply); i_elapsed = optional_name(trf_root, "elapsed", 1, NULL, reply, siz); if (!i_elapsed) i_elapsed = &userstats_elapsed; i_username = require_name(trf_root, "username", 1, NULL, reply, siz); if (!i_username) return strdup(reply); i_workername = optional_name(trf_root, "workername", 1, NULL, reply, siz); if (!i_workername) i_workername = &userstats_workername; i_hashrate = require_name(trf_root, "hashrate", 1, NULL, reply, siz); if (!i_hashrate) return strdup(reply); i_hashrate5m = require_name(trf_root, "hashrate5m", 1, NULL, reply, siz); if (!i_hashrate5m) return strdup(reply); i_hashrate1hr = require_name(trf_root, "hashrate1hr", 1, NULL, reply, siz); if (!i_hashrate1hr) return strdup(reply); i_hashrate24hr = require_name(trf_root, "hashrate24hr", 1, NULL, reply, siz); if (!i_hashrate24hr) return strdup(reply); i_idle = optional_name(trf_root, "idle", 1, NULL, reply, siz); if (!i_idle) i_idle = &userstats_idle; idle = (strcasecmp(transfer_data(i_idle), TRUE_STR) == 0); i_eos = optional_name(trf_root, "eos", 1, NULL, reply, siz); if (!i_eos) i_eos = &userstats_eos; eos = (strcasecmp(transfer_data(i_eos), TRUE_STR) == 0); ok = userstats_add(transfer_data(i_poolinstance), transfer_data(i_elapsed), transfer_data(i_username), transfer_data(i_workername), transfer_data(i_hashrate), transfer_data(i_hashrate5m), transfer_data(i_hashrate1hr), transfer_data(i_hashrate24hr), idle, eos, by, code, inet, cd, trf_root); if (!ok) { LOGERR("%s() %s.failed.DATA", __func__, id); return strdup("failed.DATA"); } LOGDEBUG("%s.ok.", id); snprintf(reply, siz, "ok."); return strdup(reply); } static char *cmd_workerstats(__maybe_unused PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *notnow, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root) { char reply[1024] = ""; size_t siz = sizeof(reply); // log to logfile K_ITEM *i_poolinstance, *i_elapsed, *i_username, *i_workername; K_ITEM *i_hashrate, *i_hashrate5m, *i_hashrate1hr, *i_hashrate24hr; K_ITEM *i_idle; bool ok = false, idle; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) return strdup(reply); i_elapsed = require_name(trf_root, "elapsed", 1, NULL, reply, siz); if (!i_elapsed) return strdup(reply); i_username = require_name(trf_root, "username", 1, NULL, reply, siz); if (!i_username) return strdup(reply); i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz); if (!i_workername) return strdup(reply); i_hashrate = require_name(trf_root, "hashrate", 1, NULL, reply, siz); if (!i_hashrate) return strdup(reply); i_hashrate5m = require_name(trf_root, "hashrate5m", 1, NULL, reply, siz); if (!i_hashrate5m) return strdup(reply); i_hashrate1hr = require_name(trf_root, "hashrate1hr", 1, NULL, reply, siz); if (!i_hashrate1hr) return strdup(reply); i_hashrate24hr = require_name(trf_root, "hashrate24hr", 1, NULL, reply, siz); if (!i_hashrate24hr) return strdup(reply); i_idle = require_name(trf_root, "idle", 1, NULL, reply, siz); if (!i_idle) return strdup(reply); idle = (strcasecmp(transfer_data(i_idle), TRUE_STR) == 0); ok = workerstats_add(transfer_data(i_poolinstance), transfer_data(i_elapsed), transfer_data(i_username), transfer_data(i_workername), transfer_data(i_hashrate), transfer_data(i_hashrate5m), transfer_data(i_hashrate1hr), transfer_data(i_hashrate24hr), idle, by, code, inet, cd, trf_root); if (!ok) { LOGERR("%s() %s.failed.DATA", __func__, id); return strdup("failed.DATA"); } LOGDEBUG("%s.ok.", id); snprintf(reply, siz, "ok."); return strdup(reply); } static char *cmd_blocklist(__maybe_unused PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, __maybe_unused tv_t *notcd, __maybe_unused K_TREE *trf_root) { K_TREE_CTX ctx[1]; K_ITEM *b_item, *w_item; BLOCKS *blocks; char reply[1024] = ""; char tmp[1024]; char *buf; size_t len, off; int32_t height = -1; tv_t first_cd = {0,0}; int rows; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); rows = 0; K_RLOCK(blocks_free); b_item = last_in_ktree(blocks_root, ctx); while (b_item && rows < 42) { DATA_BLOCKS(blocks, b_item); if (height != blocks->height) { height = blocks->height; copy_tv(&first_cd, &(blocks->createdate)); } if (CURRENT(&(blocks->expirydate))) { int_to_buf(blocks->height, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "height:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); dbhash2btchash(blocks->blockhash, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "blockhash:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); str_to_buf(blocks->nonce, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "nonce:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(blocks->reward, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "reward:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); str_to_buf(blocks->workername, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "workername:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "firstcreatedate:%d=%ld%c", rows, first_cd.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "createdate:%d=%ld%c", rows, blocks->createdate.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "status:%d=%s%c", rows, blocks_confirmed(blocks->confirmed), FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(blocks->diffacc, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "diffacc:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(blocks->diffinv, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "diffinv:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(blocks->shareacc, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "shareacc:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(blocks->shareinv, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "shareinv:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(blocks->elapsed, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "elapsed:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); w_item = find_workinfo(blocks->workinfoid, NULL); if (w_item) { char wdiffbin[TXT_SML+1]; double wdiff; WORKINFO *workinfo; DATA_WORKINFO(workinfo, w_item); hex2bin(wdiffbin, workinfo->bits, 4); wdiff = diff_from_nbits(wdiffbin); snprintf(tmp, sizeof(tmp), "netdiff:%d=%.1f%c", rows, wdiff, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } else { snprintf(tmp, sizeof(tmp), "netdiff:%d=?%c", rows, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } rows++; } b_item = prev_in_ktree(ctx); } K_RUNLOCK(blocks_free); snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "height,blockhash,nonce,reward,workername,firstcreatedate," "createdate,status,diffacc,diffinv,shareacc,shareinv,elapsed," "netdiff", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Blocks", FLDSEP, ""); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.ok.%d_blocks", id, rows); return buf; } static char *cmd_blockstatus(__maybe_unused PGconn *conn, char *cmd, char *id, tv_t *now, char *by, char *code, char *inet, __maybe_unused tv_t *cd, K_TREE *trf_root) { K_ITEM *i_height, *i_blockhash, *i_action; char reply[1024] = ""; size_t siz = sizeof(reply); K_ITEM *b_item; BLOCKS *blocks; int32_t height; char *action; bool ok = false; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_height = require_name(trf_root, "height", 1, NULL, reply, siz); if (!i_height) return strdup(reply); TXT_TO_INT("height", transfer_data(i_height), height); i_blockhash = require_name(trf_root, "blockhash", 1, NULL, reply, siz); if (!i_blockhash) return strdup(reply); i_action = require_name(trf_root, "action", 1, NULL, reply, siz); if (!i_action) return strdup(reply); action = transfer_data(i_action); K_RLOCK(blocks_free); b_item = find_blocks(height, transfer_data(i_blockhash)); K_RUNLOCK(blocks_free); if (!b_item) { snprintf(reply, siz, "ERR.unknown block"); LOGERR("%s.%s", id, reply); return strdup(reply); } DATA_BLOCKS(blocks, b_item); if (strcasecmp(action, "orphan") == 0) { switch (blocks->confirmed[0]) { case BLOCKS_NEW: case BLOCKS_CONFIRM: ok = blocks_add(conn, transfer_data(i_height), blocks->blockhash, BLOCKS_ORPHAN_STR, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, by, code, inet, now, false, id, trf_root); if (!ok) { snprintf(reply, siz, "DBE.action '%s'", action); LOGERR("%s.%s", id, reply); return strdup(reply); } // TODO: reset the share counter? break; default: snprintf(reply, siz, "ERR.invalid action '%.*s%s' for block state '%s'", CMD_SIZ, action, (strlen(action) > CMD_SIZ) ? "..." : "", blocks_confirmed(blocks->confirmed)); LOGERR("%s.%s", id, reply); return strdup(reply); } } else if (strcasecmp(action, "confirm") == 0) { // Confirm a new block that wasn't confirmed due to some bug switch (blocks->confirmed[0]) { case BLOCKS_NEW: ok = blocks_add(conn, transfer_data(i_height), blocks->blockhash, BLOCKS_CONFIRM_STR, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, by, code, inet, now, false, id, trf_root); if (!ok) { snprintf(reply, siz, "DBE.action '%s'", action); LOGERR("%s.%s", id, reply); return strdup(reply); } // TODO: reset the share counter? break; default: snprintf(reply, siz, "ERR.invalid action '%.*s%s' for block state '%s'", CMD_SIZ, action, (strlen(action) > CMD_SIZ) ? "..." : "", blocks_confirmed(blocks->confirmed)); LOGERR("%s.%s", id, reply); return strdup(reply); } } else { snprintf(reply, siz, "ERR.unknown action '%s'", transfer_data(i_action)); LOGERR("%s.%s", id, reply); return strdup(reply); } snprintf(reply, siz, "ok.%s %d", transfer_data(i_action), height); LOGDEBUG("%s.%s", id, reply); return strdup(reply); } static char *cmd_newid(PGconn *conn, char *cmd, char *id, tv_t *now, char *by, char *code, char *inet, __maybe_unused tv_t *cd, K_TREE *trf_root) { char reply[1024] = ""; size_t siz = sizeof(reply); K_ITEM *i_idname, *i_idvalue; bool ok; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_idname = require_name(trf_root, "idname", 3, (char *)idpatt, reply, siz); if (!i_idname) return strdup(reply); i_idvalue = require_name(trf_root, "idvalue", 1, (char *)intpatt, reply, siz); if (!i_idvalue) return strdup(reply); ok = idcontrol_add(conn, transfer_data(i_idname), transfer_data(i_idvalue), by, code, inet, now, trf_root); if (!ok) { LOGERR("%s() %s.failed.DBE", __func__, id); return strdup("failed.DBE"); } snprintf(reply, siz, "ok.added %s %s", transfer_data(i_idname), transfer_data(i_idvalue)); LOGDEBUG("%s.%s", id, reply); return strdup(reply); } static char *cmd_payments(__maybe_unused PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, __maybe_unused tv_t *notcd, __maybe_unused K_TREE *trf_root) { K_ITEM *i_username, look, *u_item, *p_item; K_TREE_CTX ctx[1]; PAYMENTS lookpayments, *payments; USERS *users; char reply[1024] = ""; char tmp[1024]; size_t siz = sizeof(reply); char *buf; size_t len, off; int rows; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz); if (!i_username) return strdup(reply); K_RLOCK(users_free); u_item = find_users(transfer_data(i_username)); K_RUNLOCK(users_free); if (!u_item) return strdup("bad"); DATA_USERS(users, u_item); lookpayments.userid = users->userid; lookpayments.paydate.tv_sec = 0; lookpayments.paydate.tv_usec = 0; INIT_PAYMENTS(&look); look.data = (void *)(&lookpayments); p_item = find_after_in_ktree(payments_root, &look, cmp_payments, ctx); DATA_PAYMENTS_NULL(payments, p_item); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); rows = 0; while (p_item && payments->userid == users->userid) { tv_to_buf(&(payments->paydate), reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "paydate:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); str_to_buf(payments->payaddress, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "payaddress:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(payments->amount, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "amount:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; p_item = next_in_ktree(ctx); DATA_PAYMENTS_NULL(payments, p_item); } snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "paydate,payaddress,amount", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Payments", FLDSEP, ""); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); return buf; } static char *cmd_percent(char *cmd, char *id, tv_t *now, USERS *users) { K_ITEM w_look, *w_item, us_look, *us_item, *ws_item; K_TREE_CTX w_ctx[1], us_ctx[1], pay_ctx[1]; WORKERS lookworkers, *workers; WORKERSTATUS *workerstatus; USERSTATS lookuserstats, *userstats; char tmp[1024]; char *buf; size_t len, off; int rows; K_TREE *userstats_workername_root = new_ktree(); K_TREE_CTX usw_ctx[1]; double t_hashrate5m = 0, t_hashrate1hr = 0; double t_hashrate24hr = 0; double t_diffacc = 0, t_diffinv = 0; double t_diffsta = 0, t_diffdup = 0; double t_diffhi = 0, t_diffrej = 0; double t_shareacc = 0, t_shareinv = 0; double t_sharesta = 0, t_sharedup = 0; double t_sharehi = 0, t_sharerej = 0; K_ITEM *pa_item; PAYMENTADDRESSES *pa; int64_t paytotal; double ratio; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); snprintf(tmp, sizeof(tmp), "blockacc=%.1f%c", pool.diffacc, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "blockreward=%"PRId64"%c", pool.reward, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); INIT_WORKERS(&w_look); INIT_USERSTATS(&us_look); // Add up all user's worker stats to be divided into payout percentages lookworkers.userid = users->userid; lookworkers.workername[0] = '\0'; lookworkers.expirydate.tv_sec = 0; lookworkers.expirydate.tv_usec = 0; w_look.data = (void *)(&lookworkers); w_item = find_after_in_ktree(workers_root, &w_look, cmp_workers, w_ctx); DATA_WORKERS_NULL(workers, w_item); while (w_item && workers->userid == users->userid) { if (CURRENT(&(workers->expirydate))) { ws_item = find_workerstatus(users->userid, workers->workername, __FILE__, __func__, __LINE__); if (ws_item) { DATA_WORKERSTATUS(workerstatus, ws_item); t_diffacc += workerstatus->diffacc; t_diffinv += workerstatus->diffinv; t_diffsta += workerstatus->diffsta; t_diffdup += workerstatus->diffdup; t_diffhi += workerstatus->diffhi; t_diffrej += workerstatus->diffrej; t_shareacc += workerstatus->shareacc; t_shareinv += workerstatus->shareinv; t_sharesta += workerstatus->sharesta; t_sharedup += workerstatus->sharedup; t_sharehi += workerstatus->sharehi; t_sharerej += workerstatus->sharerej; } // find last stored userstats record lookuserstats.userid = users->userid; lookuserstats.statsdate.tv_sec = date_eot.tv_sec; lookuserstats.statsdate.tv_usec = date_eot.tv_usec; // find/cmp doesn't get to here lookuserstats.poolinstance[0] = '\0'; lookuserstats.workername[0] = '\0'; us_look.data = (void *)(&lookuserstats); K_RLOCK(userstats_free); us_item = find_before_in_ktree(userstats_root, &us_look, cmp_userstats, us_ctx); DATA_USERSTATS_NULL(userstats, us_item); while (us_item && userstats->userid == lookuserstats.userid) { if (strcmp(userstats->workername, workers->workername) == 0) { if (tvdiff(now, &(userstats->statsdate)) < USERSTATS_PER_S) { if (!find_in_ktree(userstats_workername_root, us_item, cmp_userstats_workername, usw_ctx)) { t_hashrate5m += userstats->hashrate5m; t_hashrate1hr += userstats->hashrate1hr; t_hashrate24hr += userstats->hashrate24hr; userstats_workername_root = add_to_ktree(userstats_workername_root, us_item, cmp_userstats_workername); } } else break; } us_item = prev_in_ktree(us_ctx); DATA_USERSTATS_NULL(userstats, us_item); } K_RUNLOCK(userstats_free); } w_item = next_in_ktree(w_ctx); DATA_WORKERS_NULL(workers, w_item); } userstats_workername_root = free_ktree(userstats_workername_root, NULL); // Calculate total payratio paytotal = 0; K_RLOCK(paymentaddresses_free); pa_item = find_paymentaddresses(users->userid, pay_ctx); DATA_PAYMENTADDRESSES(pa, pa_item); while (pa_item && CURRENT(&(pa->expirydate)) && pa->userid == users->userid) { paytotal += pa->payratio; pa_item = prev_in_ktree(pay_ctx); DATA_PAYMENTADDRESSES_NULL(pa, pa_item); } if (paytotal == 0) paytotal = 1; // Divide totals into payout percentages rows = 0; pa_item = find_paymentaddresses(users->userid, pay_ctx); DATA_PAYMENTADDRESSES_NULL(pa, pa_item); while (pa_item && CURRENT(&(pa->expirydate)) && pa->userid == users->userid) { ratio = (double)(pa->payratio) / (double)paytotal; snprintf(tmp, sizeof(tmp), "payaddress:%d=%s%c", rows, pa->payaddress, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "payratio:%d=%"PRId32"%c", rows, pa->payratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "paypercent:%d=%.6f%c", rows, ratio * 100.0, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "p_hashrate5m:%d=%.1f%c", rows, (double)t_hashrate5m * ratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "p_hashrate1hr:%d=%.1f%c", rows, (double)t_hashrate1hr * ratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "p_hashrate24hr:%d=%.1f%c", rows, (double)t_hashrate24hr * ratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "p_diffacc:%d=%.1f%c", rows, (double)t_diffacc * ratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "p_diffinv:%d=%.1f%c", rows, (double)t_diffinv * ratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "p_diffsta:%d=%.1f%c", rows, (double)t_diffsta * ratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "p_diffdup:%d=%.1f%c", rows, (double)t_diffdup * ratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "p_diffhi:%d=%.1f%c", rows, (double)t_diffhi * ratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "p_diffrej:%d=%.1f%c", rows, (double)t_diffrej * ratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "p_shareacc:%d=%.1f%c", rows, (double)t_shareacc * ratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "p_shareinv:%d=%.1f%c", rows, (double)t_shareinv * ratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "p_sharesta:%d=%.1f%c", rows, (double)t_sharesta * ratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "p_sharedup:%d=%.1f%c", rows, (double)t_sharedup * ratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "p_sharehi:%d=%.1f%c", rows, (double)t_sharehi * ratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "p_sharerej:%d=%.1f%c", rows, (double)t_sharerej * ratio, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; pa_item = prev_in_ktree(pay_ctx); DATA_PAYMENTADDRESSES_NULL(pa, pa_item); } K_RUNLOCK(paymentaddresses_free); snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "payaddress,payratio,paypercent," "p_hashrate5m,p_hashrate1hr,p_hashrate24hr," "p_diffacc,p_diffinv," "p_diffsta,p_diffdup,p_diffhi,p_diffrej," "p_shareacc,p_shareinv," "p_sharesta,p_sharedup,p_sharehi,p_sharerej", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Percents", FLDSEP, ""); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.ok.%s", id, users->username); return buf; } static char *cmd_workers(__maybe_unused PGconn *conn, char *cmd, char *id, tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, __maybe_unused tv_t *notcd, K_TREE *trf_root) { K_ITEM *i_username, *i_stats, *i_percent, w_look, *u_item, *w_item; K_ITEM *ua_item, us_look, *us_item, *ws_item; K_TREE_CTX w_ctx[1], us_ctx[1]; WORKERS lookworkers, *workers; WORKERSTATUS *workerstatus; USERSTATS lookuserstats, *userstats; USERS *users; char reply[1024] = ""; char tmp[1024]; size_t siz = sizeof(reply); char *buf; size_t len, off; bool stats, percent; int rows; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz); if (!i_username) return strdup(reply); K_RLOCK(users_free); u_item = find_users(transfer_data(i_username)); K_RUNLOCK(users_free); if (!u_item) return strdup("bad"); DATA_USERS(users, u_item); i_stats = optional_name(trf_root, "stats", 1, NULL, reply, siz); if (!i_stats) stats = false; else stats = (strcasecmp(transfer_data(i_stats), TRUE_STR) == 0); percent = false; K_RLOCK(useratts_free); ua_item = find_useratts(users->userid, USER_MULTI_PAYOUT); K_RUNLOCK(useratts_free); if (ua_item) { i_percent = optional_name(trf_root, "percent", 1, NULL, reply, siz); if (i_percent) percent = (strcasecmp(transfer_data(i_stats), TRUE_STR) == 0); } if (percent) return cmd_percent(cmd, id, now, users); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); snprintf(tmp, sizeof(tmp), "blockacc=%.1f%c", pool.diffacc, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "blockreward=%"PRId64"%c", pool.reward, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); INIT_WORKERS(&w_look); INIT_USERSTATS(&us_look); lookworkers.userid = users->userid; lookworkers.workername[0] = '\0'; lookworkers.expirydate.tv_sec = 0; lookworkers.expirydate.tv_usec = 0; w_look.data = (void *)(&lookworkers); w_item = find_after_in_ktree(workers_root, &w_look, cmp_workers, w_ctx); DATA_WORKERS_NULL(workers, w_item); rows = 0; while (w_item && workers->userid == users->userid) { if (CURRENT(&(workers->expirydate))) { str_to_buf(workers->workername, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "workername:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); int_to_buf(workers->difficultydefault, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "difficultydefault:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); str_to_buf(workers->idlenotificationenabled, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "idlenotificationenabled:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); int_to_buf(workers->idlenotificationtime, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "idlenotificationtime:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); if (stats) { K_TREE *userstats_workername_root = new_ktree(); K_TREE_CTX usw_ctx[1]; double w_hashrate5m, w_hashrate1hr; double w_hashrate24hr; int64_t w_elapsed; tv_t w_lastshare; double w_lastdiff, w_diffacc, w_diffinv; double w_diffsta, w_diffdup; double w_diffhi, w_diffrej; double w_shareacc, w_shareinv; double w_sharesta, w_sharedup; double w_sharehi, w_sharerej; w_hashrate5m = w_hashrate1hr = w_hashrate24hr = 0.0; w_elapsed = -1; w_lastshare.tv_sec = 0; w_lastdiff = w_diffacc = w_diffinv = w_diffsta = w_diffdup = w_diffhi = w_diffrej = w_shareacc = w_shareinv = w_sharesta = w_sharedup = w_sharehi = w_sharerej = 0; ws_item = find_workerstatus(users->userid, workers->workername, __FILE__, __func__, __LINE__); if (ws_item) { DATA_WORKERSTATUS(workerstatus, ws_item); w_lastshare.tv_sec = workerstatus->last_share.tv_sec; w_lastdiff = workerstatus->last_diff; w_diffacc = workerstatus->diffacc; w_diffinv = workerstatus->diffinv; w_diffsta = workerstatus->diffsta; w_diffdup = workerstatus->diffdup; w_diffhi = workerstatus->diffhi; w_diffrej = workerstatus->diffrej; w_shareacc = workerstatus->shareacc; w_shareinv = workerstatus->shareinv; w_sharesta = workerstatus->sharesta; w_sharedup = workerstatus->sharedup; w_sharehi = workerstatus->sharehi; w_sharerej = workerstatus->sharerej; } // find last stored userid record lookuserstats.userid = users->userid; lookuserstats.statsdate.tv_sec = date_eot.tv_sec; lookuserstats.statsdate.tv_usec = date_eot.tv_usec; // find/cmp doesn't get to here lookuserstats.poolinstance[0] = '\0'; lookuserstats.workername[0] = '\0'; us_look.data = (void *)(&lookuserstats); K_RLOCK(userstats_free); us_item = find_before_in_ktree(userstats_root, &us_look, cmp_userstats, us_ctx); DATA_USERSTATS_NULL(userstats, us_item); while (us_item && userstats->userid == lookuserstats.userid) { if (strcmp(userstats->workername, workers->workername) == 0) { if (tvdiff(now, &(userstats->statsdate)) < USERSTATS_PER_S) { // TODO: add together the latest per pool instance (this is the latest per worker) if (!find_in_ktree(userstats_workername_root, us_item, cmp_userstats_workername, usw_ctx)) { w_hashrate5m += userstats->hashrate5m; w_hashrate1hr += userstats->hashrate1hr; w_hashrate24hr += userstats->hashrate24hr; if (w_elapsed == -1 || w_elapsed > userstats->elapsed) w_elapsed = userstats->elapsed; userstats_workername_root = add_to_ktree(userstats_workername_root, us_item, cmp_userstats_workername); } } else break; } us_item = prev_in_ktree(us_ctx); DATA_USERSTATS_NULL(userstats, us_item); } double_to_buf(w_hashrate5m, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_hashrate5m:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_hashrate1hr, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_hashrate1hr:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_hashrate24hr, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_hashrate24hr:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(w_elapsed, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_elapsed:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); int_to_buf((int)(w_lastshare.tv_sec), reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_lastshare:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_lastdiff, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_lastdiff:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_diffacc, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_diffacc:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_diffinv, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_diffinv:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_diffsta, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_diffsta:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_diffdup, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_diffdup:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_diffhi, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_diffhi:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_diffrej, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_diffrej:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_shareacc, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_shareacc:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_shareinv, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_shareinv:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_sharesta, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_sharesta:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_sharedup, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_sharedup:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_sharehi, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_sharehi:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_sharerej, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_sharerej:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); userstats_workername_root = free_ktree(userstats_workername_root, NULL); K_RUNLOCK(userstats_free); } rows++; } w_item = next_in_ktree(w_ctx); DATA_WORKERS_NULL(workers, w_item); } snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%s%c", rows, FLDSEP, "workername,difficultydefault,idlenotificationenabled," "idlenotificationtime", stats ? ",w_hashrate5m,w_hashrate1hr,w_hashrate24hr," "w_elapsed,w_lastshare," "w_lastdiff,w_diffacc,w_diffinv," "w_diffsta,w_diffdup,w_diffhi,w_diffrej," "w_shareacc,w_shareinv," "w_sharesta,w_sharedup,w_sharehi,w_sharerej" : "", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Workers", FLDSEP, ""); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); return buf; } static char *cmd_allusers(__maybe_unused PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, __maybe_unused tv_t *notcd, __maybe_unused K_TREE *trf_root) { K_TREE *userstats_workername_root = new_ktree(); K_ITEM *us_item, *usw_item, *tmp_item, *u_item; K_TREE_CTX us_ctx[1], usw_ctx[1]; USERSTATS *userstats, *userstats_w; USERS *users; char reply[1024] = ""; char tmp[1024]; char *buf; size_t len, off; int rows; int64_t userid = -1; double u_hashrate5m = 0.0; double u_hashrate1hr = 0.0; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); // TODO: this really should just get the last value of each client_id (within the time limit) // Find last records for each user/worker in ALLUSERS_LIMIT_S // TODO: include pool_instance K_WLOCK(userstats_free); us_item = last_in_ktree(userstats_statsdate_root, us_ctx); DATA_USERSTATS_NULL(userstats, us_item); while (us_item && tvdiff(now, &(userstats->statsdate)) < ALLUSERS_LIMIT_S) { usw_item = find_in_ktree(userstats_workername_root, us_item, cmp_userstats_workername, usw_ctx); if (!usw_item) { usw_item = k_unlink_head(userstats_free); DATA_USERSTATS(userstats_w, usw_item); userstats_w->userid = userstats->userid; strcpy(userstats_w->workername, userstats->workername); userstats_w->hashrate5m = userstats->hashrate5m; userstats_w->hashrate1hr = userstats->hashrate1hr; userstats_workername_root = add_to_ktree(userstats_workername_root, usw_item, cmp_userstats_workername); } us_item = prev_in_ktree(us_ctx); DATA_USERSTATS_NULL(userstats, us_item); } APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); rows = 0; // Add up per user usw_item = first_in_ktree(userstats_workername_root, usw_ctx); while (usw_item) { DATA_USERSTATS(userstats_w, usw_item); if (userstats_w->userid != userid) { if (userid != -1) { K_RLOCK(users_free); u_item = find_userid(userid); K_RUNLOCK(users_free); if (!u_item) { LOGERR("%s() userid %"PRId64" ignored - userstats but not users", __func__, userid); } else { DATA_USERS(users, u_item); str_to_buf(users->username, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "username:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(userid, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "userid:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(u_hashrate5m, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "u_hashrate5m:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(u_hashrate1hr, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "u_hashrate1hr:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; } } userid = userstats_w->userid; u_hashrate5m = 0; u_hashrate1hr = 0; } u_hashrate5m += userstats_w->hashrate5m; u_hashrate1hr += userstats_w->hashrate1hr; tmp_item = usw_item; usw_item = next_in_ktree(usw_ctx); k_add_head(userstats_free, tmp_item); } if (userid != -1) { K_RLOCK(users_free); u_item = find_userid(userid); K_RUNLOCK(users_free); if (!u_item) { LOGERR("%s() userid %"PRId64" ignored - userstats but not users", __func__, userid); } else { DATA_USERS(users, u_item); str_to_buf(users->username, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "username:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(userid, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "userid:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(u_hashrate5m, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "u_hashrate5m:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(u_hashrate1hr, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "u_hashrate1hr:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; } } userstats_workername_root = free_ktree(userstats_workername_root, NULL); K_WUNLOCK(userstats_free); snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "username,userid,u_hashrate5m,u_hashrate1hr", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Users", FLDSEP, ""); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.ok.allusers", id); return buf; } static char *cmd_sharelog(PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *notnow, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root) { char reply[1024] = ""; size_t siz = sizeof(reply); int64_t workinfoid; // log to logfile with processing success/failure code LOGDEBUG("%s(): cmd '%s'", __func__, cmd); if (strcasecmp(cmd, STR_WORKINFO) == 0) { K_ITEM *i_workinfoid, *i_poolinstance, *i_transactiontree, *i_merklehash; K_ITEM *i_prevhash, *i_coinbase1, *i_coinbase2, *i_version, *i_bits; K_ITEM *i_ntime, *i_reward; bool igndup = false; if (reloading && !confirm_sharesummary) { if (tv_equal(cd, &(dbstatus.newest_createdate_workinfo))) igndup = true; else if (tv_newer(cd, &(dbstatus.newest_createdate_workinfo))) return NULL; } i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz); if (!i_workinfoid) return strdup(reply); if (confirm_sharesummary) { TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); if (workinfoid < confirm_first_workinfoid || workinfoid > confirm_last_workinfoid) goto wiconf; } i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) return strdup(reply); i_transactiontree = require_name(trf_root, "transactiontree", 0, NULL, reply, siz); if (!i_transactiontree) return strdup(reply); i_merklehash = require_name(trf_root, "merklehash", 0, NULL, reply, siz); if (!i_merklehash) return strdup(reply); i_prevhash = require_name(trf_root, "prevhash", 1, NULL, reply, siz); if (!i_prevhash) return strdup(reply); i_coinbase1 = require_name(trf_root, "coinbase1", 1, NULL, reply, siz); if (!i_coinbase1) return strdup(reply); i_coinbase2 = require_name(trf_root, "coinbase2", 1, NULL, reply, siz); if (!i_coinbase2) return strdup(reply); i_version = require_name(trf_root, "version", 1, NULL, reply, siz); if (!i_version) return strdup(reply); i_bits = require_name(trf_root, "bits", 1, NULL, reply, siz); if (!i_bits) return strdup(reply); i_ntime = require_name(trf_root, "ntime", 1, NULL, reply, siz); if (!i_ntime) return strdup(reply); i_reward = require_name(trf_root, "reward", 1, NULL, reply, siz); if (!i_reward) return strdup(reply); workinfoid = workinfo_add(conn, transfer_data(i_workinfoid), transfer_data(i_poolinstance), transfer_data(i_transactiontree), transfer_data(i_merklehash), transfer_data(i_prevhash), transfer_data(i_coinbase1), transfer_data(i_coinbase2), transfer_data(i_version), transfer_data(i_bits), transfer_data(i_ntime), transfer_data(i_reward), by, code, inet, cd, igndup, trf_root); if (workinfoid == -1) { LOGERR("%s(%s) %s.failed.DBE", __func__, cmd, id); return strdup("failed.DBE"); } LOGDEBUG("%s.ok.added %"PRId64, id, workinfoid); wiconf: snprintf(reply, siz, "ok.%"PRId64, workinfoid); return strdup(reply); } else if (strcasecmp(cmd, STR_SHARES) == 0) { K_ITEM *i_workinfoid, *i_username, *i_workername, *i_clientid, *i_errn; K_ITEM *i_enonce1, *i_nonce2, *i_nonce, *i_diff, *i_sdiff; K_ITEM *i_secondaryuserid; bool ok; // This just excludes the shares we certainly don't need if (reloading && !confirm_sharesummary) { if (tv_newer(cd, &(dbstatus.sharesummary_firstshare))) return NULL; } i_nonce = require_name(trf_root, "nonce", 1, NULL, reply, siz); if (!i_nonce) return strdup(reply); i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz); if (!i_workinfoid) return strdup(reply); if (confirm_sharesummary) { TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); if (workinfoid < confirm_first_workinfoid || workinfoid > confirm_last_workinfoid) goto sconf; } i_username = require_name(trf_root, "username", 1, NULL, reply, siz); if (!i_username) return strdup(reply); i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz); if (!i_workername) return strdup(reply); i_clientid = require_name(trf_root, "clientid", 1, NULL, reply, siz); if (!i_clientid) return strdup(reply); i_errn = require_name(trf_root, "errn", 1, NULL, reply, siz); if (!i_errn) return strdup(reply); i_enonce1 = require_name(trf_root, "enonce1", 1, NULL, reply, siz); if (!i_enonce1) return strdup(reply); i_nonce2 = require_name(trf_root, "nonce2", 1, NULL, reply, siz); if (!i_nonce2) return strdup(reply); i_diff = require_name(trf_root, "diff", 1, NULL, reply, siz); if (!i_diff) return strdup(reply); i_sdiff = require_name(trf_root, "sdiff", 1, NULL, reply, siz); if (!i_sdiff) return strdup(reply); i_secondaryuserid = optional_name(trf_root, "secondaryuserid", 1, NULL, reply, siz); if (!i_secondaryuserid) i_secondaryuserid = &shares_secondaryuserid; ok = shares_add(conn, transfer_data(i_workinfoid), transfer_data(i_username), transfer_data(i_workername), transfer_data(i_clientid), transfer_data(i_errn), transfer_data(i_enonce1), transfer_data(i_nonce2), transfer_data(i_nonce), transfer_data(i_diff), transfer_data(i_sdiff), transfer_data(i_secondaryuserid), by, code, inet, cd, trf_root); if (!ok) { LOGERR("%s(%s) %s.failed.DATA", __func__, cmd, id); return strdup("failed.DATA"); } LOGDEBUG("%s.ok.added %s", id, transfer_data(i_nonce)); sconf: snprintf(reply, siz, "ok.added %s", transfer_data(i_nonce)); return strdup(reply); } else if (strcasecmp(cmd, STR_SHAREERRORS) == 0) { K_ITEM *i_workinfoid, *i_username, *i_workername, *i_clientid, *i_errn; K_ITEM *i_error, *i_secondaryuserid; bool ok; // This just excludes the shareerrors we certainly don't need if (reloading && !confirm_sharesummary) { if (tv_newer(cd, &(dbstatus.sharesummary_firstshare))) return NULL; } i_username = require_name(trf_root, "username", 1, NULL, reply, siz); if (!i_username) return strdup(reply); i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz); if (!i_workinfoid) return strdup(reply); if (confirm_sharesummary) { TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); if (workinfoid < confirm_first_workinfoid || workinfoid > confirm_last_workinfoid) goto seconf; } i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz); if (!i_workername) return strdup(reply); i_clientid = require_name(trf_root, "clientid", 1, NULL, reply, siz); if (!i_clientid) return strdup(reply); i_errn = require_name(trf_root, "errn", 1, NULL, reply, siz); if (!i_errn) return strdup(reply); i_error = require_name(trf_root, "error", 1, NULL, reply, siz); if (!i_error) return strdup(reply); i_secondaryuserid = optional_name(trf_root, "secondaryuserid", 1, NULL, reply, siz); if (!i_secondaryuserid) i_secondaryuserid = &shareerrors_secondaryuserid; ok = shareerrors_add(conn, transfer_data(i_workinfoid), transfer_data(i_username), transfer_data(i_workername), transfer_data(i_clientid), transfer_data(i_errn), transfer_data(i_error), transfer_data(i_secondaryuserid), by, code, inet, cd, trf_root); if (!ok) { LOGERR("%s(%s) %s.failed.DATA", __func__, cmd, id); return strdup("failed.DATA"); } LOGDEBUG("%s.ok.added %s", id, transfer_data(i_username)); seconf: snprintf(reply, siz, "ok.added %s", transfer_data(i_username)); return strdup(reply); } else if (strcasecmp(cmd, STR_AGEWORKINFO) == 0) { K_ITEM *i_workinfoid, *i_poolinstance; int64_t ss_count, s_count, s_diff; tv_t ss_first, ss_last; bool ok; if (reloading && !confirm_sharesummary) { if (tv_newer(cd, &(dbstatus.sharesummary_firstshare))) return NULL; } i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz); if (!i_workinfoid) return strdup(reply); if (confirm_sharesummary) { TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); if (workinfoid < confirm_first_workinfoid || workinfoid > confirm_last_workinfoid) goto awconf; } i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) return strdup(reply); TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); ok = workinfo_age(conn, workinfoid, transfer_data(i_poolinstance), by, code, inet, cd, &ss_first, &ss_last, &ss_count, &s_count, &s_diff); if (!ok) { LOGERR("%s(%s) %s.failed.DATA", __func__, cmd, id); return strdup("failed.DATA"); } else { /* Don't slow down the reload - do them later * N.B. this means if you abort/shutdown the reload, * next restart will again go back to the oldest * unaged sharesummary due to a pool shutdown */ if (!reloading) { // Aging is a queued item thus the reply is ignored auto_age_older(conn, workinfoid, transfer_data(i_poolinstance), by, code, inet, cd); } } LOGDEBUG("%s.ok.aged %"PRId64, id, workinfoid); awconf: snprintf(reply, siz, "ok.%"PRId64, workinfoid); return strdup(reply); } LOGERR("%s.bad.cmd %s", id, cmd); return strdup("bad.cmd"); } // TODO: the confirm update: identify block changes from workinfo height? static char *cmd_blocks_do(PGconn *conn, char *cmd, char *id, char *by, char *code, char *inet, tv_t *cd, bool igndup, K_TREE *trf_root) { char reply[1024] = ""; size_t siz = sizeof(reply); K_ITEM *i_height, *i_blockhash, *i_confirmed, *i_workinfoid, *i_username; K_ITEM *i_workername, *i_clientid, *i_enonce1, *i_nonce2, *i_nonce, *i_reward; TRANSFER *transfer; char *msg; bool ok; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_height = require_name(trf_root, "height", 1, NULL, reply, siz); if (!i_height) return strdup(reply); i_blockhash = require_name(trf_root, "blockhash", 1, NULL, reply, siz); if (!i_blockhash) return strdup(reply); i_confirmed = require_name(trf_root, "confirmed", 1, NULL, reply, siz); if (!i_confirmed) return strdup(reply); DATA_TRANSFER(transfer, i_confirmed); transfer->mvalue[0] = tolower(transfer->mvalue[0]); switch(transfer->mvalue[0]) { case BLOCKS_NEW: i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz); if (!i_workinfoid) return strdup(reply); i_username = require_name(trf_root, "username", 1, NULL, reply, siz); if (!i_username) return strdup(reply); i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz); if (!i_workername) return strdup(reply); i_clientid = require_name(trf_root, "clientid", 1, NULL, reply, siz); if (!i_clientid) return strdup(reply); i_enonce1 = require_name(trf_root, "enonce1", 1, NULL, reply, siz); if (!i_enonce1) return strdup(reply); i_nonce2 = require_name(trf_root, "nonce2", 1, NULL, reply, siz); if (!i_nonce2) return strdup(reply); i_nonce = require_name(trf_root, "nonce", 1, NULL, reply, siz); if (!i_nonce) return strdup(reply); i_reward = require_name(trf_root, "reward", 1, NULL, reply, siz); if (!i_reward) return strdup(reply); msg = "added"; ok = blocks_add(conn, transfer_data(i_height), transfer_data(i_blockhash), transfer_data(i_confirmed), transfer_data(i_workinfoid), transfer_data(i_username), transfer_data(i_workername), transfer_data(i_clientid), transfer_data(i_enonce1), transfer_data(i_nonce2), transfer_data(i_nonce), transfer_data(i_reward), by, code, inet, cd, igndup, id, trf_root); break; case BLOCKS_CONFIRM: msg = "confirmed"; ok = blocks_add(conn, transfer_data(i_height), transfer_data(i_blockhash), transfer_data(i_confirmed), EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, by, code, inet, cd, igndup, id, trf_root); break; default: LOGERR("%s(): %s.failed.invalid confirm='%s'", __func__, id, transfer_data(i_confirmed)); return strdup("failed.DATA"); } if (!ok) { /* Ignore during startup, * another error should have shown if it matters */ if (startup_complete) LOGERR("%s() %s.failed.DBE", __func__, id); return strdup("failed.DBE"); } LOGDEBUG("%s.ok.blocks %s", id, msg); snprintf(reply, siz, "ok.%s", msg); return strdup(reply); } static char *cmd_blocks(PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *notnow, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root) { bool igndup = false; // confirm_summaries() doesn't call this if (reloading) { if (tv_equal(cd, &(dbstatus.newest_createdate_blocks))) igndup = true; else if (tv_newer(cd, &(dbstatus.newest_createdate_blocks))) return NULL; } return cmd_blocks_do(conn, cmd, id, by, code, inet, cd, igndup, trf_root); } static char *cmd_auth_do(PGconn *conn, char *cmd, char *id, char *by, char *code, char *inet, tv_t *cd, bool igndup, K_TREE *trf_root) { char reply[1024] = ""; size_t siz = sizeof(reply); K_ITEM *i_poolinstance, *i_username, *i_workername, *i_clientid; K_ITEM *i_enonce1, *i_useragent, *i_preauth, *u_item, *oc_item; USERS *users = NULL; char *username; WORKERS *workers = NULL; OPTIONCONTROL *optioncontrol; bool ok; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_poolinstance = optional_name(trf_root, "poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) i_poolinstance = &auth_poolinstance; i_username = require_name(trf_root, "username", 1, NULL, reply, siz); if (!i_username) return strdup(reply); username = transfer_data(i_username); i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz); if (!i_workername) return strdup(reply); i_clientid = require_name(trf_root, "clientid", 1, NULL, reply, siz); if (!i_clientid) return strdup(reply); i_enonce1 = require_name(trf_root, "enonce1", 1, NULL, reply, siz); if (!i_enonce1) return strdup(reply); i_useragent = require_name(trf_root, "useragent", 0, NULL, reply, siz); if (!i_useragent) return strdup(reply); i_preauth = optional_name(trf_root, "preauth", 1, NULL, reply, siz); if (!i_preauth) i_preauth = &auth_preauth; K_RLOCK(optioncontrol_free); oc_item = find_optioncontrol(OPTIONCONTROL_AUTOADDUSER, cd); K_RUNLOCK(optioncontrol_free); if (oc_item) { K_RLOCK(users_free); u_item = find_users(username); K_RUNLOCK(users_free); if (!u_item) { DATA_OPTIONCONTROL(optioncontrol, oc_item); u_item = users_add(conn, username, EMPTY, optioncontrol->optionvalue, by, code, inet, cd, trf_root); } } ok = auths_add(conn, transfer_data(i_poolinstance), username, transfer_data(i_workername), transfer_data(i_clientid), transfer_data(i_enonce1), transfer_data(i_useragent), transfer_data(i_preauth), by, code, inet, cd, igndup, trf_root, false, &users, &workers); if (!ok) { LOGDEBUG("%s() %s.failed.DBE", __func__, id); return strdup("failed.DBE"); } snprintf(reply, siz, "ok.authorise={\"secondaryuserid\":\"%s\"," "\"difficultydefault\":%d}", users->secondaryuserid, workers->difficultydefault); LOGDEBUG("%s.%s", id, reply); return strdup(reply); } static char *cmd_auth(PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *now, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root) { bool igndup = false; // confirm_summaries() doesn't call this if (reloading) { if (tv_equal(cd, &(dbstatus.newest_createdate_auths))) igndup = true; else if (tv_newer(cd, &(dbstatus.newest_createdate_auths))) return NULL; } return cmd_auth_do(conn, cmd, id, by, code, inet, cd, igndup, trf_root); } static char *cmd_addrauth_do(PGconn *conn, char *cmd, char *id, char *by, char *code, char *inet, tv_t *cd, bool igndup, K_TREE *trf_root) { char reply[1024] = ""; size_t siz = sizeof(reply); K_ITEM *i_poolinstance, *i_username, *i_workername, *i_clientid; K_ITEM *i_enonce1, *i_useragent, *i_preauth; USERS *users = NULL; WORKERS *workers = NULL; bool ok; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_poolinstance = optional_name(trf_root, "poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) i_poolinstance = &auth_poolinstance; i_username = require_name(trf_root, "username", 1, NULL, reply, siz); if (!i_username) return strdup(reply); i_workername = require_name(trf_root, "workername", 1, NULL, reply, siz); if (!i_workername) return strdup(reply); i_clientid = require_name(trf_root, "clientid", 1, NULL, reply, siz); if (!i_clientid) return strdup(reply); i_enonce1 = require_name(trf_root, "enonce1", 1, NULL, reply, siz); if (!i_enonce1) return strdup(reply); i_useragent = require_name(trf_root, "useragent", 0, NULL, reply, siz); if (!i_useragent) return strdup(reply); i_preauth = require_name(trf_root, "preauth", 1, NULL, reply, siz); if (!i_preauth) return strdup(reply); ok = auths_add(conn, transfer_data(i_poolinstance), transfer_data(i_username), transfer_data(i_workername), transfer_data(i_clientid), transfer_data(i_enonce1), transfer_data(i_useragent), transfer_data(i_preauth), by, code, inet, cd, igndup, trf_root, true, &users, &workers); if (!ok) { LOGDEBUG("%s() %s.failed.DBE", __func__, id); return strdup("failed.DBE"); } snprintf(reply, siz, "ok.addrauth={\"secondaryuserid\":\"%s\"," "\"difficultydefault\":%d}", users->secondaryuserid, workers->difficultydefault); LOGDEBUG("%s.%s", id, reply); return strdup(reply); } static char *cmd_addrauth(PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *now, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root) { bool igndup = false; // confirm_summaries() doesn't call this if (reloading) { if (tv_equal(cd, &(dbstatus.newest_createdate_auths))) igndup = true; else if (tv_newer(cd, &(dbstatus.newest_createdate_auths))) return NULL; } return cmd_addrauth_do(conn, cmd, id, by, code, inet, cd, igndup, trf_root); } static char *cmd_heartbeat(__maybe_unused PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, __maybe_unused tv_t *cd, __maybe_unused K_TREE *trf_root) { HEARTBEATQUEUE *heartbeatqueue; K_STORE *hq_store; K_ITEM *hq_item; char reply[1024], tmp[1024], *buf; size_t siz = sizeof(reply); size_t len, off; bool first; // Wait until startup is complete, we get a heartbeat every second if (!startup_complete) goto pulse; K_WLOCK(heartbeatqueue_free); if (heartbeatqueue_store->count == 0) { K_WUNLOCK(heartbeatqueue_free); goto pulse; } hq_store = k_new_store(heartbeatqueue_free); k_list_transfer_to_head(heartbeatqueue_store, hq_store); K_WUNLOCK(heartbeatqueue_free); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok.heartbeat={\"diffchange\":["); hq_item = hq_store->tail; first = true; while (hq_item) { DATA_HEARTBEATQUEUE(heartbeatqueue, hq_item); tvs_to_buf(&last_bc, reply, siz); snprintf(tmp, sizeof(tmp), "%s{\"workername\":\"%s\"," "\"difficultydefault\":%d," "\"createdate\":\"%ld,%ld\"}", first ? "" : ",", heartbeatqueue->workername, heartbeatqueue->difficultydefault, heartbeatqueue->createdate.tv_sec, heartbeatqueue->createdate.tv_usec); APPEND_REALLOC(buf, off, len, tmp); hq_item = hq_item->prev; first = false; } APPEND_REALLOC(buf, off, len, "]}"); K_WLOCK(heartbeatqueue_free); k_list_transfer_to_head(hq_store, heartbeatqueue_free); K_WUNLOCK(heartbeatqueue_free); hq_store = k_free_store(hq_store); LOGDEBUG("%s.%s.%s", cmd, id, buf); return buf; pulse: snprintf(reply, siz, "ok.pulse"); LOGDEBUG("%s.%s.%s", cmd, id, reply); return strdup(reply); } static char *cmd_homepage(__maybe_unused PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, __maybe_unused tv_t *notcd, K_TREE *trf_root) { K_ITEM *i_username, *u_item, *b_item, *p_item, *us_item, look; K_ITEM *ua_item, *pa_item; double u_hashrate5m, u_hashrate1hr; char reply[1024], tmp[1024], *buf; size_t siz = sizeof(reply); USERSTATS lookuserstats, *userstats; POOLSTATS *poolstats; BLOCKS *blocks; USERS *users; int64_t u_elapsed; K_TREE_CTX ctx[1], w_ctx[1]; size_t len, off; bool has_uhr; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = optional_name(trf_root, "username", 1, NULL, reply, siz); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); if (last_bc.tv_sec) { tvs_to_buf(&last_bc, reply, siz); snprintf(tmp, sizeof(tmp), "lastbc=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); K_RLOCK(workinfo_free); if (workinfo_current) { WORKINFO *wic; int32_t hi; DATA_WORKINFO(wic, workinfo_current); hi = coinbase1height(wic->coinbase1); snprintf(tmp, sizeof(tmp), "lastheight=%d%c", hi-1, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } else { snprintf(tmp, sizeof(tmp), "lastheight=?%c", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } K_RUNLOCK(workinfo_free); } else { snprintf(tmp, sizeof(tmp), "lastbc=?%clastheight=?%c", FLDSEP, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } if (current_ndiff) { snprintf(tmp, sizeof(tmp), "currndiff=%.1f%c", current_ndiff, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } else { snprintf(tmp, sizeof(tmp), "currndiff=?%c", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } // TODO: handle orphans K_RLOCK(blocks_free); b_item = last_in_ktree(blocks_root, ctx); K_RUNLOCK(blocks_free); if (b_item) { DATA_BLOCKS(blocks, b_item); tvs_to_buf(&(blocks->createdate), reply, siz); snprintf(tmp, sizeof(tmp), "lastblock=%s%cconfirmed=%s%c", reply, FLDSEP, blocks->confirmed, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "lastblockheight=%d%c", blocks->height, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } else { snprintf(tmp, sizeof(tmp), "lastblock=?%cconfirmed=?%c" "lastblockheight=?%c", FLDSEP, FLDSEP, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } snprintf(tmp, sizeof(tmp), "blockacc=%.1f%c", pool.diffacc, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "blockerr=%.1f%c", pool.diffinv, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "blockshareacc=%.1f%c", pool.shareacc, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "blockshareinv=%.1f%c", pool.shareinv, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); // TODO: assumes only one poolinstance (for now) p_item = last_in_ktree(poolstats_root, ctx); if (p_item) { DATA_POOLSTATS(poolstats, p_item); int_to_buf(poolstats->users, reply, siz); snprintf(tmp, sizeof(tmp), "users=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); int_to_buf(poolstats->workers, reply, siz); snprintf(tmp, sizeof(tmp), "workers=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(poolstats->hashrate, reply, siz); snprintf(tmp, sizeof(tmp), "p_hashrate=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(poolstats->hashrate5m, reply, siz); snprintf(tmp, sizeof(tmp), "p_hashrate5m=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(poolstats->hashrate1hr, reply, siz); snprintf(tmp, sizeof(tmp), "p_hashrate1hr=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(poolstats->hashrate24hr, reply, siz); snprintf(tmp, sizeof(tmp), "p_hashrate24hr=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(poolstats->elapsed, reply, siz); snprintf(tmp, sizeof(tmp), "p_elapsed=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); tvs_to_buf(&(poolstats->createdate), reply, siz); snprintf(tmp, sizeof(tmp), "p_statsdate=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } else { snprintf(tmp, sizeof(tmp), "users=?%cworkers=?%cp_hashrate=?%c" "p_hashrate5m=?%cp_hashrate1hr=?%c" "p_hashrate24hr=?%cp_elapsed=?%c" "p_statsdate=?%c", FLDSEP, FLDSEP, FLDSEP, FLDSEP, FLDSEP, FLDSEP, FLDSEP, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } // Don't bother with locking - it's just an FYI web stat snprintf(tmp, sizeof(tmp), "sync=%d%c", workqueue_store->count, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); u_item = NULL; if (i_username) { K_RLOCK(users_free); u_item = find_users(transfer_data(i_username)); K_RUNLOCK(users_free); } // User info to add to or affect the web site display if (u_item) { DATA_USERS(users, u_item); K_RLOCK(useratts_free); ua_item = find_useratts(users->userid, USER_MULTI_PAYOUT); K_RUNLOCK(useratts_free); if (ua_item) { snprintf(tmp, sizeof(tmp), "u_multiaddr=1%c", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } if (!(*(users->emailaddress))) { snprintf(tmp, sizeof(tmp), "u_noemail=1%c", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } K_RLOCK(paymentaddresses_free); pa_item = find_paymentaddresses(users->userid, ctx); K_RUNLOCK(paymentaddresses_free); if (!pa_item) { snprintf(tmp, sizeof(tmp), "u_nopayaddr=1%c", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } } has_uhr = false; if (p_item && u_item) { K_TREE *userstats_workername_root = new_ktree(); u_hashrate5m = u_hashrate1hr = 0.0; u_elapsed = -1; // find last stored userid record lookuserstats.userid = users->userid; lookuserstats.statsdate.tv_sec = date_eot.tv_sec; lookuserstats.statsdate.tv_usec = date_eot.tv_usec; // find/cmp doesn't get to here STRNCPY(lookuserstats.poolinstance, EMPTY); STRNCPY(lookuserstats.workername, EMPTY); INIT_USERSTATS(&look); look.data = (void *)(&lookuserstats); K_RLOCK(userstats_free); us_item = find_before_in_ktree(userstats_root, &look, cmp_userstats, ctx); DATA_USERSTATS_NULL(userstats, us_item); while (us_item && userstats->userid == lookuserstats.userid && tvdiff(now, &(userstats->statsdate)) < USERSTATS_PER_S) { // TODO: add the latest per pool instance (this is the latest per worker) // Ignore summarised data from the DB, it should be old so irrelevant if (userstats->poolinstance[0] && !find_in_ktree(userstats_workername_root, us_item, cmp_userstats_workername, w_ctx)) { u_hashrate5m += userstats->hashrate5m; u_hashrate1hr += userstats->hashrate1hr; if (u_elapsed == -1 || u_elapsed > userstats->elapsed) u_elapsed = userstats->elapsed; has_uhr = true; userstats_workername_root = add_to_ktree(userstats_workername_root, us_item, cmp_userstats_workername); } us_item = prev_in_ktree(ctx); DATA_USERSTATS_NULL(userstats, us_item); } userstats_workername_root = free_ktree(userstats_workername_root, NULL); K_RUNLOCK(userstats_free); } if (has_uhr) { double_to_buf(u_hashrate5m, reply, siz); snprintf(tmp, sizeof(tmp), "u_hashrate5m=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(u_hashrate1hr, reply, siz); snprintf(tmp, sizeof(tmp), "u_hashrate1hr=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(u_elapsed, reply, siz); snprintf(tmp, sizeof(tmp), "u_elapsed=%s", reply); APPEND_REALLOC(buf, off, len, tmp); } else { snprintf(tmp, sizeof(tmp), "u_hashrate5m=?%cu_hashrate1hr=?%cu_elapsed=?", FLDSEP, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } LOGDEBUG("%s.ok.home,user=%s", id, i_username ? transfer_data(i_username): "N"); return buf; } /* Return the list of useratts for the given username=value * Format is attlist=attname.element,attname.element,... * Replies will be attname.element=value * The 2 date fields, date and date2, have a secondary element name * dateexp and date2exp * This will return Y or N depending upon if the date has expired as: * attname.dateexp=N (or Y) and attname.date2exp=N (or Y) * Expired means the date is <= now */ static char *cmd_getatts(__maybe_unused PGconn *conn, char *cmd, char *id, tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, __maybe_unused tv_t *notcd, K_TREE *trf_root) { K_ITEM *i_username, *i_attlist, *u_item, *ua_item; char reply[1024] = ""; size_t siz = sizeof(reply); char tmp[1024]; USERATTS *useratts; USERS *users; char *reason = NULL; char *answer = NULL; char *attlist = NULL, *ptr, *comma, *dot; size_t len, off; bool first; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz); if (!i_username) { reason = "Missing username"; goto nuts; } K_RLOCK(users_free); u_item = find_users(transfer_data(i_username)); K_RUNLOCK(users_free); if (!u_item) { reason = "Unknown user"; goto nuts; } else { DATA_USERS(users, u_item); i_attlist = require_name(trf_root, "attlist", 1, NULL, reply, siz); if (!i_attlist) { reason = "Missing attlist"; goto nuts; } APPEND_REALLOC_INIT(answer, off, len); attlist = ptr = strdup(transfer_data(i_attlist)); first = true; while (ptr && *ptr) { comma = strchr(ptr, ','); if (comma) *(comma++) = '\0'; dot = strchr(ptr, '.'); if (!dot) { reason = "Missing element"; goto nuts; } *(dot++) = '\0'; K_RLOCK(useratts_free); ua_item = find_useratts(users->userid, ptr); K_RUNLOCK(useratts_free); /* web code must check the existance of the attname * in the reply since it will be missing if it doesn't * exist in the DB */ if (ua_item) { char num_buf[BIGINT_BUFSIZ]; char ctv_buf[CDATE_BUFSIZ]; char *ans; DATA_USERATTS(useratts, ua_item); if (strcmp(dot, "str") == 0) { ans = useratts->attstr; } else if (strcmp(dot, "str2") == 0) { ans = useratts->attstr2; } else if (strcmp(dot, "num") == 0) { bigint_to_buf(useratts->attnum, num_buf, sizeof(num_buf)); ans = num_buf; } else if (strcmp(dot, "num2") == 0) { bigint_to_buf(useratts->attnum2, num_buf, sizeof(num_buf)); ans = num_buf; } else if (strcmp(dot, "date") == 0) { ctv_to_buf(&(useratts->attdate), ctv_buf, sizeof(num_buf)); ans = ctv_buf; } else if (strcmp(dot, "dateexp") == 0) { // Y/N if date is <= now (expired) if (tv_newer(&(useratts->attdate), now)) ans = TRUE_STR; else ans = FALSE_STR; } else if (strcmp(dot, "date2") == 0) { ctv_to_buf(&(useratts->attdate2), ctv_buf, sizeof(num_buf)); ans = ctv_buf; } else if (strcmp(dot, "date2exp") == 0) { // Y/N if date2 is <= now (expired) if (tv_newer(&(useratts->attdate2), now)) ans = TRUE_STR; else ans = FALSE_STR; } else { reason = "Unknown element"; goto nuts; } snprintf(tmp, sizeof(tmp), "%s%s.%s=%s", first ? EMPTY : FLDSEPSTR, ptr, dot, ans); APPEND_REALLOC(answer, off, len, tmp); first = false; } ptr = comma; } } nuts: if (attlist) free(attlist); if (reason) { if (answer) free(answer); snprintf(reply, siz, "ERR.%s", reason); LOGERR("%s.%s.%s", cmd, id, reply); return strdup(reply); } snprintf(reply, siz, "ok.%s", answer); LOGDEBUG("%s.%s", id, answer); free(answer); return strdup(reply); } static void att_to_date(tv_t *date, char *data, tv_t *now) { int add; if (strncasecmp(data, "now+", 4) == 0) { add = atoi(data+4); copy_tv(date, now); date->tv_sec += add; } else if (strcasecmp(data, "now") == 0) { copy_tv(date, now); } else { txt_to_ctv("date", data, date, sizeof(*date)); } } /* Store useratts in the DB for the given username=value * Format is 1 or more: ua_attname.element=value * i.e. each starts with the constant "ua_" * attname cannot contain Tab . or = * element is per the coded list below, which also cannot contain Tab . or = * Any matching useratts attnames found currently in the DB are expired * Transfer will sort them so that any of the same attname * will be next to each other * thus will combine multiple elements for the same attname * into one single useratts record (as is mandatory) * The 2 date fields date and date2 require either epoch values sec,usec * (usec is optional and defaults to 0) or one of: now or now+NNN * now is the current epoch value and now+NNN is the epoch + NNN seconds * See att_to_date() above * */ static char *cmd_setatts(PGconn *conn, char *cmd, char *id, tv_t *now, char *by, char *code, char *inet, __maybe_unused tv_t *notcd, K_TREE *trf_root) { ExecStatusType rescode; PGresult *res; bool conned = false; K_ITEM *i_username, *t_item, *u_item, *ua_item = NULL; K_TREE_CTX ctx[1]; char reply[1024] = ""; size_t siz = sizeof(reply); TRANSFER *transfer; USERATTS *useratts = NULL; USERS *users; char attname[sizeof(useratts->attname)*2]; char *reason = NULL; char *dot, *data; bool begun = false; int set = 0, db = 0; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz); if (!i_username) { reason = "Missing user"; goto bats; } K_RLOCK(users_free); u_item = find_users(transfer_data(i_username)); K_RUNLOCK(users_free); if (!u_item) { reason = "Unknown user"; goto bats; } else { DATA_USERS(users, u_item); t_item = first_in_ktree(trf_root, ctx); while (t_item) { DATA_TRANSFER(transfer, t_item); if (strncmp(transfer->name, "ua_", 3) == 0) { data = transfer_data(t_item); STRNCPY(attname, transfer->name + 3); dot = strchr(attname, '.'); if (!dot) { reason = "Missing element"; goto bats; } *(dot++) = '\0'; // If we already had a different one, save it to the DB if (ua_item && strcmp(useratts->attname, attname) != 0) { if (conn == NULL) { conn = dbconnect(); conned = true; } if (!begun) { // Beginning of a write txn res = PQexec(conn, "Begin", CKPQ_WRITE); rescode = PQresultStatus(res); PQclear(res); if (!PGOK(rescode)) { PGLOGERR("Begin", rescode, conn); reason = "DBERR"; goto bats; } begun = true; } if (useratts_item_add(conn, ua_item, now, begun)) { ua_item = NULL; db++; } else { reason = "DBERR"; goto rollback; } } if (!ua_item) { K_RLOCK(useratts_free); ua_item = k_unlink_head(useratts_free); K_RUNLOCK(useratts_free); DATA_USERATTS(useratts, ua_item); bzero(useratts, sizeof(*useratts)); useratts->userid = users->userid; STRNCPY(useratts->attname, attname); HISTORYDATEINIT(useratts, now, by, code, inet); HISTORYDATETRANSFER(trf_root, useratts); } // List of valid element names for storage if (strcmp(dot, "str") == 0) { STRNCPY(useratts->attstr, data); set++; } else if (strcmp(dot, "str2") == 0) { STRNCPY(useratts->attstr2, data); set++; } else if (strcmp(dot, "num") == 0) { TXT_TO_BIGINT("num", data, useratts->attnum); set++; } else if (strcmp(dot, "num2") == 0) { TXT_TO_BIGINT("num2", data, useratts->attnum2); set++; } else if (strcmp(dot, "date") == 0) { att_to_date(&(useratts->attdate), data, now); set++; } else if (strcmp(dot, "date2") == 0) { att_to_date(&(useratts->attdate2), data, now); set++; } else { reason = "Unknown element"; goto bats; } } t_item = next_in_ktree(ctx); } if (ua_item) { if (conn == NULL) { conn = dbconnect(); conned = true; } if (!begun) { // Beginning of a write txn res = PQexec(conn, "Begin", CKPQ_WRITE); rescode = PQresultStatus(res); PQclear(res); if (!PGOK(rescode)) { PGLOGERR("Begin", rescode, conn); reason = "DBERR"; goto bats; } begun = true; } if (!useratts_item_add(conn, ua_item, now, begun)) { reason = "DBERR"; goto rollback; } db++; } } rollback: if (!reason) res = PQexec(conn, "Commit", CKPQ_WRITE); else res = PQexec(conn, "Rollback", CKPQ_WRITE); PQclear(res); bats: if (conned) PQfinish(conn); if (reason) { if (ua_item) { K_WLOCK(useratts_free); k_add_head(useratts_free, ua_item); K_WUNLOCK(useratts_free); } snprintf(reply, siz, "ERR.%s", reason); LOGERR("%s.%s.%s", cmd, id, reply); return strdup(reply); } snprintf(reply, siz, "ok.set %d,%d", db, set); LOGDEBUG("%s.%s", id, reply); return strdup(reply); } /* Expire the list of useratts for the given username=value * Format is attlist=attname,attname,... * Each matching DB attname record will have it's expirydate set to now * thus an attempt to access it with getatts will not find it and * return nothing for that attname */ static char *cmd_expatts(__maybe_unused PGconn *conn, char *cmd, char *id, tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, __maybe_unused tv_t *notcd, K_TREE *trf_root) { K_ITEM *i_username, *i_attlist, *u_item, *ua_item; char reply[1024] = ""; size_t siz = sizeof(reply); USERATTS *useratts; USERS *users; char *reason = NULL; char *attlist, *ptr, *comma; int db = 0, mis = 0; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name(trf_root, "username", 3, (char *)userpatt, reply, siz); if (!i_username) { reason = "Missing username"; goto rats; } K_RLOCK(users_free); u_item = find_users(transfer_data(i_username)); K_RUNLOCK(users_free); if (!u_item) { reason = "Unknown user"; goto rats; } else { DATA_USERS(users, u_item); i_attlist = require_name(trf_root, "attlist", 1, NULL, reply, siz); if (!i_attlist) { reason = "Missing attlist"; goto rats; } attlist = ptr = strdup(transfer_data(i_attlist)); while (ptr && *ptr) { comma = strchr(ptr, ','); if (comma) *(comma++) = '\0'; K_RLOCK(useratts_free); ua_item = find_useratts(users->userid, ptr); K_RUNLOCK(useratts_free); if (!ua_item) mis++; else { DATA_USERATTS(useratts, ua_item); HISTORYDATEINIT(useratts, now, by, code, inet); HISTORYDATETRANSFER(trf_root, useratts); /* Since we are expiring records, don't bother * with combining them all into a single * transaction and don't abort on error * Thus if an error is returned, retry would be * necessary, but some may also have been * expired successfully */ if (!useratts_item_expire(conn, ua_item, now)) reason = "DBERR"; else db++; } ptr = comma; } free(attlist); } rats: if (reason) { snprintf(reply, siz, "ERR.%s", reason); LOGERR("%s.%s.%s", cmd, id, reply); return strdup(reply); } snprintf(reply, siz, "ok.exp %d,%d", db, mis); LOGDEBUG("%s.%s.%s", cmd, id, reply); return strdup(reply); } /* Return the list of optioncontrols * Format is optlist=optionname,optionname,optionname,... * Replies will be optionname=value * Any optionnames not in the DB or not yet active will be missing */ static char *cmd_getopts(__maybe_unused PGconn *conn, char *cmd, char *id, tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, __maybe_unused tv_t *notcd, K_TREE *trf_root) { K_ITEM *i_optlist, *oc_item; char reply[1024] = ""; size_t siz = sizeof(reply); char tmp[1024]; OPTIONCONTROL *optioncontrol; char *reason = NULL; char *answer = NULL; char *optlist = NULL, *ptr, *comma; size_t len, off; bool first; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_optlist = require_name(trf_root, "optlist", 1, NULL, reply, siz); if (!i_optlist) { reason = "Missing optlist"; goto ruts; } APPEND_REALLOC_INIT(answer, off, len); optlist = ptr = strdup(transfer_data(i_optlist)); first = true; while (ptr && *ptr) { comma = strchr(ptr, ','); if (comma) *(comma++) = '\0'; K_RLOCK(optioncontrol_free); oc_item = find_optioncontrol(ptr, now); K_RUNLOCK(optioncontrol_free); /* web code must check the existance of the optionname * in the reply since it will be missing if it doesn't * exist in the DB */ if (oc_item) { DATA_OPTIONCONTROL(optioncontrol, oc_item); snprintf(tmp, sizeof(tmp), "%s%s=%s", first ? EMPTY : FLDSEPSTR, optioncontrol->optionname, optioncontrol->optionvalue); APPEND_REALLOC(answer, off, len, tmp); first = false; } ptr = comma; } ruts: if (optlist) free(optlist); if (reason) { if (answer) free(answer); snprintf(reply, siz, "ERR.%s", reason); LOGERR("%s.%s.%s", cmd, id, reply); return strdup(reply); } snprintf(reply, siz, "ok.%s", answer); LOGDEBUG("%s.%s", id, answer); free(answer); return strdup(reply); } // This is the same as att_set_date() for now #define opt_set_date(_date, _data, _now) att_set_date(_date, _data, _now) /* Store optioncontrols in the DB * Format is 1 or more: oc_optionname.fld=value * i.e. each starts with the constant "oc_" * optionname cannot contain Tab . or = * fld is one of the 3: value, date, height * value must exist * None, one or both of date and height can exist * If a matching optioncontrol (same name, date and height) exists, * it will have it's expiry date set to now and be replaced with the new value * The date field requires either an epoch sec,usec * (usec is optional and defaults to 0) or one of: now or now+NNN * now is the current epoch value and now+NNN is the epoch + NNN seconds * See opt_set_date() above */ static char *cmd_setopts(PGconn *conn, char *cmd, char *id, tv_t *now, char *by, char *code, char *inet, __maybe_unused tv_t *notcd, K_TREE *trf_root) { ExecStatusType rescode; PGresult *res; bool conned = false; K_ITEM *t_item, *oc_item = NULL, *ok = NULL; K_TREE_CTX ctx[1]; char reply[1024] = ""; size_t siz = sizeof(reply); TRANSFER *transfer; OPTIONCONTROL *optioncontrol; char optionname[sizeof(optioncontrol->optionname)*2]; char *reason = NULL; char *dot, *data; bool begun = false, gotvalue = false; int db = 0; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); t_item = first_in_ktree(trf_root, ctx); while (t_item) { DATA_TRANSFER(transfer, t_item); if (strncmp(transfer->name, "oc_", 3) == 0) { data = transfer_data(t_item); STRNCPY(optionname, transfer->name + 3); dot = strchr(optionname, '.'); if (!dot) { reason = "Missing field"; goto rollback; } *(dot++) = '\0'; // If we already had a different one, save it to the DB if (oc_item && strcmp(optioncontrol->optionname, optionname) != 0) { if (!gotvalue) { reason = "Missing value"; goto rollback; } if (conn == NULL) { conn = dbconnect(); conned = true; } if (!begun) { // Beginning of a write txn res = PQexec(conn, "Begin", CKPQ_WRITE); rescode = PQresultStatus(res); PQclear(res); if (!PGOK(rescode)) { PGLOGERR("Begin", rescode, conn); reason = "DBERR"; goto rollback; } begun = true; } ok = optioncontrol_item_add(conn, oc_item, now, begun); oc_item = NULL; if (ok) db++; else { reason = "DBERR"; goto rollback; } } if (!oc_item) { K_RLOCK(optioncontrol_free); oc_item = k_unlink_head(optioncontrol_free); K_RUNLOCK(optioncontrol_free); DATA_OPTIONCONTROL(optioncontrol, oc_item); bzero(optioncontrol, sizeof(*optioncontrol)); STRNCPY(optioncontrol->optionname, optionname); optioncontrol->activationheight = OPTIONCONTROL_HEIGHT; HISTORYDATEINIT(optioncontrol, now, by, code, inet); HISTORYDATETRANSFER(trf_root, optioncontrol); gotvalue = false; } if (strcmp(dot, "value") == 0) { optioncontrol->optionvalue = strdup(data); if (!(optioncontrol->optionvalue)) quithere(1, "malloc (%d) OOM", (int)strlen(data)); gotvalue = true; } else if (strcmp(dot, "date") == 0) { att_to_date(&(optioncontrol->activationdate), data, now); } else if (strcmp(dot, "height") == 0) { TXT_TO_INT("height", data, optioncontrol->activationheight); } else { reason = "Unknown field"; goto rollback; } } t_item = next_in_ktree(ctx); } if (oc_item) { if (!gotvalue) { reason = "Missing value"; goto rollback; } if (conn == NULL) { conn = dbconnect(); conned = true; } if (!begun) { // Beginning of a write txn res = PQexec(conn, "Begin", CKPQ_WRITE); rescode = PQresultStatus(res); PQclear(res); if (!PGOK(rescode)) { PGLOGERR("Begin", rescode, conn); reason = "DBERR"; goto rollback; } begun = true; } ok = optioncontrol_item_add(conn, oc_item, now, begun); oc_item = NULL; if (ok) db++; else { reason = "DBERR"; goto rollback; } } rollback: if (begun) { if (reason) res = PQexec(conn, "Rollback", CKPQ_WRITE); else res = PQexec(conn, "Commit", CKPQ_WRITE); PQclear(res); } if (conned) PQfinish(conn); if (reason) { snprintf(reply, siz, "ERR.%s", reason); LOGERR("%s.%s.%s", cmd, id, reply); return strdup(reply); } snprintf(reply, siz, "ok.set %d", db); LOGDEBUG("%s.%s", id, reply); return strdup(reply); } // order by userid asc static cmp_t cmp_mu(K_ITEM *a, K_ITEM *b) { MININGPAYOUTS *ma, *mb; DATA_MININGPAYOUTS(ma, a); DATA_MININGPAYOUTS(mb, b); return CMP_BIGINT(ma->userid, mb->userid); } static K_TREE *upd_add_mu(K_TREE *mu_root, K_STORE *mu_store, int64_t userid, int64_t diffacc) { MININGPAYOUTS lookminingpayouts, *miningpayouts; K_ITEM look, *mu_item; K_TREE_CTX ctx[1]; lookminingpayouts.userid = userid; INIT_MININGPAYOUTS(&look); look.data = (void *)(&lookminingpayouts); mu_item = find_in_ktree(mu_root, &look, cmp_mu, ctx); if (mu_item) { DATA_MININGPAYOUTS(miningpayouts, mu_item); miningpayouts->amount += diffacc; } else { K_WLOCK(mu_store); mu_item = k_unlink_head(miningpayouts_free); DATA_MININGPAYOUTS(miningpayouts, mu_item); miningpayouts->userid = userid; miningpayouts->amount = diffacc; mu_root = add_to_ktree(mu_root, mu_item, cmp_mu); k_add_head(mu_store, mu_item); K_WUNLOCK(mu_store); } return mu_root; } /* Find the block_workinfoid of the block requested then add all it's diffacc shares then keep stepping back shares until diffacc_total matches or exceeds the number required (diff_want) - this is begin_workinfoid (also summarising diffacc per user) then keep stepping back until we complete the current begin_workinfoid (also summarising diffacc per user) While we are still below diff_want find each workmarker and add on the full set of worksummary diffacc shares (also summarising diffacc per user) This will give us the total number of diff1 shares (diffacc_total) to use for the payment calculations The value of diff_want defaults to the block's network difficulty (block_ndiff) but can be changed with diff_times and diff_add to: block_ndiff * diff_times + diff_add N.B. diff_times and diff_add can be zero, positive or negative The pplns_elapsed time of the shares is from the createdate of the begin_workinfoid that has shares accounted to the total, up to the createdate of the last share The user average hashrate would be: diffacc_user * 2^32 / pplns_elapsed PPLNS fraction of the payout would be: diffacc_user / diffacc_total N.B. 'begin' means the oldest back in time and 'end' means the newest 'end' should usually be the info of the found block with the pplns data going back in time to 'begin' */ /* Blocks after 334106 were set to 5xN * however, they cannot count back to include the workinfoid of 333809 * due to the markersummaries that were created. * Code checks that if the block is after FIVExSTT then it must stop * counting back shares at - and not include - FIVExWID */ #define FIVExSTT 334106 #define FIVExLIM 333809 // 333809 workinfoid #define FIVExWID 6085620100361140756 static char *cmd_pplns(__maybe_unused PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, __maybe_unused tv_t *notcd, K_TREE *trf_root) { char reply[1024], tmp[1024], *buf; char *block_extra, *share_status = EMPTY, *marks_status = EMPTY; size_t siz = sizeof(reply); K_ITEM *i_height, *i_difftimes, *i_diffadd, *i_allowaged; K_ITEM b_look, ss_look, *b_item, *w_item, *ss_item; K_ITEM wm_look, *wm_item, ms_look, *ms_item; K_ITEM *mu_item, *wb_item, *u_item; SHARESUMMARY looksharesummary, *sharesummary; WORKMARKERS lookworkmarkers, *workmarkers; MARKERSUMMARY lookmarkersummary, *markersummary; MININGPAYOUTS *miningpayouts; WORKINFO *workinfo; TRANSFER *transfer; BLOCKS lookblocks, *blocks; K_TREE *mu_root; K_STORE *mu_store; USERS *users; int32_t height; int64_t block_workinfoid, end_workinfoid; int64_t begin_workinfoid; int64_t total_share_count, acc_share_count; int64_t ss_count, wm_count, ms_count; char tv_buf[DATE_BUFSIZ]; tv_t cd, begin_tv, block_tv, end_tv; K_TREE_CTX ctx[1], wm_ctx[1], ms_ctx[1], pay_ctx[1]; double ndiff, total_diff, elapsed; double diff_times = 1.0; double diff_add = 0.0; double diff_want; bool allow_aged = false, countbacklimit; char ndiffbin[TXT_SML+1]; size_t len, off; int rows; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); if (sharesummary_marks_limit) marks_status = "ckdb -w load value means pplns may be incorrect"; i_height = require_name(trf_root, "height", 1, NULL, reply, siz); if (!i_height) return strdup(reply); TXT_TO_INT("height", transfer_data(i_height), height); i_difftimes = optional_name(trf_root, "diff_times", 1, NULL, reply, siz); if (i_difftimes) TXT_TO_DOUBLE("diff_times", transfer_data(i_difftimes), diff_times); i_diffadd = optional_name(trf_root, "diff_add", 1, NULL, reply, siz); if (i_diffadd) TXT_TO_DOUBLE("diff_add", transfer_data(i_diffadd), diff_add); i_allowaged = optional_name(trf_root, "allow_aged", 1, NULL, reply, siz); if (i_allowaged) { DATA_TRANSFER(transfer, i_allowaged); if (toupper(transfer->mvalue[0]) == TRUE_STR[0]) allow_aged = true; } block_tv.tv_sec = block_tv.tv_usec = 0L; cd.tv_sec = cd.tv_usec = 0L; lookblocks.height = height + 1; lookblocks.blockhash[0] = '\0'; INIT_BLOCKS(&b_look); b_look.data = (void *)(&lookblocks); K_RLOCK(blocks_free); b_item = find_before_in_ktree(blocks_root, &b_look, cmp_blocks, ctx); if (!b_item) { K_RUNLOCK(blocks_free); snprintf(reply, siz, "ERR.no block height %d", height); return strdup(reply); } DATA_BLOCKS_NULL(blocks, b_item); while (b_item && blocks->height == height) { if (blocks->confirmed[0] == BLOCKS_NEW) { copy_tv(&block_tv, &(blocks->createdate)); copy_tv(&end_tv, &(blocks->createdate)); } // Allow any state, but report it if (CURRENT(&(blocks->expirydate))) break; b_item = prev_in_ktree(ctx); DATA_BLOCKS_NULL(blocks, b_item); } K_RUNLOCK(blocks_free); if (!b_item || blocks->height != height) { snprintf(reply, siz, "ERR.no CURRENT block %d", height); return strdup(reply); } if (block_tv.tv_sec == 0) { snprintf(reply, siz, "ERR.block %d missing '%s' record", height, blocks_confirmed(BLOCKS_NEW_STR)); return strdup(reply); } switch (blocks->confirmed[0]) { case BLOCKS_NEW: block_extra = "Can't be paid out yet"; break; case BLOCKS_ORPHAN: block_extra = "Can't be paid out"; break; default: block_extra = EMPTY; break; } block_workinfoid = blocks->workinfoid; w_item = find_workinfo(block_workinfoid, NULL); if (!w_item) { snprintf(reply, siz, "ERR.missing workinfo %"PRId64, block_workinfoid); return strdup(reply); } DATA_WORKINFO(workinfo, w_item); hex2bin(ndiffbin, workinfo->bits, 4); ndiff = diff_from_nbits(ndiffbin); diff_want = ndiff * diff_times + diff_add; if (diff_want < 1.0) { snprintf(reply, siz, "ERR.invalid diff_want result %f", diff_want); return strdup(reply); } if (blocks->height > FIVExSTT) countbacklimit = true; else countbacklimit = false; begin_workinfoid = end_workinfoid = 0; total_share_count = acc_share_count = 0; total_diff = 0; ss_count = wm_count = ms_count = 0; mu_store = k_new_store(miningpayouts_free); mu_root = new_ktree(); looksharesummary.workinfoid = block_workinfoid; looksharesummary.userid = MAXID; looksharesummary.workername = EMPTY; INIT_SHARESUMMARY(&ss_look); ss_look.data = (void *)(&looksharesummary); K_RLOCK(sharesummary_free); K_RLOCK(workmarkers_free); K_RLOCK(markersummary_free); ss_item = find_before_in_ktree(sharesummary_workinfoid_root, &ss_look, cmp_sharesummary_workinfoid, ctx); DATA_SHARESUMMARY_NULL(sharesummary, ss_item); if (ss_item) end_workinfoid = sharesummary->workinfoid; /* add up all sharesummaries until >= diff_want * also record the latest lastshare - that will be the end pplns time * which will be >= block_tv */ while (total_diff < diff_want && ss_item) { switch (sharesummary->complete[0]) { case SUMMARY_CONFIRM: break; case SUMMARY_COMPLETE: if (allow_aged) break; default: share_status = "Not ready1"; } // Stop before FIVExWID if necessary if (countbacklimit && sharesummary->workinfoid <= FIVExWID) break; ss_count++; total_share_count += sharesummary->sharecount; acc_share_count += sharesummary->shareacc; total_diff += (int64_t)(sharesummary->diffacc); begin_workinfoid = sharesummary->workinfoid; if (tv_newer(&end_tv, &(sharesummary->lastshare))) copy_tv(&end_tv, &(sharesummary->lastshare)); mu_root = upd_add_mu(mu_root, mu_store, sharesummary->userid, (int64_t)(sharesummary->diffacc)); ss_item = prev_in_ktree(ctx); DATA_SHARESUMMARY_NULL(sharesummary, ss_item); } // include all the rest of the sharesummaries with begin_workinfoid while (ss_item && sharesummary->workinfoid == begin_workinfoid) { switch (sharesummary->complete[0]) { case SUMMARY_CONFIRM: break; case SUMMARY_COMPLETE: if (allow_aged) break; default: if (share_status == EMPTY) share_status = "Not ready2"; else share_status = "Not ready1+2"; } ss_count++; total_share_count += sharesummary->sharecount; acc_share_count += sharesummary->shareacc; total_diff += (int64_t)(sharesummary->diffacc); mu_root = upd_add_mu(mu_root, mu_store, sharesummary->userid, (int64_t)(sharesummary->diffacc)); ss_item = prev_in_ktree(ctx); DATA_SHARESUMMARY_NULL(sharesummary, ss_item); } /* If we haven't met or exceeded the required N, * move on to the markersummaries */ if (total_diff < diff_want) { lookworkmarkers.expirydate.tv_sec = default_expiry.tv_sec; lookworkmarkers.expirydate.tv_usec = default_expiry.tv_usec; if (begin_workinfoid != 0) lookworkmarkers.workinfoidend = begin_workinfoid; else lookworkmarkers.workinfoidend = block_workinfoid + 1; INIT_WORKMARKERS(&wm_look); wm_look.data = (void *)(&lookworkmarkers); wm_item = find_before_in_ktree(workmarkers_workinfoid_root, &wm_look, cmp_workmarkers_workinfoid, wm_ctx); DATA_WORKMARKERS_NULL(workmarkers, wm_item); while (total_diff < diff_want && wm_item && CURRENT(&(workmarkers->expirydate))) { if (WMPROCESSED(workmarkers->status)) { // Stop before FIVExWID if necessary if (countbacklimit && workmarkers->workinfoidstart <= FIVExWID) break; wm_count++; lookmarkersummary.markerid = workmarkers->markerid; lookmarkersummary.userid = MAXID; lookmarkersummary.workername = EMPTY; INIT_MARKERSUMMARY(&ms_look); ms_look.data = (void *)(&lookmarkersummary); ms_item = find_before_in_ktree(markersummary_root, &ms_look, cmp_markersummary, ms_ctx); DATA_MARKERSUMMARY_NULL(markersummary, ms_item); // add the whole markerid while (ms_item && markersummary->markerid == workmarkers->markerid) { if (end_workinfoid == 0) end_workinfoid = workmarkers->workinfoidend; ms_count++; total_share_count += markersummary->sharecount; acc_share_count += markersummary->shareacc; total_diff += (int64_t)(markersummary->diffacc); begin_workinfoid = workmarkers->workinfoidstart; if (tv_newer(&end_tv, &(markersummary->lastshare))) copy_tv(&end_tv, &(markersummary->lastshare)); mu_root = upd_add_mu(mu_root, mu_store, markersummary->userid, (int64_t)(markersummary->diffacc)); ms_item = prev_in_ktree(ms_ctx); DATA_MARKERSUMMARY_NULL(markersummary, ms_item); } } wm_item = prev_in_ktree(wm_ctx); DATA_WORKMARKERS_NULL(workmarkers, wm_item); } } K_RUNLOCK(markersummary_free); K_RUNLOCK(workmarkers_free); K_RUNLOCK(sharesummary_free); if (total_diff == 0.0) { snprintf(reply, siz, "ERR.total share diff 0 before workinfo %"PRId64, block_workinfoid); goto shazbot; } wb_item = find_workinfo(begin_workinfoid, NULL); if (!wb_item) { snprintf(reply, siz, "ERR.missing begin workinfo record! %"PRId64, block_workinfoid); goto shazbot; } DATA_WORKINFO(workinfo, wb_item); copy_tv(&begin_tv, &(workinfo->createdate)); /* Elapsed is from the start of the first workinfoid used, * to the time of the last share counted - * which can be after the block, but must have the same workinfoid as * the block, if it is after the block * All shares accepted in all workinfoids after the block's workinfoid * will not be creditied to this block no matter what the height * of their workinfoid is - but will be candidates for the next block */ elapsed = tvdiff(&end_tv, &begin_tv); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); snprintf(tmp, sizeof(tmp), "block=%d%c", height, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "block_hash=%s%c", blocks->blockhash, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "block_reward=%"PRId64"%c", blocks->reward, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "block_status=%s%c", blocks_confirmed(blocks->confirmed), FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "block_extra=%s%c", block_extra, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "share_status=%s%c", share_status, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "marks_status=%s%c", marks_status, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "workername=%s%c", blocks->workername, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "nonce=%s%c", blocks->nonce, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "begin_workinfoid=%"PRId64"%c", begin_workinfoid, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "block_workinfoid=%"PRId64"%c", block_workinfoid, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "end_workinfoid=%"PRId64"%c", end_workinfoid, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "diffacc_total=%.0f%c", total_diff, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "pplns_elapsed=%f%c", elapsed, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows = 0; mu_item = first_in_ktree(mu_root, ctx); while (mu_item) { DATA_MININGPAYOUTS(miningpayouts, mu_item); K_RLOCK(users_free); u_item = find_userid(miningpayouts->userid); K_RUNLOCK(users_free); if (!u_item) { snprintf(reply, siz, "ERR.unknown userid %"PRId64, miningpayouts->userid); goto shazbot; } DATA_USERS(users, u_item); K_ITEM *pa_item; PAYMENTADDRESSES *pa; int64_t paytotal; double amount; int count; K_RLOCK(paymentaddresses_free); pa_item = find_paymentaddresses(miningpayouts->userid, pay_ctx); if (pa_item) { paytotal = 0; DATA_PAYMENTADDRESSES(pa, pa_item); while (pa_item && CURRENT(&(pa->expirydate)) && pa->userid == miningpayouts->userid) { paytotal += pa->payratio; pa_item = prev_in_ktree(pay_ctx); DATA_PAYMENTADDRESSES_NULL(pa, pa_item); } count = 0; pa_item = find_paymentaddresses(miningpayouts->userid, pay_ctx); DATA_PAYMENTADDRESSES_NULL(pa, pa_item); while (pa_item && CURRENT(&(pa->expirydate)) && pa->userid == miningpayouts->userid) { amount = (double)(miningpayouts->amount) * (double)pa->payratio / (double)paytotal; snprintf(tmp, sizeof(tmp), "user:%d=%s.%d%cpayaddress:%d=%s%c", rows, users->username, ++count, FLDSEP, rows, pa->payaddress, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "diffacc_user:%d=%.1f%c", rows, amount, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; pa_item = prev_in_ktree(pay_ctx); DATA_PAYMENTADDRESSES_NULL(pa, pa_item); } K_RUNLOCK(paymentaddresses_free); } else { K_RUNLOCK(paymentaddresses_free); snprintf(tmp, sizeof(tmp), "user:%d=%s.0%cpayaddress:%d=%s%c", rows, users->username, FLDSEP, rows, "none", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "diffacc_user:%d=%"PRId64"%c", rows, miningpayouts->amount, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; } mu_item = next_in_ktree(ctx); } snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "user,diffacc_user,payaddress", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s%c", "Users", FLDSEP, "", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); tv_to_buf(&begin_tv, tv_buf, sizeof(tv_buf)); snprintf(tmp, sizeof(tmp), "begin_stamp=%s%c", tv_buf, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "begin_epoch=%ld%c", begin_tv.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); tv_to_buf(&block_tv, tv_buf, sizeof(tv_buf)); snprintf(tmp, sizeof(tmp), "block_stamp=%s%c", tv_buf, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "block_epoch=%ld%c", block_tv.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); tv_to_buf(&end_tv, tv_buf, sizeof(tv_buf)); snprintf(tmp, sizeof(tmp), "end_stamp=%s%c", tv_buf, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "end_epoch=%ld%c", end_tv.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "block_ndiff=%f%c", ndiff, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "diff_times=%f%c", diff_times, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "diff_add=%f%c", diff_add, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "diff_want=%f%c", diff_want, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "acc_share_count=%"PRId64"%c", acc_share_count, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "total_share_count=%"PRId64"%c", total_share_count, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "ss_count=%"PRId64"%c", ss_count, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "wm_count=%"PRId64"%c", wm_count, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "ms_count=%"PRId64"%c", ms_count, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); // So web can always verify it received all data APPEND_REALLOC(buf, off, len, "pplns_last=1"); mu_root = free_ktree(mu_root, NULL); K_WLOCK(mu_store); k_list_transfer_to_head(mu_store, miningpayouts_free); K_WUNLOCK(mu_store); mu_store = k_free_store(mu_store); LOGDEBUG("%s.ok.pplns.%s", id, buf); return buf; shazbot: mu_root = free_ktree(mu_root, NULL); K_WLOCK(mu_store); k_list_transfer_to_head(mu_store, miningpayouts_free); K_WUNLOCK(mu_store); mu_store = k_free_store(mu_store); return strdup(reply); } static char *cmd_dsp(__maybe_unused PGconn *conn, __maybe_unused char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, __maybe_unused tv_t *notcd, __maybe_unused K_TREE *trf_root) { __maybe_unused K_ITEM *i_file; __maybe_unused char reply[1024] = ""; __maybe_unused size_t siz = sizeof(reply); LOGDEBUG("%s(): cmd '%s'", __func__, cmd); // WARNING: This is a gaping security hole - only use in development LOGDEBUG("%s.disabled.dsp", id); return strdup("disabled.dsp"); /* i_file = require_name(trf_root, "file", 1, NULL, reply, siz); if (!i_file) return strdup(reply); dsp_ktree(blocks_free, blocks_root, transfer_data(i_file), NULL); dsp_ktree(transfer_free, trf_root, transfer_data(i_file), NULL); dsp_ktree(paymentaddresses_free, paymentaddresses_root, transfer_data(i_file), NULL); dsp_ktree(sharesummary_free, sharesummary_root, transfer_data(i_file), NULL); dsp_ktree(userstats_free, userstats_root, transfer_data(i_file), NULL); dsp_ktree(markersummary_free, markersummary_root, transfer_data(i_file), NULL); dsp_ktree(workmarkers_free, workmarkers_root, transfer_data(i_file), NULL); LOGDEBUG("%s.ok.dsp.file='%s'", id, transfer_data(i_file)); return strdup("ok.dsp"); */ } static char *cmd_stats(__maybe_unused PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by, __maybe_unused char *code, __maybe_unused char *inet, __maybe_unused tv_t *notcd, __maybe_unused K_TREE *trf_root) { char tmp[1024], *buf; size_t len, off; uint64_t ram, ram2, tot = 0; K_LIST *klist; int rows = 0; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); // Doesn't include blob memory // - average transactiontree length of ~119k I have is ~28k (>3.3GB) #define USEINFO(_obj, _stores, _trees) \ klist = _obj ## _free; \ ram = sizeof(K_LIST) + _stores * sizeof(K_STORE) + \ klist->allocate * klist->item_mem_count * klist->siz + \ sizeof(K_TREE) * (klist->total - klist->count) * _trees; \ ram2 = klist->ram; \ snprintf(tmp, sizeof(tmp), \ "name:%d=" #_obj "%cinitial:%d=%d%callocated:%d=%d%c" \ "store:%d=%d%ctrees:%d=%d%cram:%d=%"PRIu64"%c" \ "ram2:%d=%"PRIu64"%ccull:%d=%d%c", \ rows, FLDSEP, \ rows, klist->allocate, FLDSEP, \ rows, klist->total, FLDSEP, \ rows, klist->total - klist->count, FLDSEP, \ rows, _trees, FLDSEP, \ rows, ram, FLDSEP, \ rows, ram2, FLDSEP, \ rows, klist->cull_count, FLDSEP); \ APPEND_REALLOC(buf, off, len, tmp); \ tot += ram + ram2; \ rows++; USEINFO(users, 1, 2); USEINFO(useratts, 1, 1); USEINFO(workers, 1, 1); USEINFO(paymentaddresses, 1, 1); USEINFO(payments, 1, 1); USEINFO(idcontrol, 1, 0); USEINFO(optioncontrol, 1, 1); USEINFO(workinfo, 1, 1); USEINFO(shares, 1, 1); USEINFO(shareerrors, 1, 1); USEINFO(sharesummary, 1, 2); USEINFO(workmarkers, 1, 2); USEINFO(markersummary, 1, 2); USEINFO(marks, 1, 1); USEINFO(blocks, 1, 1); USEINFO(miningpayouts, 1, 1); USEINFO(auths, 1, 1); USEINFO(poolstats, 1, 1); USEINFO(userstats, 4, 2); USEINFO(workerstatus, 1, 1); USEINFO(workqueue, 1, 0); USEINFO(transfer, 0, 0); USEINFO(heartbeatqueue, 1, 0); USEINFO(logqueue, 1, 0); snprintf(tmp, sizeof(tmp), "totalram=%"PRIu64"%c", tot, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "name,initial,allocated,store,trees,ram,cull", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Stats", FLDSEP, ""); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.ok.%s...", id, cmd); return buf; } // TODO: add to heartbeat to disable the miner if active and status != "" static char *cmd_userstatus(PGconn *conn, char *cmd, char *id, tv_t *now, char *by, char *code, char *inet, __maybe_unused tv_t *cd, K_TREE *trf_root) { char reply[1024] = ""; size_t siz = sizeof(reply); K_ITEM *i_username, *i_userid, *i_status, *u_item; int64_t userid; char *status; USERS *users; bool ok; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = optional_name(trf_root, "username", 3, (char *)userpatt, reply, siz); i_userid = optional_name(trf_root, "userid", 1, (char *)intpatt, reply, siz); // Either username or userid if (!i_username && !i_userid) { snprintf(reply, siz, "failed.invalid/missing userinfo"); LOGERR("%s.%s", id, reply); return strdup(reply); } // A zero length status re-enables it i_status = require_name(trf_root, "status", 0, NULL, reply, siz); if (!i_status) return strdup(reply); status = transfer_data(i_status); K_RLOCK(users_free); if (i_username) u_item = find_users(transfer_data(i_username)); else { TXT_TO_BIGINT("userid", transfer_data(i_userid), userid); u_item = find_userid(userid); } K_RUNLOCK(users_free); if (!u_item) ok = false; else { ok = users_update(conn, u_item, NULL, NULL, NULL, by, code, inet, now, trf_root, status); } if (!ok) { LOGERR("%s() %s.failed.DBE", __func__, id); return strdup("failed.DBE"); } DATA_USERS(users, u_item); snprintf(reply, siz, "ok.updated %"PRId64" %s status %s", users->userid, users->username, status[0] ? "disabled" : "enabled"); LOGWARNING("%s.%s", id, reply); return strdup(reply); } /* Socket interface to the functions that will be used later to automatically * create marks, workmarkers and process the workmarkers * to generate markersummaries */ static char *cmd_marks(PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *now, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root) { char reply[1024] = ""; size_t siz = sizeof(reply); char tmp[1024] = ""; char msg[1024] = ""; K_ITEM *i_action, *i_workinfoid, *i_marktype, *i_description; K_ITEM *i_height, *i_status, *i_extra, *m_item, *b_item, *w_item; K_ITEM *wm_item, *wm_item_prev; WORKMARKERS *workmarkers; K_TREE_CTX ctx[1]; BLOCKS *blocks; MARKS *marks; char *action; int64_t workinfoid = -1; char *marktype; int32_t height = 0; char description[TXT_BIG+1] = { '\0' }; char extra[TXT_BIG+1] = { '\0' }; char status[TXT_FLAG+1] = { MARK_READY, '\0' }; bool ok; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_action = require_name(trf_root, "action", 1, NULL, reply, siz); if (!i_action) return strdup(reply); action = transfer_data(i_action); if (strcasecmp(action, "add") == 0) { /* Add a mark * Require marktype * Require workinfoid for all but 'b' * If marktype is 'b' or 'p' then require height/block (number) * If marktype is 'o' or 'f' then require description * Status optional - default READY */ i_marktype = require_name(trf_root, "marktype", 1, NULL, reply, siz); if (!i_marktype) return strdup(reply); marktype = transfer_data(i_marktype); if (marktype[0] != MARKTYPE_BLOCK) { i_workinfoid = require_name(trf_root, "workinfoid", 1, (char *)intpatt, reply, siz); if (!i_workinfoid) return strdup(reply); TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); } switch (marktype[0]) { case MARKTYPE_BLOCK: case MARKTYPE_PPLNS: i_height = require_name(trf_root, "height", 1, (char *)intpatt, reply, siz); if (!i_height) return strdup(reply); TXT_TO_INT("height", transfer_data(i_height), height); K_RLOCK(blocks_free); b_item = find_prev_blocks(height+1); K_RUNLOCK(blocks_free); if (b_item) { DATA_BLOCKS(blocks, b_item); if (blocks->height != height) b_item = NULL; } if (!b_item) { snprintf(reply, siz, "no blocks with height %"PRId32, height); return strdup(reply); } if (marktype[0] == MARKTYPE_BLOCK) workinfoid = blocks->workinfoid; if (!marks_description(description, sizeof(description), marktype, height, NULL, NULL)) goto dame; break; case MARKTYPE_SHIFT_BEGIN: case MARKTYPE_SHIFT_END: snprintf(reply, siz, "marktype %s not yet handled", marks_marktype(marktype)); return strdup(reply); case MARKTYPE_OTHER_BEGIN: case MARKTYPE_OTHER_FINISH: i_description = require_name(trf_root, "description", 1, NULL, reply, siz); if (!i_description) return strdup(reply); if (!marks_description(description, sizeof(description), marktype, height, NULL, transfer_data(i_description))) goto dame; break; default: snprintf(reply, siz, "unknown marktype '%s'", marktype); return strdup(reply); } i_status = optional_name(trf_root, "status", 1, NULL, reply, siz); if (i_status) { STRNCPY(status, transfer_data(i_status)); switch(status[0]) { case MARK_READY: case MARK_USED: case '\0': break; default: snprintf(reply, siz, "unknown mark status '%s'", status); return strdup(reply); } } if (workinfoid == -1) { snprintf(reply, siz, "workinfoid not found"); return strdup(reply); } w_item = find_workinfo(workinfoid, NULL); if (!w_item) { snprintf(reply, siz, "invalid workinfoid %"PRId64, workinfoid); return strdup(reply); } ok = marks_process(conn, true, EMPTY, workinfoid, description, extra, marktype, status, by, code, inet, cd, trf_root); } else if (strcasecmp(action, "expire") == 0) { /* Expire the mark - effectively deletes it * Require workinfoid */ i_workinfoid = require_name(trf_root, "workinfoid", 1, (char *)intpatt, reply, siz); if (!i_workinfoid) return strdup(reply); TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); K_RLOCK(marks_free); m_item = find_marks(workinfoid); K_RUNLOCK(marks_free); if (!m_item) { snprintf(reply, siz, "unknown current mark with workinfoid %"PRId64, workinfoid); return strdup(reply); } ok = marks_process(conn, false, EMPTY, workinfoid, NULL, NULL, NULL, NULL, by, code, inet, cd, trf_root); } else if (strcasecmp(action, "status") == 0) { /* Change the status on a mark * Require workinfoid and status * N.B. you can cause generate errors if you change the status of a USED marks */ i_workinfoid = require_name(trf_root, "workinfoid", 1, (char *)intpatt, reply, siz); if (!i_workinfoid) return strdup(reply); TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); K_RLOCK(marks_free); m_item = find_marks(workinfoid); K_RUNLOCK(marks_free); if (!m_item) { snprintf(reply, siz, "unknown current mark with workinfoid %"PRId64, workinfoid); return strdup(reply); } DATA_MARKS(marks, m_item); i_status = require_name(trf_root, "status", 0, NULL, reply, siz); if (!i_status) return strdup(reply); STRNCPY(status, transfer_data(i_status)); switch(status[0]) { case MARK_READY: case MARK_USED: case '\0': break; default: snprintf(reply, siz, "unknown mark status '%s'", status); return strdup(reply); } // Unchanged if (strcmp(status, marks->status) == 0) { action = "status-unchanged"; ok = true; } else { ok = marks_process(conn, true, marks->poolinstance, workinfoid, marks->description, marks->extra, marks->marktype, status, by, code, inet, cd, trf_root); } } else if (strcasecmp(action, "extra") == 0) { /* Change the 'extra' description * Require workinfoid and extra * If a mark is actually multiple marks with the same * workinfoid, then we can record the extra info here * This would be true of each block, once shifts are * implemented, since the current shift ends when a * block is found * This could also be true, very rarely, if the beginning * of a pplns payout range matched any other mark, * since the beginning can be any workinfoid */ i_workinfoid = require_name(trf_root, "workinfoid", 1, (char *)intpatt, reply, siz); if (!i_workinfoid) return strdup(reply); TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); K_RLOCK(marks_free); m_item = find_marks(workinfoid); K_RUNLOCK(marks_free); if (!m_item) { snprintf(reply, siz, "unknown current mark with workinfoid %"PRId64, workinfoid); return strdup(reply); } DATA_MARKS(marks, m_item); i_extra = require_name(trf_root, "extra", 0, NULL, reply, siz); if (!i_extra) return strdup(reply); STRNCPY(extra, transfer_data(i_extra)); // Unchanged if (strcmp(extra, marks->extra) == 0) { action = "extra-unchanged"; ok = true; } else { ok = marks_process(conn, true, marks->poolinstance, workinfoid, marks->description, extra, marks->marktype, status, by, code, inet, cd, trf_root); } } else if (strcasecmp(action, "generate") == 0) { /* Generate workmarkers * No parameters */ tmp[0] = '\0'; ok = workmarkers_generate(conn, tmp, sizeof(tmp), by, code, inet, cd, trf_root, true); if (!ok) { snprintf(reply, siz, "%s error: %s", action, tmp); LOGERR("%s.%s", id, reply); return strdup(reply); } if (*tmp) { snprintf(reply, siz, "%s: %s", action, tmp); LOGWARNING("%s.%s", id, reply); } } else if (strcasecmp(action, "expunge") == 0) { /* Expire all generated workmarkers that aren't PROCESSED * No parameters * This exists so we can fix all workmarkers that haven't * been PROCESSED yet, * if there was a problem with the marks * Simply expunge all the workmarkers, correct the marks, * then generate the workmarkers again * WARNING - using psql to do the worksummary generation * will not update the workmarkers status inside ckdb * so this will expunge those worksummary records also * You'll need to restart ckdb after using psql */ int count = 0; ok = true; wm_item_prev = NULL; K_RLOCK(workmarkers_free); wm_item = last_in_ktree(workmarkers_root, ctx); K_RUNLOCK(workmarkers_free); while (wm_item) { K_RLOCK(workmarkers_free); wm_item_prev = prev_in_ktree(ctx); K_RUNLOCK(workmarkers_free); DATA_WORKMARKERS(workmarkers, wm_item); if (CURRENT(&(workmarkers->expirydate)) && !WMPROCESSED(workmarkers->status)) { ok = workmarkers_process(conn, false, workmarkers->markerid, NULL, 0, 0, NULL, NULL, by, code, inet, cd, trf_root); if (!ok) break; count++; } wm_item = wm_item_prev; } if (ok) { if (count == 0) { snprintf(msg, sizeof(msg), "no unprocessed current workmarkers"); } else { snprintf(msg, sizeof(msg), "%d workmarkers expunged", count); } } } else { snprintf(reply, siz, "unknown action '%s'", action); LOGERR("%s.%s", id, reply); return strdup(reply); } if (!ok) { dame: LOGERR("%s() %s.failed.DBE", __func__, id); return strdup("failed.DBE"); } if (msg[0]) snprintf(reply, siz, "ok.%s %s", action, msg); else snprintf(reply, siz, "ok.%s", action); LOGWARNING("%s.%s", id, reply); return strdup(reply); } // TODO: limit access by having seperate sockets for each #define ACCESS_POOL "p" #define ACCESS_SYSTEM "s" #define ACCESS_WEB "w" #define ACCESS_PROXY "x" #define ACCESS_CKDB "c" /* The socket command format is as follows: * Basic structure: * cmd.ID.fld1=value1 FLDSEP fld2=value2 FLDSEP fld3=... * cmd is the cmd_str from the table below * ID is a string of anything but '.' - preferably just digits and/or letters * FLDSEP is a single character macro - defined in the code near the top * no spaces around FLDSEP - they are added above for readability * i.e. it's really: cmd.ID.fld1=value1FLDSEPfld2... * fldN names cannot contain '=' or FLDSEP * valueN values cannot contain FLDSEP except for the json field (see below) * * The reply will be ID.timestamp.status.information... * Status 'ok' means it succeeded * Some cmds you can optionally send as just 'cmd' if 'noid' below is true * then the reply will be .timestamp.status.information * i.e. a zero length 'ID' at the start of the reply * * Data from ckpool starts with a fld1: json={...} of field data * This is assumed to be the only field data sent and any other fields after * it will cause a json error * Any fields before it will circumvent the json interpretation of {...} and * the full json in {...} will be stored as text in TRANSFER under the name * 'json' - which will (usually) mean the command will fail if it requires * actual field data * * Examples of the commands not from ckpool with an example reply * STAMP is the unix timestamp in seconds * With no ID: * ping * .STAMP.ok.pong * * shutdown * .STAMP.ok.exiting * * With an ID * In each case the ID in these examples, also returned, is 'ID' which can * of course be most any string, as stated above * For commands with multiple fld=value the space between them must be typed * as a TAB * ping.ID * ID.STAMP.ok.pong * * newid.ID.idname=fooid idvalue=1234 * ID.STAMP.ok.added fooid 1234 * * loglevel is a special case to make it quick and easy to use: * loglevel.ID * sets the loglevel to atoi(ID) * Without an ID, it just reports the current value * * createdate = true * means that the data sent must contain a fld or json fld called createdate * * The reply format for authorise, addrauth and heartbeat includes json: * ID.STAMP.ok.cmd={json} * where cmd is auth, addrauth, or heartbeat * For the heartbeat pulse reply it has no '={}' */ // cmd_val cmd_str noid createdate func access struct CMDS ckdb_cmds[] = { { CMD_SHUTDOWN, "shutdown", true, false, NULL, ACCESS_SYSTEM }, { CMD_PING, "ping", true, false, NULL, ACCESS_SYSTEM ACCESS_POOL ACCESS_WEB }, { CMD_VERSION, "version", true, false, NULL, ACCESS_SYSTEM ACCESS_POOL ACCESS_WEB }, { CMD_LOGLEVEL, "loglevel", true, false, NULL, ACCESS_SYSTEM }, { CMD_SHARELOG, STR_WORKINFO, false, true, cmd_sharelog, ACCESS_POOL }, { CMD_SHARELOG, STR_SHARES, false, true, cmd_sharelog, ACCESS_POOL }, { CMD_SHARELOG, STR_SHAREERRORS, false, true, cmd_sharelog, ACCESS_POOL }, { CMD_SHARELOG, STR_AGEWORKINFO, false, true, cmd_sharelog, ACCESS_POOL }, { CMD_AUTH, "authorise", false, true, cmd_auth, ACCESS_POOL }, { CMD_ADDRAUTH, "addrauth", false, true, cmd_addrauth, ACCESS_POOL }, { CMD_HEARTBEAT,"heartbeat", false, true, cmd_heartbeat, ACCESS_POOL }, { CMD_ADDUSER, "adduser", false, false, cmd_adduser, ACCESS_WEB }, { CMD_NEWPASS, "newpass", false, false, cmd_newpass, ACCESS_WEB }, { CMD_CHKPASS, "chkpass", false, false, cmd_chkpass, ACCESS_WEB }, { CMD_USERSET, "usersettings", false, false, cmd_userset, ACCESS_WEB }, { CMD_WORKERSET,"workerset", false, false, cmd_workerset, ACCESS_WEB }, { CMD_POOLSTAT, "poolstats", false, true, cmd_poolstats, ACCESS_POOL }, { CMD_USERSTAT, "userstats", false, true, cmd_userstats, ACCESS_POOL }, { CMD_WORKERSTAT,"workerstats", false, true, cmd_workerstats,ACCESS_POOL }, { CMD_BLOCK, "block", false, true, cmd_blocks, ACCESS_POOL }, { CMD_BLOCKLIST,"blocklist", false, false, cmd_blocklist, ACCESS_WEB }, { CMD_BLOCKSTATUS,"blockstatus",false, false, cmd_blockstatus,ACCESS_WEB }, { CMD_NEWID, "newid", false, false, cmd_newid, ACCESS_SYSTEM }, { CMD_PAYMENTS, "payments", false, false, cmd_payments, ACCESS_WEB }, { CMD_WORKERS, "workers", false, false, cmd_workers, ACCESS_WEB }, { CMD_ALLUSERS, "allusers", false, false, cmd_allusers, ACCESS_WEB }, { CMD_HOMEPAGE, "homepage", false, false, cmd_homepage, ACCESS_WEB }, { CMD_GETATTS, "getatts", false, false, cmd_getatts, ACCESS_WEB }, { CMD_SETATTS, "setatts", false, false, cmd_setatts, ACCESS_WEB }, { CMD_EXPATTS, "expatts", false, false, cmd_expatts, ACCESS_WEB }, { CMD_GETOPTS, "getopts", false, false, cmd_getopts, ACCESS_WEB }, { CMD_SETOPTS, "setopts", false, false, cmd_setopts, ACCESS_WEB }, { CMD_DSP, "dsp", false, false, cmd_dsp, ACCESS_SYSTEM }, { CMD_STATS, "stats", true, false, cmd_stats, ACCESS_SYSTEM ACCESS_WEB }, { CMD_PPLNS, "pplns", false, false, cmd_pplns, ACCESS_SYSTEM ACCESS_WEB }, { CMD_USERSTATUS,"userstatus", false, false, cmd_userstatus, ACCESS_SYSTEM ACCESS_WEB }, { CMD_MARKS, "marks", false, false, cmd_marks, ACCESS_SYSTEM }, { CMD_END, NULL, false, false, NULL, NULL } };