/* * Copyright 1995-2016 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" /* * Allow overriding the username however the username must still be present * This should ONLY be used for web reporting cmds i.e. read only * Current PHP allows this for a hard coded user */ static K_ITEM *adminuser(K_TREE *trf_root, char *reply, size_t siz) { K_ITEM *i_username, *i_admin; char reply2[1024] = ""; i_username = require_name(trf_root, "username", MIN_USERNAME, (char *)userpatt, reply, siz); if (!i_username) return NULL; i_admin = optional_name(trf_root, "admin", MIN_USERNAME, (char *)userpatt, reply2, sizeof(reply2)); if (i_admin) return i_admin; return i_username; } 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; int event = EVENT_OK; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name(trf_root, "username", MIN_USERNAME, (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); event = events_add(EVENTID_CREACC, trf_root); if (event == EVENT_OK) { u_item = users_add(conn, transfer_data(i_username), transfer_data(i_emailaddress), transfer_data(i_passwordhash), 0, by, code, inet, now, trf_root); } } if (!u_item) { LOGERR("%s() %s.failed.DBE", __func__, id); return reply_event(event, "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, *i_2fa, *u_item; char reply[1024] = ""; size_t siz = sizeof(reply); int event = EVENT_OK; bool ok = true; char *oldhash; int32_t value; USERS *users; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name(trf_root, "username", MIN_USERNAME, (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; } i_2fa = require_name(trf_root, "2fa", 1, (char *)intpatt, reply, siz); if (!i_2fa) { event = events_add(EVENTID_INV2FA, trf_root); return reply_event(event, reply); } 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) { DATA_USERS(users, u_item); if (USER_TOTP_ENA(users)) { value = (int32_t)atoi(transfer_data(i_2fa)); ok = check_2fa(users, value); if (!ok) event = events_add(EVENTID_WRONG2FA, trf_root); } if (ok) { ok = users_update(NULL, u_item, oldhash, transfer_data(i_newhash), NULL, by, code, inet, now, trf_root, NULL, &event); } } else ok = false; } if (!ok) { LOGERR("%s.failed.%s", id, transfer_data(i_username)); return reply_event(event, "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, *i_2fa, *u_item; char reply[1024] = ""; size_t siz = sizeof(reply); int event = EVENT_OK; USERS *users; bool ok; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name(trf_root, "username", MIN_USERNAME, (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); i_2fa = require_name(trf_root, "2fa", 1, (char *)intpatt, reply, siz); if (!i_2fa) { event = events_add(EVENTID_INV2FA, trf_root); return reply_event(event, reply); } K_RLOCK(users_free); u_item = find_users(transfer_data(i_username)); K_RUNLOCK(users_free); if (!u_item) { event = events_add(EVENTID_INVUSER, trf_root); ok = false; } else { DATA_USERS(users, u_item); ok = check_hash(users, transfer_data(i_passwordhash)); if (!ok) event = events_add(EVENTID_PASSFAIL, trf_root); if (ok && USER_TOTP_ENA(users)) { uint32_t value = (int32_t)atoi(transfer_data(i_2fa)); ok = check_2fa(users, value); if (!ok) event = events_add(EVENTID_WRONG2FA, trf_root); } } if (!ok) { LOGERR("%s.failed.%s", id, transfer_data(i_username)); return reply_event(event, "failed."); } LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); return strdup("ok."); } static char *cmd_2fa(__maybe_unused 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_action, *i_entropy, *i_value, *u_item, *u_new; char reply[1024] = ""; size_t siz = sizeof(reply); int event = EVENT_OK; size_t len, off; char tmp[1024]; int32_t entropy, value; USERS *users; char *action, *buf = NULL, *st = NULL; char *sfa_status = EMPTY, *sfa_error = EMPTY, *sfa_msg = EMPTY; bool ok = false, key = false; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name(trf_root, "username", MIN_USERNAME, (char *)userpatt, reply, siz); if (!i_username) return strdup(reply); // Field always expected, blank means to report the status i_action = require_name(trf_root, "action", 0, NULL, reply, siz); if (!i_action) return strdup(reply); action = transfer_data(i_action); /* Field always expected with a value, * but the value is only used when generating a Secret Key */ i_entropy = require_name(trf_root, "entropy", 1, (char *)intpatt, reply, siz); if (!i_entropy) return strdup(reply); // Field always expected, use 0 if not required i_value = require_name(trf_root, "value", 1, (char *)intpatt, reply, siz); if (!i_value) { event = events_add(EVENTID_INV2FA, trf_root); return reply_event(event, reply); } K_RLOCK(users_free); u_item = find_users(transfer_data(i_username)); K_RUNLOCK(users_free); if (u_item) { DATA_USERS(users, u_item); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); switch (users->databits & (USER_TOTPAUTH | USER_TEST2FA)) { case 0: break; case USER_TOTPAUTH: sfa_status = "ok"; break; case (USER_TOTPAUTH | USER_TEST2FA): sfa_status = "test"; key = true; break; default: // USER_TEST2FA only <- currently invalid LOGERR("%s() users databits invalid for " "'%s/%"PRId64, __func__, st = safe_text_nonull(users->username), users->databits); FREENULL(st); goto dame; } if (!*action) { ok = true; } else if (strcmp(action, "setup") == 0) { // Can't setup if anything is already present -> new if (users->databits & (USER_TOTPAUTH | USER_TEST2FA)) goto dame; entropy = (int32_t)atoi(transfer_data(i_entropy)); u_new = gen_2fa_key(u_item, entropy, by, code, inet, now, trf_root); if (u_new) { ok = true; sfa_status = "test"; key = true; u_item = u_new; DATA_USERS(users, u_item); } } else if (strcmp(action, "test") == 0) { // Can't test if it's not ready to test if ((users->databits & (USER_TOTPAUTH | USER_TEST2FA)) != (USER_TOTPAUTH | USER_TEST2FA)) goto dame; value = (int32_t)atoi(transfer_data(i_value)); ok = tst_2fa(u_item, value, by, code, inet, now, trf_root); if (!ok) sfa_error = "Invalid code"; else { key = false; sfa_status = "ok"; sfa_msg = "2FA Enabled"; } // Report sfa_error to web ok = true; } else if (strcmp(action, "untest") == 0) { // Can't untest if it's not ready to test if ((users->databits & (USER_TOTPAUTH | USER_TEST2FA)) != (USER_TOTPAUTH | USER_TEST2FA)) goto dame; // since it's currently test, the value isn't required u_new = remove_2fa(u_item, 0, by, code, inet, now, trf_root, false); if (u_new) { ok = true; sfa_status = EMPTY; key = false; sfa_msg = "2FA Cancelled"; } } else if (strcmp(action, "new") == 0) { // Can't new if 2FA isn't already present -> setup if ((users->databits & USER_TOTPAUTH) == 0) goto dame; value = (int32_t)atoi(transfer_data(i_value)); if (!check_2fa(users, value)) { event = events_add(EVENTID_WRONG2FA, trf_root); sfa_error = "Invalid code"; // Report sfa_error to web ok = true; } else { entropy = (int32_t)atoi(transfer_data(i_entropy)); u_new = gen_2fa_key(u_item, entropy, by, code, inet, now, trf_root); if (u_new) { ok = true; sfa_status = "test"; key = true; u_item = u_new; DATA_USERS(users, u_item); } } } else if (strcmp(action, "remove") == 0) { // Can't remove if 2FA isn't already present if (!(users->databits & (USER_TOTPAUTH | USER_TEST2FA))) goto dame; // remove requires value value = (int32_t)atoi(transfer_data(i_value)); if (!check_2fa(users, value)) { event = events_add(EVENTID_WRONG2FA, trf_root); sfa_error = "Invalid code"; // Report sfa_error to web ok = true; } else { /* already tested 2fa so don't retest, also, * a retest will fail using the same value */ u_new = remove_2fa(u_item, value, by, code, inet, now, trf_root, false); if (u_new) { ok = true; sfa_status = EMPTY; key = false; sfa_msg = "2FA Removed"; } } } if (key) { char *keystr, *issuer = "KanoCKDB"; char cd_buf[DATE_BUFSIZ]; unsigned char *bin; OPTIONCONTROL *oc; K_ITEM *oc_item; size_t binlen; bin = users_userdata_get_bin(users, USER_TOTPAUTH_NAME, USER_TOTPAUTH, &binlen); if (binlen != TOTPAUTH_KEYSIZE) { LOGERR("%s() invalid key for '%s/%s " "len(%d) != %d", __func__, st = safe_text_nonull(users->username), USER_TOTPAUTH_NAME, (int)binlen, TOTPAUTH_KEYSIZE); FREENULL(st); } if (bin && binlen == TOTPAUTH_KEYSIZE) { keystr = tob32(users, bin, binlen, USER_TOTPAUTH_NAME, TOTPAUTH_DSP_KEYSIZE); snprintf(tmp, sizeof(tmp), "2fa_key=%s%c", keystr, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); FREENULL(keystr); oc_item = find_optioncontrol(TOTPAUTH_ISSUER, now, OPTIONCONTROL_HEIGHT); if (oc_item) { DATA_OPTIONCONTROL(oc, oc_item); issuer = oc->optionvalue; } else { tv_to_buf(now, cd_buf, sizeof(cd_buf)); LOGEMERG("%s(): missing optioncontrol " "%s (%s/%d)", __func__, TOTPAUTH_ISSUER, cd_buf, OPTIONCONTROL_HEIGHT); } // TODO: add issuer to optioncontrol snprintf(tmp, sizeof(tmp), "2fa_auth=%s%c2fa_hash=%s%c" "2fa_time=%d%c2fa_issuer=%s%c", TOTPAUTH_AUTH, FLDSEP, TOTPAUTH_HASH, FLDSEP, TOTPAUTH_TIME, FLDSEP, issuer, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } FREENULL(bin); } } if (!ok) { dame: // Only db/php/code errors should get here LOGERR("%s.failed.%s-%s", id, transfer_data(i_username), action); FREENULL(buf); return reply_event(event, "failed."); } snprintf(tmp, sizeof(tmp), "2fa_status=%s%c2fa_error=%s%c2fa_msg=%s", sfa_status, FLDSEP, sfa_error, FLDSEP, sfa_msg); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.%s-%s.%s", id, transfer_data(i_username), action, buf); return reply_event_free(event, buf); } 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_2fa, *i_rows, *i_address; K_ITEM *i_ratio, *i_payname, *i_email, *u_item, *pa_item, *old_pa_item; K_ITEM *ua_item = NULL; USERATTS *useratts = NULL; char *email, *address, *payname; char reply[1024] = ""; size_t siz = sizeof(reply); int event = EVENT_OK; 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, limit; bool ok; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = require_name(trf_root, "username", MIN_USERNAME, (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) { event = events_add(EVENTID_UNKATTS, trf_root); 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; } K_RLOCK(useratts_free); ua_item = find_useratts(users->userid, USER_MULTI_PAYOUT); K_RUNLOCK(useratts_free); if (!ua_item) limit = 1; else { DATA_USERATTS(useratts, ua_item); if (useratts->attnum > 0) limit = (int)(useratts->attnum); else limit = USER_ADDR_LIMIT; } 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); snprintf(tmp, sizeof(tmp), "limit=%d%c", limit, 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); snprintf(tmp, sizeof(tmp), "payname:%d=%s%c", rows, row->payname, 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,payname", FLDSEP); APPEND_REALLOC(answer, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "PaymentAddresses", FLDSEP, ""); APPEND_REALLOC(answer, off, len, tmp); } else { i_2fa = require_name(trf_root, "2fa", 1, (char *)intpatt, reply, siz); if (!i_2fa) { event = events_add(EVENTID_INV2FA, trf_root); reason = "Invalid data"; goto struckout; } if (!check_hash(users, transfer_data(i_passwordhash))) { event = events_add(EVENTID_PASSFAIL, trf_root); reason = "Incorrect password"; goto struckout; } if (USER_TOTP_ENA(users)) { uint32_t value = (int32_t)atoi(transfer_data(i_2fa)); if (!check_2fa(users, value)) { event = events_add(EVENTID_WRONG2FA, trf_root); reason = "Invalid data"; 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); // discard any extras above the limit for (i = 0; i < rows && pa_store->count < limit; 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); event = events_add(EVENTID_INCBTC, trf_root); reason = "Invalid address"; goto struckout; } address = transfer_data(i_address); pa_item = STORE_HEAD_NOLOCK(pa_store); 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; } snprintf(tmp, sizeof(tmp), "payname:%d", i); i_payname = optional_name(trf_root, tmp, 0, NULL, reply, siz); if (i_payname) payname = transfer_data(i_payname); else payname = EMPTY; pa_item = k_unlink_head(paymentaddresses_free); DATA_PAYMENTADDRESSES(row, pa_item); bzero(row, sizeof(*row)); STRNCPY(row->payaddress, address); row->payratio = ratio; STRNCPY(row->payname, payname); 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 = STORE_HEAD_NOLOCK(pa_store); while (pa_item) { DATA_PAYMENTADDRESSES(row, pa_item); // Only EVER validate addresses once ... for now K_RLOCK(paymentaddresses_free); old_pa_item = find_any_payaddress(row->payaddress); K_RUNLOCK(paymentaddresses_free); 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) { event = events_add(EVENTID_BTCUSED, trf_root); reason = "Unavailable BTC address"; goto struckout; } } else if (!btc_valid_address(row->payaddress)) { event = events_add(EVENTID_INVBTC, trf_root); 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, &event); 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) { char *user, *st = NULL; snprintf(reply, siz, "ERR.%s", reason); if (i_username) user = st = safe_text(transfer_data(i_username)); else user = EMPTY; LOGERR("%s.%s.%s (%s)", cmd, id, reply, user); FREENULL(st); return reply_event(event, 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 reply_event_free(event, ret); } static char *cmd_workerset(PGconn *conn, char *cmd, char *id, tv_t *now, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root) { K_ITEM *i_username, *i_workername, *i_diffdef, *i_oldworkers; K_ITEM *u_item, *ua_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); USERATTS *useratts; 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", MIN_USERNAME, (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"); i_oldworkers = optional_name(trf_root, "oldworkers", 1, NULL, reply, siz); if (i_oldworkers) { bool update = false; int64_t new_ow = atol(transfer_data(i_oldworkers)); K_RLOCK(useratts_free); ua_item = find_useratts(users->userid, USER_OLD_WORKERS); K_RUNLOCK(useratts_free); if (!ua_item) { if (new_ow != USER_OLD_WORKERS_DEFAULT) update = true; } else { DATA_USERATTS(useratts, ua_item); if (new_ow != useratts->attnum) update = true; } if (update) { ua_item = useratts_add(conn, users->username, USER_OLD_WORKERS, EMPTY, EMPTY, EMPTY, transfer_data(i_oldworkers), EMPTY, EMPTY, EMPTY, by, code, inet, cd, trf_root, false); if (!ua_item) reason = "Invalid"; } goto kazuki; } // 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(false, 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); } } } kazuki: 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); if (poolinstance && strcmp(poolinstance, transfer_data(i_poolinstance))) { POOLINSTANCE_DATA_SET(poolstats, transfer_data(i_poolinstance)); return strdup(FAILED_PI); } 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); K_RLOCK(poolstats_free); ps = find_before_in_ktree(poolstats_root, &look, ctx); K_RUNLOCK(poolstats_free); 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) { if (!igndup) 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 * We don't care about dups during reload since poolstats_fill() * doesn't load all the data */ if (reloading) igndup = true; 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); if (poolinstance && strcmp(poolinstance, transfer_data(i_poolinstance))) { POOLINSTANCE_DATA_SET(userstats, transfer_data(i_poolinstance)); return strdup(FAILED_PI); } 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, *i_instances; 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); if (poolinstance && strcmp(poolinstance, transfer_data(i_poolinstance))) { POOLINSTANCE_DATA_SET(workerstats, transfer_data(i_poolinstance)); return strdup(FAILED_PI); } 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); i_instances = optional_name(trf_root, "instances", 1, NULL, reply, siz); 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, i_instances ? transfer_data(i_instances) : NULL, 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, 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; BLOCKS *blocks; char reply[1024] = ""; char tmp[1024]; char *buf, *desc, desc_buf[64]; size_t len, off; tv_t stats_tv = {0,0}, stats_tv2 = {0,0}; int rows, srows, tot, seq; int64_t maxrows; bool has_stats; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); maxrows = sys_setting(BLOCKS_SETTING_NAME, BLOCKS_DEFAULT, now); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); redo: has_stats = check_update_blocks_stats(&stats_tv); srows = rows = 0; K_RLOCK(blocks_free); b_item = first_in_ktree(blocks_root, ctx); tot = 0; while (b_item) { DATA_BLOCKS(blocks, b_item); if (CURRENT(&(blocks->expirydate))) { if (blocks->confirmed[0] != BLOCKS_ORPHAN && blocks->confirmed[0] != BLOCKS_REJECT) tot++; } b_item = next_in_ktree(ctx); } seq = tot; b_item = last_in_ktree(blocks_root, ctx); while (b_item && rows < (int)maxrows) { DATA_BLOCKS(blocks, b_item); if (CURRENT(&(blocks->expirydate))) { if (blocks->confirmed[0] == BLOCKS_ORPHAN || blocks->confirmed[0] == BLOCKS_REJECT) { snprintf(tmp, sizeof(tmp), "seq:%d=%c%c", rows, blocks->confirmed[0] == BLOCKS_ORPHAN ? 'o' : 'r', FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } else { snprintf(tmp, sizeof(tmp), "seq:%d=%d%c", rows, seq--, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } 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); // When block was found snprintf(tmp, sizeof(tmp), "first"CDTRF":%d=%ld%c", rows, blocks->blockcreatedate.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); // Last time block was updated snprintf(tmp, sizeof(tmp), CDTRF":%d=%ld%c", rows, blocks->createdate.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); // When previous valid block was found snprintf(tmp, sizeof(tmp), "prev"CDTRF":%d=%ld%c", rows, blocks->prevcreatedate.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "confirmed:%d=%s%cstatus:%d=%s%c", rows, blocks->confirmed, FLDSEP, rows, blocks_confirmed(blocks->confirmed), FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "info:%d=%s%c", rows, blocks->info, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "statsconf:%d=%s%c", rows, blocks->statsconfirmed, 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); if (has_stats) { snprintf(tmp, sizeof(tmp), "netdiff:%d=%.8f%cdiffratio:%d=%.8f%c" "cdf:%d=%.8f%cluck:%d=%.8f%c", rows, blocks->netdiff, FLDSEP, rows, blocks->blockdiffratio, FLDSEP, rows, blocks->blockcdf, FLDSEP, rows, blocks->blockluck, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } else { snprintf(tmp, sizeof(tmp), "netdiff:%d=?%cdiffratio:%d=?%c" "cdf:%d=?%cluck:%d=?%c", rows, FLDSEP, rows, FLDSEP, rows, FLDSEP, rows, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } rows++; } b_item = prev_in_ktree(ctx); } if (has_stats) { seq = tot; b_item = last_in_ktree(blocks_root, ctx); while (b_item) { DATA_BLOCKS(blocks, b_item); if (CURRENT(&(blocks->expirydate)) && blocks->confirmed[0] != BLOCKS_ORPHAN && blocks->confirmed[0] != BLOCKS_REJECT) { desc = NULL; if (seq == 1) { snprintf(desc_buf, sizeof(desc_buf), "All - Last %d", tot); desc = desc_buf; } else if (seq == tot - 4) { desc = "Last 5"; } else if (seq == tot - 9) { desc = "Last 10"; } else if (seq == tot - 24) { desc = "Last 25"; } else if (seq == tot - 49) { desc = "Last 50"; } else if (seq == tot - 99) { desc = "Last 100"; } else if (seq == tot - 249) { desc = "Last 250"; } else if (seq == tot - 499) { desc = "Last 500"; } else if (seq == tot - 999) { desc = "Last 1000"; } if (desc) { snprintf(tmp, sizeof(tmp), "s_seq:%d=%d%c" "s_desc:%d=%s%c" "s_height:%d=%d%c" "s_"CDTRF":%d=%ld%c" "s_prev"CDTRF":%d=%ld%c" "s_diffratio:%d=%.8f%c" "s_diffmean:%d=%.8f%c" "s_cdferl:%d=%.8f%c" "s_luck:%d=%.8f%c" "s_txmean:%d=%.8f%c", srows, seq, FLDSEP, srows, desc, FLDSEP, srows, (int)(blocks->height), FLDSEP, srows, blocks->blockcreatedate.tv_sec, FLDSEP, srows, blocks->prevcreatedate.tv_sec, FLDSEP, srows, blocks->diffratio, FLDSEP, srows, blocks->diffmean, FLDSEP, srows, blocks->cdferl, FLDSEP, srows, blocks->luck, FLDSEP, srows, blocks->txmean, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); srows++; } seq--; } b_item = prev_in_ktree(ctx); } copy_tv(&stats_tv2, &blocks_stats_time); } K_RUNLOCK(blocks_free); // Only check for a redo if we used the stats values if (has_stats) { /* If the stats changed then redo with the new corrected values * This isn't likely at all, but it guarantees the blocks * page shows correct information since any code that wants * to modify the blocks table must have it under write lock * then flag the stats as needing to be recalculated */ if (!tv_equal(&stats_tv, &stats_tv2)) { APPEND_REALLOC_RESET(buf, off); goto redo; } } snprintf(tmp, sizeof(tmp), "s_rows=%d%cs_flds=%s%c", srows, FLDSEP, "s_seq,s_desc,s_height,s_"CDTRF",s_prev"CDTRF",s_diffratio," "s_diffmean,s_cdferl,s_luck,s_txmean", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "seq,height,blockhash,nonce,reward,workername,first"CDTRF"," CDTRF",prev"CDTRF",confirmed,status,info,statsconf,diffacc," "diffinv,shareacc,shareinv,elapsed,netdiff,diffratio,cdf,luck", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "Blocks,BlockStats", FLDSEP, ",s"); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.ok.%d_blocks", id, rows); return buf; } static char *cmd_blockstatus(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, *i_info; char reply[1024] = ""; size_t siz = sizeof(reply); K_ITEM *b_item; BLOCKS *blocks; int32_t height; char *action, *info, *tmp; 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), NULL); 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); // Default to previous value info = blocks->info; if (strcasecmp(action, "orphan") == 0) { switch (blocks->confirmed[0]) { case BLOCKS_NEW: case BLOCKS_CONFIRM: ok = blocks_add(conn, height, blocks->blockhash, BLOCKS_ORPHAN_STR, info, 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, "reject") == 0) { i_info = require_name(trf_root, "info", 0, (char *)strpatt, reply, siz); if (!i_info) return strdup(reply); tmp = transfer_data(i_info); /* Override if not empty * Thus you can't blank it out if current has a value */ if (tmp && *tmp) info = tmp; switch (blocks->confirmed[0]) { case BLOCKS_NEW: case BLOCKS_CONFIRM: case BLOCKS_ORPHAN: case BLOCKS_REJECT: ok = blocks_add(conn, height, blocks->blockhash, BLOCKS_REJECT_STR, info, 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, height, blocks->blockhash, BLOCKS_CONFIRM_STR, info, 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, *u_item, *p_item, *p2_item, *po_item; K_TREE_CTX ctx[1]; K_STORE *pay_store; PAYMENTS *payments, *last_payments = NULL; PAYOUTS *payouts; USERS *users; char reply[1024] = ""; char tmp[1024]; size_t siz = sizeof(reply); char *buf; size_t len, off; int rows; bool pok; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = adminuser(trf_root, 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); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); rows = 0; pay_store = k_new_store(payments_free); K_WLOCK(payments_free); p_item = find_first_payments(users->userid, ctx); DATA_PAYMENTS_NULL(payments, p_item); /* TODO: allow to see details of a single payoutid * if it has multiple items (percent payout user) */ while (p_item && payments->userid == users->userid) { if (CURRENT(&(payments->expirydate))) { if (!last_payments || payments->payoutid != last_payments->payoutid) { p2_item = k_unlink_head(payments_free); DATA_PAYMENTS_NULL(last_payments, p2_item); memcpy(last_payments, payments, sizeof(*last_payments)); k_add_tail(pay_store, p2_item); } else { STRNCPY(last_payments->payaddress, "*Multiple"); last_payments->amount += payments->amount; } } p_item = next_in_ktree(ctx); DATA_PAYMENTS_NULL(payments, p_item); } K_WUNLOCK(payments_free); p_item = STORE_HEAD_NOLOCK(pay_store); while (p_item) { DATA_PAYMENTS(payments, p_item); pok = false; K_RLOCK(payouts_free); po_item = find_payoutid(payments->payoutid); DATA_PAYOUTS_NULL(payouts, po_item); if (p_item && PAYGENERATED(payouts->status)) pok = true; K_RUNLOCK(payouts_free); if (pok) { bigint_to_buf(payouts->payoutid, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "payoutid:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); int_to_buf(payouts->height, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "height:%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); 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); rows++; } p_item = p_item->next; } snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "payoutid,height,payaddress,amount,paydate", 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], pay_ctx[1]; WORKERS lookworkers, *workers; WORKERSTATUS *workerstatus; USERSTATS *userstats; char tmp[1024]; char *buf; size_t len, off; int rows; 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'; DATE_ZERO(&(lookworkers.expirydate)); w_look.data = (void *)(&lookworkers); K_RLOCK(workers_free); w_item = find_after_in_ktree(workers_root, &w_look, w_ctx); K_RUNLOCK(workers_free); DATA_WORKERS_NULL(workers, w_item); while (w_item && workers->userid == users->userid) { if (CURRENT(&(workers->expirydate))) { K_RLOCK(workerstatus_free); ws_item = find_workerstatus(true, users->userid, workers->workername); if (ws_item) { DATA_WORKERSTATUS(workerstatus, ws_item); t_diffacc += workerstatus->block_diffacc; t_diffinv += workerstatus->block_diffinv; t_diffsta += workerstatus->block_diffsta; t_diffdup += workerstatus->block_diffdup; t_diffhi += workerstatus->block_diffhi; t_diffrej += workerstatus->block_diffrej; t_shareacc += workerstatus->block_shareacc; t_shareinv += workerstatus->block_shareinv; t_sharesta += workerstatus->block_sharesta; t_sharedup += workerstatus->block_sharedup; t_sharehi += workerstatus->block_sharehi; t_sharerej += workerstatus->block_sharerej; } K_RUNLOCK(workerstatus_free); /* TODO: workers_root userid+worker is ordered * so no 'find' should be needed - * just cmp to last 'unused us_item' userid+worker * then step it forward to be the next ready 'unused' */ K_RLOCK(userstats_free); us_item = find_userstats(users->userid, workers->workername); if (us_item) { DATA_USERSTATS(userstats, us_item); if (tvdiff(now, &(userstats->statsdate)) < USERSTATS_PER_S) { t_hashrate5m += userstats->hashrate5m; t_hashrate1hr += userstats->hashrate1hr; t_hashrate24hr += userstats->hashrate24hr; } } K_RUNLOCK(userstats_free); } K_RLOCK(workers_free); w_item = next_in_ktree(w_ctx); K_RUNLOCK(workers_free); DATA_WORKERS_NULL(workers, w_item); } // 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), "payname:%d=%s%c", rows, pa->payname, 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,payname," "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_item, *ws_item; K_TREE_CTX w_ctx[1]; WORKERS lookworkers, *workers; WORKERSTATUS *workerstatus; USERSTATS *userstats; USERATTS *useratts; USERS *users; char reply[1024] = ""; char tmp[1024]; int64_t oldworkers = USER_OLD_WORKERS_DEFAULT; size_t siz = sizeof(reply); tv_t last_share; char *buf; size_t len, off; bool stats, percent; int rows; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = adminuser(trf_root, 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); K_RLOCK(useratts_free); ua_item = find_useratts(users->userid, USER_OLD_WORKERS); K_RUNLOCK(useratts_free); if (ua_item) { DATA_USERATTS(useratts, ua_item); oldworkers = useratts->attnum; } 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); snprintf(tmp, sizeof(tmp), "oldworkers=%"PRId64"%c", oldworkers, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); if (oldworkers > 0) oldworkers *= 24L * 60L * 60L; else oldworkers = now->tv_sec + 1; INIT_WORKERS(&w_look); lookworkers.userid = users->userid; lookworkers.workername[0] = '\0'; DATE_ZERO(&(lookworkers.expirydate)); w_look.data = (void *)(&lookworkers); K_RLOCK(workers_free); w_item = find_after_in_ktree(workers_root, &w_look, w_ctx); K_RUNLOCK(workers_free); DATA_WORKERS_NULL(workers, w_item); rows = 0; while (w_item && workers->userid == users->userid) { if (CURRENT(&(workers->expirydate))) { K_RLOCK(workerstatus_free); ws_item = find_workerstatus(true, users->userid, workers->workername); if (ws_item) { DATA_WORKERSTATUS(workerstatus, ws_item); // good or bad - either means active copy_tv(&last_share, &(workerstatus->last_share)); } else DATE_ZERO(&last_share); K_RUNLOCK(workerstatus_free); if (tvdiff(now, &last_share) < oldworkers) { 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) { double w_hashrate5m, w_hashrate1hr; double w_hashrate24hr; int64_t w_elapsed; tv_t w_lastshare; tv_t w_lastshareacc; double w_lastdiffacc, w_diffacc; double 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; double w_active_diffacc; tv_t w_active_start; int w_instances; w_hashrate5m = w_hashrate1hr = w_hashrate24hr = 0.0; w_elapsed = -1; w_instances = NO_INSTANCE_DATA; if (!ws_item) { w_lastshare.tv_sec = w_lastshareacc.tv_sec = 0L; w_lastdiffacc = w_diffacc = w_diffinv = w_diffsta = w_diffdup = w_diffhi = w_diffrej = w_shareacc = w_shareinv = w_sharesta = w_sharedup = w_sharehi = w_sharerej = w_active_diffacc = 0; w_active_start.tv_sec = 0L; } else { DATA_WORKERSTATUS(workerstatus, ws_item); // It's bad to read possibly changing data K_RLOCK(workerstatus_free); w_lastshare.tv_sec = workerstatus->last_share.tv_sec; w_lastshareacc.tv_sec = workerstatus->last_share_acc.tv_sec; w_lastdiffacc = workerstatus->last_diff_acc; w_diffacc = workerstatus->block_diffacc; w_diffinv = workerstatus->block_diffinv; w_diffsta = workerstatus->block_diffsta; w_diffdup = workerstatus->block_diffdup; w_diffhi = workerstatus->block_diffhi; w_diffrej = workerstatus->block_diffrej; w_shareacc = workerstatus->block_shareacc; w_shareinv = workerstatus->block_shareinv; w_sharesta = workerstatus->block_sharesta; w_sharedup = workerstatus->block_sharedup; w_sharehi = workerstatus->block_sharehi; w_sharerej = workerstatus->block_sharerej; w_active_diffacc = workerstatus->active_diffacc; w_active_start.tv_sec = workerstatus->active_start.tv_sec; K_RUNLOCK(workerstatus_free); } /* TODO: workers_root userid+worker is ordered * so no 'find' should be needed - * just cmp to last 'unused us_item' userid+worker * then step it forward to be the next ready 'unused' */ K_RLOCK(userstats_free); us_item = find_userstats(users->userid, workers->workername); if (us_item) { DATA_USERSTATS(userstats, us_item); if (tvdiff(now, &(userstats->statsdate)) < USERSTATS_PER_S) { w_hashrate5m += userstats->hashrate5m; w_hashrate1hr += userstats->hashrate1hr; w_hashrate24hr += userstats->hashrate24hr; if (w_elapsed == -1 || w_elapsed > userstats->elapsed) w_elapsed = userstats->elapsed; if (userstats->instances != NO_INSTANCE_DATA) { if (w_instances == NO_INSTANCE_DATA) w_instances = 0; w_instances += userstats->instances; } } } K_RUNLOCK(userstats_free); 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); int_to_buf((int)(w_lastshareacc.tv_sec), reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_lastshareacc:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(w_lastdiffacc, 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); double_to_buf(w_active_diffacc, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_active_diffacc:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); int_to_buf((int)(w_active_start.tv_sec), reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_active_start:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); int_to_buf(w_instances, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "w_instances:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } rows++; } } K_RLOCK(workers_free); w_item = next_in_ktree(w_ctx); K_RUNLOCK(workers_free); 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_lastshareacc," "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," "w_active_diffacc,w_active_start,w_instances" : "", 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_STORE *usu_store = k_new_store(userstats_free); K_ITEM *us_item, *usu_item, *u_item; K_TREE_CTX us_ctx[1]; USERSTATS *userstats, *userstats_u = NULL; USERS *users; char reply[1024] = ""; char tmp[1024]; char *buf; size_t len, off; int rows; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); /* Sum up all recent userstats without workername * i.e. userstasts per username */ K_WLOCK(userstats_free); us_item = first_in_ktree(userstats_root, us_ctx); while (us_item) { DATA_USERSTATS(userstats, us_item); if (tvdiff(now, &(userstats->statsdate)) < ALLUSERS_LIMIT_S) { if (!userstats_u || userstats->userid != userstats_u->userid) { usu_item = k_unlink_head(userstats_free); DATA_USERSTATS(userstats_u, usu_item); userstats_u->userid = userstats->userid; /* Remember the first workername for if we ever * get the missing user LOGERR message below */ STRNCPY(userstats_u->workername, userstats->workername); userstats_u->hashrate5m = userstats->hashrate5m; userstats_u->hashrate1hr = userstats->hashrate1hr; userstats_u->instances = userstats->instances; k_add_head(usu_store, usu_item); } else { userstats_u->hashrate5m += userstats->hashrate5m; userstats_u->hashrate1hr += userstats->hashrate1hr; if (userstats->instances != NO_INSTANCE_DATA) { if ( userstats_u->instances == NO_INSTANCE_DATA) userstats_u->instances = 0; userstats_u->instances += userstats->instances; } } } us_item = next_in_ktree(us_ctx); } K_WUNLOCK(userstats_free); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); rows = 0; usu_item = STORE_HEAD_NOLOCK(usu_store); while (usu_item) { DATA_USERSTATS(userstats_u, usu_item); K_RLOCK(users_free); u_item = find_userid(userstats_u->userid); K_RUNLOCK(users_free); if (!u_item) { LOGERR("%s() userstats, but not users, " "ignored %"PRId64"/%s", __func__, userstats_u->userid, userstats_u->workername); } 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(users->userid, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "userid:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(userstats_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(userstats_u->hashrate1hr, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "u_hashrate1hr:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); int_to_buf(userstats_u->instances, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "u_instances:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; } usu_item = usu_item->next; } K_WLOCK(userstats_free); k_list_transfer_to_head(usu_store, userstats_free); K_WUNLOCK(userstats_free); k_free_store(usu_store); snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "username,userid,u_hashrate5m,u_hashrate1hr,u_instances", 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; i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) return strdup(reply); if (poolinstance && strcmp(poolinstance, transfer_data(i_poolinstance))){ POOLINSTANCE_DATA_SET(workinfo, transfer_data(i_poolinstance)); return strdup(FAILED_PI); } i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz); if (!i_workinfoid) return strdup(reply); TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); if (reloading && !confirm_sharesummary) { if (workinfoid <= dbstatus.newest_workinfoid) igndup = true; } if (confirm_sharesummary) { if (workinfoid < confirm_first_workinfoid || workinfoid > confirm_last_workinfoid) goto wiconf; } 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"); } else { // Only flag a successful workinfo ck_wlock(&last_lock); setnow(&last_workinfo); ck_wunlock(&last_lock); } 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; 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); TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); if (reloading && !confirm_sharesummary) { /* ISDR (Ignored shares during reload) * This will discard any shares older than the newest * workinfoidend of any workmarker - including ready * but not processed workmarkers * This means that if a workmarker needs re-processing * and all of it's shares need to be redone, that will * require a seperate procedure to the reload * This would be the (as yet non-existant) * confirm_markersummary which will replace the * now unusable confirm_sharesummary code * However, if the workmarker simply just needs to be * flagged as processed, this avoids the problem of * duplicating shares before flagging it */ if (workinfoid <= dbstatus.newest_workmarker_workinfoid) return NULL; } if (confirm_sharesummary) { 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"); } else { // Only flag a successful share int32_t errn; TXT_TO_INT("errn", transfer_data(i_errn), errn); ck_wlock(&last_lock); setnow(&last_share); if (errn == SE_NONE) copy_tv(&last_share_acc, &last_share); else copy_tv(&last_share_inv, &last_share); ck_wunlock(&last_lock); } 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; 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); TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); if (reloading && !confirm_sharesummary) { // See comment 'ISDR' above for shares if (workinfoid <= dbstatus.newest_workmarker_workinfoid) return NULL; } 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; i_poolinstance = require_name(trf_root, "poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) return strdup(reply); if (poolinstance && strcmp(poolinstance, transfer_data(i_poolinstance))) { POOLINSTANCE_DATA_SET(ageworkinfo, transfer_data(i_poolinstance)); return strdup(FAILED_PI); } i_workinfoid = require_name(trf_root, "workinfoid", 1, NULL, reply, siz); if (!i_workinfoid) return strdup(reply); TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); if (reloading && !confirm_sharesummary) { // This excludes any already summarised if (workinfoid <= dbstatus.newest_workmarker_workinfoid) return NULL; } if (confirm_sharesummary) { TXT_TO_BIGINT("workinfoid", transfer_data(i_workinfoid), workinfoid); if (workinfoid < confirm_first_workinfoid || workinfoid > confirm_last_workinfoid) goto awconf; } ok = workinfo_age(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 */ if (!reloading) { // Aging is a queued item thus the reply is ignored auto_age_older(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, int32_t height, 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_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_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, height, transfer_data(i_blockhash), transfer_data(i_confirmed), EMPTY, 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, height, transfer_data(i_blockhash), transfer_data(i_confirmed), EMPTY, 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) { char reply[1024] = ""; size_t siz = sizeof(reply); bool igndup = false; K_ITEM *i_height; int32_t height; 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); // confirm_summaries() doesn't call this if (reloading) { // Since they're blocks, just try them all if (height <= dbstatus.newest_height_blocks) igndup = true; } return cmd_blocks_do(conn, cmd, height, 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, K_TREE *trf_root) { K_ITEM tmp_poolinstance_item; TRANSFER tmp_poolinstance; K_TREE_CTX ctx[1]; char reply[1024] = ""; size_t siz = sizeof(reply); int event = EVENT_OK; K_ITEM *i_poolinstance, *i_username, *i_workername, *i_clientid; K_ITEM *i_enonce1, *i_useragent, *i_preauth, *u_item, *oc_item, *w_item; USERS *users = NULL; char *username; WORKERS *workers = NULL; OPTIONCONTROL *optioncontrol; size_t len, off; char *buf; bool ok = true, first; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_poolinstance = optional_name(trf_root, "poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) { if (poolinstance) { STRNCPY(tmp_poolinstance.name, "poolinstance"); STRNCPY(tmp_poolinstance.svalue, poolinstance); tmp_poolinstance.mvalue = tmp_poolinstance.svalue; tmp_poolinstance_item.name = Transfer; tmp_poolinstance_item.prev = NULL; tmp_poolinstance_item.next = NULL; tmp_poolinstance_item.data = (void *)(&tmp_poolinstance); i_poolinstance = &tmp_poolinstance_item; } else i_poolinstance = &auth_poolinstance; } else { if (poolinstance && strcmp(poolinstance, transfer_data(i_poolinstance))) { POOLINSTANCE_DATA_SET(auth, transfer_data(i_poolinstance)); return strdup(FAILED_PI); } } 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; oc_item = find_optioncontrol(OPTIONCONTROL_AUTOADDUSER, cd, pool.height); if (oc_item) { K_RLOCK(users_free); u_item = find_users(username); K_RUNLOCK(users_free); if (!u_item) { event = events_add(EVENTID_AUTOACC, trf_root); if (event == EVENT_OK) { DATA_OPTIONCONTROL(optioncontrol, oc_item); u_item = users_add(conn, username, EMPTY, optioncontrol->optionvalue, 0, by, code, inet, cd, trf_root); } else ok = false; } } if (ok) { 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, trf_root, false, &users, &workers, &event); } if (!ok) { LOGDEBUG("%s() %s.failed.DBE", __func__, id); return reply_event(event, "failed.DBE"); } // Only flag a successful auth ck_wlock(&last_lock); setnow(&last_auth); ck_wunlock(&last_lock); if (switch_state < SWITCH_STATE_AUTHWORKERS) { snprintf(reply, siz, "ok.authorise={\"secondaryuserid\":\"%s\"," "\"difficultydefault\":%d}", users->secondaryuserid, workers->difficultydefault); LOGDEBUG("%s.%s", id, reply); return strdup(reply); } APPEND_REALLOC_INIT(buf, off, len); snprintf(reply, siz, "ok.authorise={\"secondaryuserid\":\"%s\"," "\"workers\":[", users->secondaryuserid); APPEND_REALLOC(buf, off, len, reply); first = true; K_RLOCK(workers_free); w_item = first_workers(users->userid, ctx); DATA_WORKERS_NULL(workers, w_item); while (w_item && workers->userid == users->userid) { if (CURRENT(&(workers->expirydate))) { snprintf(reply, siz, "%s{\"workername\":\"%s\"," "\"difficultydefault\":%"PRId32"}", first ? EMPTY : ",", workers->workername, workers->difficultydefault); APPEND_REALLOC(buf, off, len, reply); first = false; } w_item = next_in_ktree(ctx); DATA_WORKERS_NULL(workers, w_item); } K_RUNLOCK(workers_free); APPEND_REALLOC(buf, off, len, "]}"); LOGDEBUG("%s.%s", id, buf); return buf; } 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) { return cmd_auth_do(conn, cmd, id, by, code, inet, cd, trf_root); } static char *cmd_addrauth_do(PGconn *conn, char *cmd, char *id, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root) { K_ITEM tmp_poolinstance_item; TRANSFER tmp_poolinstance; K_TREE_CTX ctx[1]; char reply[1024] = ""; size_t siz = sizeof(reply); int event = EVENT_OK; K_ITEM *i_poolinstance, *i_username, *i_workername, *i_clientid; K_ITEM *i_enonce1, *i_useragent, *i_preauth, *w_item; USERS *users = NULL; WORKERS *workers = NULL; size_t len, off; char *buf; bool ok, first; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_poolinstance = optional_name(trf_root, "poolinstance", 1, NULL, reply, siz); if (!i_poolinstance) { if (poolinstance) { STRNCPY(tmp_poolinstance.name, "poolinstance"); STRNCPY(tmp_poolinstance.svalue, poolinstance); tmp_poolinstance.mvalue = tmp_poolinstance.svalue; tmp_poolinstance_item.name = Transfer; tmp_poolinstance_item.prev = NULL; tmp_poolinstance_item.next = NULL; tmp_poolinstance_item.data = (void *)(&tmp_poolinstance); i_poolinstance = &tmp_poolinstance_item; } else i_poolinstance = &auth_poolinstance; } else { if (poolinstance && strcmp(poolinstance, transfer_data(i_poolinstance))) { POOLINSTANCE_DATA_SET(addrauth, transfer_data(i_poolinstance)); return strdup(FAILED_PI); } } 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, trf_root, true, &users, &workers, &event); if (!ok) { LOGDEBUG("%s() %s.failed.DBE", __func__, id); return reply_event(event, "failed.DBE"); } // Only flag a successful auth ck_wlock(&last_lock); setnow(&last_auth); ck_wunlock(&last_lock); if (switch_state < SWITCH_STATE_AUTHWORKERS) { snprintf(reply, siz, "ok.addrauth={\"secondaryuserid\":\"%s\"," "\"difficultydefault\":%d}", users->secondaryuserid, workers->difficultydefault); LOGDEBUG("%s.%s", id, reply); return strdup(reply); } APPEND_REALLOC_INIT(buf, off, len); snprintf(reply, siz, "ok.addrauth={\"secondaryuserid\":\"%s\"," "\"workers\":[", users->secondaryuserid); APPEND_REALLOC(buf, off, len, reply); first = true; K_RLOCK(workers_free); w_item = first_workers(users->userid, ctx); DATA_WORKERS_NULL(workers, w_item); while (w_item && workers->userid == users->userid) { if (CURRENT(&(workers->expirydate))) { snprintf(reply, siz, "%s{\"workername\":\"%s\"," "\"difficultydefault\":%"PRId32"}", first ? EMPTY : ",", workers->workername, workers->difficultydefault); APPEND_REALLOC(buf, off, len, reply); first = false; } w_item = next_in_ktree(ctx); DATA_WORKERS_NULL(workers, w_item); } K_RUNLOCK(workers_free); APPEND_REALLOC(buf, off, len, "]}"); LOGDEBUG("%s.%s", id, buf); return buf; } 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) { return cmd_addrauth_do(conn, cmd, id, by, code, inet, cd, 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; ck_wlock(&last_lock); setnow(&last_heartbeat); ck_wunlock(&last_lock); 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 = STORE_TAIL_NOLOCK(hq_store); 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," "\""CDTRF"\":\"%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, 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; int u_instances; K_TREE_CTX 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."); // N.B. cmd_homepage isn't called until startup_complete ftv_to_buf(now, reply, siz); snprintf(tmp, sizeof(tmp), "now=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ck_rlock(&last_lock); ftv_to_buf(&last_heartbeat, reply, siz); snprintf(tmp, sizeof(tmp), "lasthb=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ftv_to_buf(&last_workinfo, reply, siz); snprintf(tmp, sizeof(tmp), "lastwi=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ftv_to_buf(&last_share, reply, siz); snprintf(tmp, sizeof(tmp), "lastsh=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ftv_to_buf(&last_share_acc, reply, siz); snprintf(tmp, sizeof(tmp), "lastshacc=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ftv_to_buf(&last_share_inv, reply, siz); snprintf(tmp, sizeof(tmp), "lastshinv=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ftv_to_buf(&last_auth, reply, siz); ck_runlock(&last_lock); snprintf(tmp, sizeof(tmp), "lastau=%s%c", reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); 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; DATA_WORKINFO(wic, workinfo_current); snprintf(tmp, sizeof(tmp), "lastheight=%d%c", wic->height-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: DB only has one poolinstance with -i K_RLOCK(poolstats_free); p_item = last_in_ktree(poolstats_root, ctx); K_RUNLOCK(poolstats_free); 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 int psync = pool_workqueue_store->count; int csync = cmd_workqueue_store->count; int bsync = btc_workqueue_store->count; snprintf(tmp, sizeof(tmp), "psync=%d%c", psync, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "csync=%d%c", csync, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "bsync=%d%c", bsync, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "sync=%d%c", psync + csync + bsync, 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) { u_hashrate5m = u_hashrate1hr = 0.0; u_elapsed = -1; u_instances = NO_INSTANCE_DATA; /* find last matching userid record - before userid+1 * Use 'before' in case there is (unexpectedly) a userstats * with an empty workername */ lookuserstats.userid = users->userid+1; STRNCPY(lookuserstats.workername, EMPTY); INIT_USERSTATS(&look); look.data = (void *)(&lookuserstats); K_RLOCK(userstats_free); us_item = find_before_in_ktree(userstats_root, &look, ctx); DATA_USERSTATS_NULL(userstats, us_item); while (us_item && userstats->userid == users->userid) { if (tvdiff(now, &(userstats->statsdate)) < USERSTATS_PER_S) { u_hashrate5m += userstats->hashrate5m; u_hashrate1hr += userstats->hashrate1hr; if (u_elapsed == -1 || u_elapsed > userstats->elapsed) u_elapsed = userstats->elapsed; if (userstats->instances != NO_INSTANCE_DATA) { if (u_instances == NO_INSTANCE_DATA) u_instances = 0; u_instances += userstats->instances; } has_uhr = true; } us_item = prev_in_ktree(ctx); DATA_USERSTATS_NULL(userstats, us_item); } 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); int_to_buf(u_instances, reply, siz); snprintf(tmp, sizeof(tmp), "u_instances=%s", reply); APPEND_REALLOC(buf, off, len, tmp); } else { snprintf(tmp, sizeof(tmp), "u_hashrate5m=?%cu_hashrate1hr=?%cu_elapsed=?%cu_instances=?", FLDSEP, 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); int event = EVENT_OK; 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", MIN_USERNAME, (char *)userpatt, reply, siz); if (!i_username) { // Shouldn't happen except with a code problem no event required reason = "Missing username"; goto nuts; } K_RLOCK(users_free); u_item = find_users(transfer_data(i_username)); K_RUNLOCK(users_free); if (!u_item) { // page_api.php without a valid username event = events_add(EVENTID_UNKATTS, trf_root); 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 reply_event(event, 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", MIN_USERNAME, (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_nolock(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_WLOCK(useratts_free); ua_item = k_unlink_head(useratts_free); K_WUNLOCK(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", MIN_USERNAME, (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'; oc_item = find_optioncontrol(ptr, now, pool.height); /* 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_nolock(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_WLOCK(optioncontrol_free); oc_item = k_unlink_head(optioncontrol_free); K_WUNLOCK(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) { DUP_POINTER(optioncontrol_free, optioncontrol->optionvalue, 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); } /* Kept for reference/comparison to cmd_pplns2() * This will get different results due to the fact that it uses the current * contents of the payoutaddresses table * However, the only differences should be the addresses, * and the breakdown for percent address users, * the totals per user and per payout should still be the same */ 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; 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; } LOGDEBUG("%s(): height %"PRId32, __func__, height); DATE_ZERO(&block_tv); DATE_ZERO(&cd); 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, 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); } LOGDEBUG("%s(): block %"PRId32"/%"PRId64"/%s/%s/%"PRId64, __func__, blocks->height, blocks->workinfoid, blocks->workername, blocks->confirmed, blocks->reward); switch (blocks->confirmed[0]) { case BLOCKS_NEW: block_extra = "Can't be paid out yet"; break; case BLOCKS_ORPHAN: case BLOCKS_REJECT: 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); ndiff = workinfo->diff_target; 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; LOGDEBUG("%s(): ndiff %.0f limit=%s", __func__, ndiff, countbacklimit ? "true" : "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_auto("OldMPU", cmp_mu, miningpayouts_free); looksharesummary.workinfoid = block_workinfoid; looksharesummary.userid = MAXID; looksharesummary.workername = EMPTY; INIT_SHARESUMMARY(&ss_look); ss_look.data = (void *)(&looksharesummary); K_WLOCK(miningpayouts_free); K_RLOCK(sharesummary_free); K_RLOCK(workmarkers_free); K_RLOCK(markersummary_free); ss_item = find_before_in_ktree(sharesummary_workinfoid_root, &ss_look, 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 lastshareacc - 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->lastshareacc))) copy_tv(&end_tv, &(sharesummary->lastshareacc)); 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); 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); } LOGDEBUG("%s(): ss %"PRId64" total %.0f want %.0f", __func__, ss_count, total_diff, diff_want); /* 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, wm_ctx); DATA_WORKMARKERS_NULL(workmarkers, wm_item); LOGDEBUG("%s(): workmarkers < %"PRId64, __func__, lookworkmarkers.workinfoidend); 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, 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->lastshareacc))) copy_tv(&end_tv, &(markersummary->lastshareacc)); 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); } LOGDEBUG("%s(): wm %"PRId64" ms %"PRId64" total %.0f want %.0f", __func__, wm_count, ms_count, total_diff, diff_want); } K_RUNLOCK(markersummary_free); K_RUNLOCK(workmarkers_free); K_RUNLOCK(sharesummary_free); K_WUNLOCK(miningpayouts_free); LOGDEBUG("%s(): total %.0f want %.0f", __func__, total_diff, diff_want); 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"); free_ktree(mu_root, NULL); K_WLOCK(miningpayouts_free); k_list_transfer_to_head(mu_store, miningpayouts_free); K_WUNLOCK(miningpayouts_free); mu_store = k_free_store(mu_store); LOGDEBUG("%s.ok.pplns.%s", id, buf); return buf; shazbot: free_ktree(mu_root, NULL); K_WLOCK(miningpayouts_free); k_list_transfer_to_head(mu_store, miningpayouts_free); K_WUNLOCK(miningpayouts_free); mu_store = k_free_store(mu_store); return strdup(reply); } // Generated from the payouts, miningpayouts and payments data static char *cmd_pplns2(__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, *marks_status = EMPTY; size_t siz = sizeof(reply); K_ITEM *i_height; K_ITEM b_look, *b_item, *p_item, *mp_item, *pay_item, *u_item; K_ITEM *w_item; MININGPAYOUTS *miningpayouts; PAYMENTS *payments; PAYOUTS *payouts; BLOCKS lookblocks, *blocks; WORKINFO *bworkinfo, *workinfo; USERS *users; int32_t height; K_TREE_CTX b_ctx[1], mp_ctx[1], pay_ctx[1]; char tv_buf[DATE_BUFSIZ]; size_t len, off; int rows; bool pok; 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); LOGDEBUG("%s(): height %"PRId32, __func__, height); lookblocks.height = height; lookblocks.blockhash[0] = '\0'; INIT_BLOCKS(&b_look); b_look.data = (void *)(&lookblocks); K_RLOCK(blocks_free); b_item = find_after_in_ktree(blocks_root, &b_look, b_ctx); K_RUNLOCK(blocks_free); if (!b_item) { snprintf(reply, siz, "ERR.no block height >= %"PRId32, height); return strdup(reply); } DATA_BLOCKS(blocks, b_item); if (!b_item || blocks->height != height) { snprintf(reply, siz, "ERR.no block height %"PRId32, height); return strdup(reply); } if (blocks->blockcreatedate.tv_sec == 0) { snprintf(reply, siz, "ERR.block %"PRId32" has 0 blockcreatedate", height); return strdup(reply); } if (!CURRENT(&(blocks->expirydate))) { snprintf(reply, siz, "ERR.no CURRENT block %d"PRId32, height); return strdup(reply); } LOGDEBUG("%s(): block %"PRId32"/%"PRId64"/%s/%s/%"PRId64, __func__, blocks->height, blocks->workinfoid, blocks->workername, blocks->confirmed, blocks->reward); switch (blocks->confirmed[0]) { case BLOCKS_NEW: block_extra = "Can't be paid out yet"; break; case BLOCKS_ORPHAN: case BLOCKS_REJECT: block_extra = "Can't be paid out"; break; default: block_extra = EMPTY; break; } w_item = find_workinfo(blocks->workinfoid, NULL); if (!w_item) { snprintf(reply, siz, "ERR.missing block workinfo record!" " %"PRId64, blocks->workinfoid); return strdup(reply); } DATA_WORKINFO(bworkinfo, w_item); pok = false; K_RLOCK(payouts_free); p_item = find_payouts(height, blocks->blockhash); DATA_PAYOUTS_NULL(payouts, p_item); if (p_item && PAYGENERATED(payouts->status)) pok = true; K_RUNLOCK(payouts_free); if (!p_item) { snprintf(reply, siz, "ERR.no payout for %"PRId32"/%s", height, blocks->blockhash); return strdup(reply); } if (!pok) { snprintf(reply, siz, "ERR.payout %"PRId64" status=%s " "for %"PRId32"/%s", payouts->payoutid, payouts->status, height, blocks->blockhash); return strdup(reply); } LOGDEBUG("%s(): total %.1f want %.1f", __func__, payouts->diffused, payouts->diffwanted); w_item = find_workinfo(payouts->workinfoidstart, NULL); if (!w_item) { snprintf(reply, siz, "ERR.missing begin workinfo record!" " %"PRId64, payouts->workinfoidstart); return strdup(reply); } DATA_WORKINFO(workinfo, w_item); 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), "miner_reward=%"PRId64"%c", payouts->minerreward, 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), "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", payouts->workinfoidstart, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "block_workinfoid=%"PRId64"%c", blocks->workinfoid, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "end_workinfoid=%"PRId64"%c", payouts->workinfoidend, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "diffacc_total=%.1f%c", payouts->diffused, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "pplns_elapsed=%"PRId64"%c", payouts->elapsed, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows = 0; K_RLOCK(miningpayouts_free); mp_item = first_miningpayouts(payouts->payoutid, mp_ctx); K_RUNLOCK(miningpayouts_free); DATA_MININGPAYOUTS_NULL(miningpayouts, mp_item); while (mp_item && miningpayouts->payoutid == payouts->payoutid) { if (CURRENT(&(miningpayouts->expirydate))) { int out = 0; 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_RLOCK(payments_free); pay_item = find_first_paypayid(miningpayouts->userid, payouts->payoutid, pay_ctx); DATA_PAYMENTS_NULL(payments, pay_item); while (pay_item && payments->userid == miningpayouts->userid && payments->payoutid == payouts->payoutid) { if (CURRENT(&(payments->expirydate))) { snprintf(tmp, sizeof(tmp), "user:%d=%s%c" "payaddress:%d=%s%c" "amount:%d=%"PRId64"%c" "diffacc:%d=%.1f%c", rows, payments->subname, FLDSEP, rows, payments->payaddress, FLDSEP, rows, payments->amount, FLDSEP, rows, payments->diffacc, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; out++; } pay_item = next_in_ktree(pay_ctx); DATA_PAYMENTS_NULL(payments, pay_item); } K_RUNLOCK(payments_free); if (out == 0) { snprintf(tmp, sizeof(tmp), "user:%d=%s.0%c" "payaddress:%d=%s%c" "amount:%d=%"PRId64"%c" "diffacc:%d=%.1f%c", rows, users->username, FLDSEP, rows, "none", FLDSEP, rows, miningpayouts->amount, FLDSEP, rows, miningpayouts->diffacc, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; } } K_RLOCK(miningpayouts_free); mp_item = next_in_ktree(mp_ctx); K_RUNLOCK(miningpayouts_free); DATA_MININGPAYOUTS_NULL(miningpayouts, mp_item); } snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "user,payaddress,amount,diffacc", 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(&(workinfo->createdate), 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", workinfo->createdate.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); tv_to_buf(&(blocks->blockcreatedate), 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", blocks->blockcreatedate.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); tv_to_buf(&(payouts->lastshareacc), 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", payouts->lastshareacc.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "%s%c", payouts->stats, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "block_ndiff=%f%c", bworkinfo->diff_target, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "diff_want=%.1f%c", payouts->diffwanted, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "acc_share_count=%.0f%c", payouts->shareacc, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); // So web can always verify it received all data APPEND_REALLOC(buf, off, len, "pplns_last=1"); LOGDEBUG("%s.ok.pplns.%s", id, buf); return buf; shazbot: return strdup(reply); } static char *cmd_payouts(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); char msg[1024] = ""; K_ITEM *i_action, *i_payoutid, *i_height, *i_blockhash, *i_addrdate; K_ITEM *p_item, *p2_item, *old_p2_item; PAYOUTS *payouts, *payouts2, *old_payouts2; char *action; int64_t payoutid = -1; int32_t height = 0; char blockhash[TXT_BIG+1]; tv_t addrdate; bool ok = true; 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, "generated") == 0) { /* Change the status of a processing payout to generated * Require payoutid * Use this if the payout process completed but the end txn, * that only updates the payout to generated, failed */ i_payoutid = require_name(trf_root, "payoutid", 1, (char *)intpatt, reply, siz); if (!i_payoutid) return strdup(reply); TXT_TO_BIGINT("payoutid", transfer_data(i_payoutid), payoutid); K_WLOCK(payouts_free); p_item = find_payoutid(payoutid); if (!p_item) { K_WUNLOCK(payouts_free); snprintf(reply, siz, "no payout with id %"PRId64, payoutid); return strdup(reply); } DATA_PAYOUTS(payouts, p_item); if (!PAYPROCESSING(payouts->status)) { K_WUNLOCK(payouts_free); snprintf(reply, siz, "status !processing (%s) for payout %"PRId64, payouts->status, payoutid); return strdup(reply); } p2_item = k_unlink_head(payouts_free); K_WUNLOCK(payouts_free); /* There is a risk of the p_item changing while it's unlocked, * but since this is a manual interface it's not really likely * and there'll be an error if something goes wrong * It reports the old and new status */ DATA_PAYOUTS(payouts2, p2_item); bzero(payouts2, sizeof(*payouts2)); payouts2->payoutid = payouts->payoutid; payouts2->height = payouts->height; STRNCPY(payouts2->blockhash, payouts->blockhash); payouts2->minerreward = payouts->minerreward; payouts2->workinfoidstart = payouts->workinfoidstart; payouts2->workinfoidend = payouts->workinfoidend; payouts2->elapsed = payouts->elapsed; STRNCPY(payouts2->status, PAYOUTS_GENERATED_STR); payouts2->diffwanted = payouts->diffwanted; payouts2->diffused = payouts->diffused; payouts2->shareacc = payouts->shareacc; copy_tv(&(payouts2->lastshareacc), &(payouts->lastshareacc)); DUP_POINTER(payouts_free, payouts2->stats, payouts->stats); ok = payouts_add(conn, true, p2_item, &old_p2_item, by, code, inet, now, NULL, false); if (!ok) { snprintf(reply, siz, "failed payout %"PRId64, payoutid); return strdup(reply); } // Original wasn't generated, so reward it reward_shifts(payouts2, 1); DATA_PAYOUTS(payouts2, p2_item); DATA_PAYOUTS(old_payouts2, old_p2_item); snprintf(msg, sizeof(msg), "payout %"PRId64" changed from '%s' to '%s' for " "%"PRId32"/%s", payoutid, old_payouts2->status, payouts2->status, payouts2->height, payouts2->blockhash); } else if (strcasecmp(action, "orphan") == 0 || strcasecmp(action, "reject") == 0) { /* Change the status of a generated payout to orphaned * or rejected * Require payoutid * Use this if the orphan or reject process didn't * automatically update a generated payout * TODO: get orphaned and rejected blocks to automatically do this */ i_payoutid = require_name(trf_root, "payoutid", 1, (char *)intpatt, reply, siz); if (!i_payoutid) return strdup(reply); TXT_TO_BIGINT("payoutid", transfer_data(i_payoutid), payoutid); K_WLOCK(payouts_free); p_item = find_payoutid(payoutid); if (!p_item) { K_WUNLOCK(payouts_free); snprintf(reply, siz, "no payout with id %"PRId64, payoutid); return strdup(reply); } DATA_PAYOUTS(payouts, p_item); if (!PAYGENERATED(payouts->status)) { K_WUNLOCK(payouts_free); snprintf(reply, siz, "status !generated (%s) for payout %"PRId64, payouts->status, payoutid); return strdup(reply); } p2_item = k_unlink_head(payouts_free); K_WUNLOCK(payouts_free); /* There is a risk of the p_item changing while it's unlocked, * but since this is a manual interface it's not really likely * and there'll be an error if something goes wrong * It reports the old and new status */ DATA_PAYOUTS(payouts2, p2_item); bzero(payouts2, sizeof(*payouts2)); payouts2->payoutid = payouts->payoutid; payouts2->height = payouts->height; STRNCPY(payouts2->blockhash, payouts->blockhash); payouts2->minerreward = payouts->minerreward; payouts2->workinfoidstart = payouts->workinfoidstart; payouts2->workinfoidend = payouts->workinfoidend; payouts2->elapsed = payouts->elapsed; if (strcasecmp(action, "orphan") == 0) STRNCPY(payouts2->status, PAYOUTS_ORPHAN_STR); else STRNCPY(payouts2->status, PAYOUTS_REJECT_STR); payouts2->diffwanted = payouts->diffwanted; payouts2->diffused = payouts->diffused; payouts2->shareacc = payouts->shareacc; copy_tv(&(payouts2->lastshareacc), &(payouts->lastshareacc)); DUP_POINTER(payouts_free, payouts2->stats, payouts->stats); ok = payouts_add(conn, true, p2_item, &old_p2_item, by, code, inet, now, NULL, false); if (!ok) { snprintf(reply, siz, "failed payout %"PRId64, payoutid); return strdup(reply); } // Original was generated, so undo the reward reward_shifts(payouts2, -1); DATA_PAYOUTS(payouts2, p2_item); DATA_PAYOUTS(old_payouts2, old_p2_item); snprintf(msg, sizeof(msg), "payout %"PRId64" changed from '%s' to '%s' for " "%"PRId32"/%s", payoutid, old_payouts2->status, payouts2->status, payouts2->height, payouts2->blockhash); } else if (strcasecmp(action, "expire") == 0) { /* Expire the payout - effectively deletes it * Require payoutid * TODO: If any payments are paid then don't allow it */ i_payoutid = require_name(trf_root, "payoutid", 1, (char *)intpatt, reply, siz); if (!i_payoutid) return strdup(reply); TXT_TO_BIGINT("payoutid", transfer_data(i_payoutid), payoutid); // payouts_full_expire updates the shift rewards p_item = payouts_full_expire(conn, payoutid, now, true); if (!p_item) { snprintf(reply, siz, "failed payout %"PRId64, payoutid); return strdup(reply); } DATA_PAYOUTS(payouts, p_item); snprintf(msg, sizeof(msg), "payout %"PRId64" block %"PRId32" reward %"PRId64 " status '%s'", payouts->payoutid, payouts->height, payouts->minerreward, payouts->status); } else if (strcasecmp(action, "process") == 0) { /* Generate a payout * Require height, blockhash and addrdate * addrdate is an epoch integer * and 0 means uses the default = block NEW createdate * this is the date to use to determine payoutaddresses * Check the console for processing messages */ i_height = require_name(trf_root, "height", 6, (char *)intpatt, reply, siz); if (!i_height) return strdup(reply); TXT_TO_INT("height", transfer_data(i_height), height); i_blockhash = require_name(trf_root, "blockhash", 64, (char *)hashpatt, reply, siz); if (!i_blockhash) return strdup(reply); TXT_TO_STR("blockhash", transfer_data(i_blockhash), blockhash); i_addrdate = require_name(trf_root, "addrdate", 1, (char *)intpatt, reply, siz); if (!i_addrdate) return strdup(reply); TXT_TO_CTV("addrdate", transfer_data(i_addrdate), addrdate); // process_pplns updates the shift rewards if (addrdate.tv_sec == 0) ok = process_pplns(height, blockhash, NULL); else ok = process_pplns(height, blockhash, &addrdate); } else if (strcasecmp(action, "genon") == 0) { /* Turn on auto payout generation * and report the before/after status * No parameters */ bool old = genpayout_auto; genpayout_auto = true; snprintf(msg, sizeof(msg), "payout generation state was %s," " now %s", old ? "On" : "Off", genpayout_auto ? "On" : "Off"); ok = true; } else if (strcasecmp(action, "genoff") == 0) { /* Turn off auto payout generation * and report the before/after status * No parameters */ bool old = genpayout_auto; genpayout_auto = false; snprintf(msg, sizeof(msg), "payout generation state was %s," " now %s", old ? "On" : "Off", genpayout_auto ? "On" : "Off"); ok = true; } else { snprintf(reply, siz, "unknown action '%s'", action); LOGERR("%s.%s", id, reply); return strdup(reply); } snprintf(reply, siz, "%s.%s%s%s", ok ? "ok" : "ERR", action, msg[0] ? " " : EMPTY, msg[0] ? msg : EMPTY); LOGWARNING("%s.%s", id, reply); return strdup(reply); } static char *cmd_mpayouts(__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, *u_item, *mp_item, *po_item; K_TREE_CTX ctx[1]; MININGPAYOUTS *mp; PAYOUTS *payouts; 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 = adminuser(trf_root, 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); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); rows = 0; K_RLOCK(payouts_free); po_item = last_in_ktree(payouts_root, ctx); DATA_PAYOUTS_NULL(payouts, po_item); /* TODO: allow to see details of a single payoutid * if it has multiple items (percent payout user) */ while (po_item) { if (CURRENT(&(payouts->expirydate)) && PAYGENERATED(payouts->status)) { K_RLOCK(miningpayouts_free); mp_item = find_miningpayouts(payouts->payoutid, users->userid); if (mp_item) { DATA_MININGPAYOUTS(mp, mp_item); bigint_to_buf(payouts->payoutid, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "payoutid:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); int_to_buf(payouts->height, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "height:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "block"CDTRF":%d=%ld%c", rows, payouts->blockcreatedate.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(payouts->elapsed, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "elapsed:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(mp->amount, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "amount:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(mp->diffacc, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "diffacc:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(payouts->minerreward, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "minerreward:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(payouts->diffused, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "diffused:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); str_to_buf(payouts->status, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "status:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; } K_RUNLOCK(miningpayouts_free); } po_item = prev_in_ktree(ctx); DATA_PAYOUTS_NULL(payouts, po_item); } K_RUNLOCK(payouts_free); snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "payoutid,height,block"CDTRF",elapsed,amount,diffacc,minerreward,diffused,status", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s", "MiningPayouts", FLDSEP, ""); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); return buf; } typedef struct worker_match { char *worker; bool match; size_t len; bool used; bool everused; } WM; static char *worker_offset(char *workername) { char *c1, *c2; /* Find the start of the workername including the SEP */ c1 = strchr(workername, WORKSEP1); c2 = strchr(workername, WORKSEP2); if (c1 || c2) { if (!c1 || (c1 && c2 && (c2 < c1))) c1 = c2; } // No workername after the username if (!c1) c1 = WORKERS_EMPTY; return c1; } /* Some arbitrarily large limit, increase it if needed (doesn't need to be very large) */ #define SELECT_LIMIT 63 /* select is a string of workernames separated by WORKERS_SEL_SEP * Setup the wm array of workers with select broken up * The wm array is terminated by workers = NULL * and will have 0 elements if select is NULL/empty * The count of the first occurrence of WORKERS_ALL is returned, * or -1 if WORKERS_ALL isn't found */ static int select_list(WM *wm, char *select) { int count, all_count = -1; size_t len, offset; char *end; if (select == NULL || *select == '\0') return all_count; len = strlen(select); count = 0; offset = 0; while (offset < len) { if (select[offset] == WORKERS_SEL_SEP) offset++; else { wm[count].worker = select + offset; wm[count+1].worker = NULL; end = strchr(wm[count].worker, WORKERS_SEL_SEP); if (end != NULL) { offset = 1 + end - select; *end = '\0'; } if (all_count == -1 && strcasecmp(wm[count].worker, WORKERS_ALL) == 0) { all_count = count; } if (end == NULL || ++count > SELECT_LIMIT) break; } } return all_count; } static char *cmd_shifts(__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_select; K_ITEM *u_item, *p_item, *m_item, ms_look, *wm_item, *ms_item, *wi_item; K_TREE_CTX wm_ctx[1], ms_ctx[1]; WORKMARKERS *wm; WORKINFO *wi; MARKERSUMMARY markersummary, *ms, ms_add[SELECT_LIMIT+1]; PAYOUTS *payouts; USERS *users; MARKS *marks = NULL; char reply[1024] = ""; char tmp[1024]; size_t siz = sizeof(reply); char *select = NULL; WM workm[SELECT_LIMIT+1]; char *buf = NULL, *work, *st = NULL; size_t len, off; tv_t marker_end = { 0L, 0L }; int rows, want, i, where_all; int64_t maxrows; double wm_count, d; int64_t last_payout_start = 0; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = adminuser(trf_root, 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); maxrows = user_sys_setting(users->userid, SHIFTS_SETTING_NAME, SHIFTS_DEFAULT, now); K_RLOCK(payouts_free); p_item = find_last_payouts(); K_RUNLOCK(payouts_free); if (p_item) { DATA_PAYOUTS(payouts, p_item); wm_count = payout_stats(payouts, "wm_count"); wm_count *= 1.42; if (maxrows < wm_count) maxrows = wm_count; last_payout_start = payouts->workinfoidstart; } i_select = optional_name(trf_root, "select", 1, NULL, reply, siz); if (i_select) select = strdup(transfer_data(i_select)); APPEND_REALLOC_INIT(buf, off, len); snprintf(tmp, sizeof(tmp), " select='%s'", select ? st = safe_text_nonull(select) : "null"); FREENULL(st); APPEND_REALLOC(buf, off, len, tmp); bzero(workm, sizeof(workm)); where_all = select_list(&(workm[0]), select); // Nothing selected = all if (workm[0].worker == NULL) { where_all = 0; workm[0].worker = WORKERS_ALL; APPEND_REALLOC(buf, off, len, " no workers"); } else { for (i = 0; workm[i].worker; i++) { // N.B. len is only used if match is true len = workm[i].len = strlen(workm[i].worker); // If at least 3 characters and last is '*' if (len > 2 && workm[i].worker[len-1] == '*') { workm[i].worker[len-1] = '\0'; workm[i].match = true; workm[i].len--; } snprintf(tmp, sizeof(tmp), " workm[%d]=%s,%s,%d", i, st = safe_text_nonull(workm[i].worker), workm[i].match ? "Y" : "N", (int)(workm[i].len)); FREENULL(st); APPEND_REALLOC(buf, off, len, tmp); } } if (where_all >= 0) workm[where_all].used = true; snprintf(tmp, sizeof(tmp), " where_all=%d", where_all); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s() user=%"PRId64"/%s' %s", __func__, users->userid, users->username, buf+1); FREENULL(buf); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); INIT_MARKERSUMMARY(&ms_look); ms_look.data = (void *)(&markersummary); rows = 0; K_RLOCK(workmarkers_free); wm_item = last_in_ktree(workmarkers_workinfoid_root, wm_ctx); DATA_WORKMARKERS_NULL(wm, wm_item); /* TODO: allow to see details of a single payoutid * if it has multiple items (percent payout user) */ while (rows < (maxrows - 1) && wm_item) { if (CURRENT(&(wm->expirydate)) && WMPROCESSED(wm->status)) { K_RUNLOCK(workmarkers_free); K_RLOCK(marks_free); m_item = find_marks(wm->workinfoidend); K_RUNLOCK(marks_free); DATA_MARKS_NULL(marks, m_item); if (m_item == NULL) { // Log it but keep going LOGERR("%s() missing mark for markerid " "%"PRId64"/%s widend %"PRId64, __func__, wm->markerid, wm->description, wm->workinfoidend); } // Zero everything for this shift bzero(ms_add, sizeof(ms_add)); for (i = 0; workm[i].worker; i++) { if (i != where_all) workm[i].used = false; } markersummary.markerid = wm->markerid; markersummary.userid = users->userid; markersummary.workername = EMPTY; K_RLOCK(markersummary_free); ms_item = find_after_in_ktree(markersummary_root, &ms_look, ms_ctx); DATA_MARKERSUMMARY_NULL(ms, ms_item); while (ms_item && ms->markerid == wm->markerid && ms->userid == users->userid) { work = worker_offset(ms->workername); for (want = 0; workm[want].worker; want++) { if ((want == where_all) || (workm[want].match && strncmp(work, workm[want].worker, workm[want].len) == 0) || (!(workm[want].match) && strcmp(workm[want].worker, work) == 0)) { workm[want].used = true; workm[want].everused = true; ms_add[want].diffacc += ms->diffacc; ms_add[want].diffsta += ms->diffsta; ms_add[want].diffdup += ms->diffdup; ms_add[want].diffhi += ms->diffhi; ms_add[want].diffrej += ms->diffrej; ms_add[want].shareacc += ms->shareacc; ms_add[want].sharesta += ms->sharesta; ms_add[want].sharedup += ms->sharedup; ms_add[want].sharehi += ms->sharehi; ms_add[want].sharerej += ms->sharerej; } } ms_item = next_in_ktree(ms_ctx); DATA_MARKERSUMMARY_NULL(ms, ms_item); } K_RUNLOCK(markersummary_free); for (i = 0; i <= SELECT_LIMIT; i++) { if (workm[i].used) { double_to_buf(ms_add[i].diffacc, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "%d_diffacc:%d=%s%c", i, rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); d = ms_add[i].diffsta + ms_add[i].diffdup + ms_add[i].diffhi + ms_add[i].diffrej; double_to_buf(d, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "%d_diffinv:%d=%s%c", i, rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(ms_add[i].shareacc, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "%d_shareacc:%d=%s%c", i, rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); d = ms_add[i].sharesta + ms_add[i].sharedup + ms_add[i].sharehi + ms_add[i].sharerej; double_to_buf(d, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "%d_shareinv:%d=%s%c", i, rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } } if (marker_end.tv_sec == 0L) { wi_item = next_workinfo(wm->workinfoidend, NULL); if (!wi_item) { /* There's no workinfo after this shift * Unexpected ... estimate last wid+30s */ wi_item = find_workinfo(wm->workinfoidend, NULL); if (!wi_item) { // Nothing is currently locked LOGERR("%s() workmarker %"PRId64"/%s." " missing widend %"PRId64, __func__, wm->markerid, wm->description, wm->workinfoidend); snprintf(reply, siz, "data error 1"); free(buf); return(strdup(reply)); } DATA_WORKINFO(wi, wi_item); copy_tv(&marker_end, &(wi->createdate)); marker_end.tv_sec += 30; } else { DATA_WORKINFO(wi, wi_item); copy_tv(&marker_end, &(wi->createdate)); } } wi_item = find_workinfo(wm->workinfoidstart, NULL); if (!wi_item) { // Nothing is currently locked LOGERR("%s() workmarker %"PRId64"/%s. missing " "widstart %"PRId64, __func__, wm->markerid, wm->description, wm->workinfoidstart); snprintf(reply, siz, "data error 2"); free(buf); return(strdup(reply)); } DATA_WORKINFO(wi, wi_item); bigint_to_buf(wm->markerid, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "markerid:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); str_to_buf(wm->description, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "shift:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "endmarkextra:%d=%s%c", rows, m_item ? marks->extra : EMPTY, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ftv_to_buf(&(wi->createdate), reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "start:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ftv_to_buf(&marker_end, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "end:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "rewards:%d=%d%c", rows, wm->rewards, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); // Use %.15e -> 16 non-leading-zero decimal places snprintf(tmp, sizeof(tmp), "ppsvalue:%d=%.15f%c", rows, wm->pps_value, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); // Use %.15e -> 16 non-leading-zero decimal places snprintf(tmp, sizeof(tmp), "ppsrewarded:%d=%.15e%c", rows, wm->rewarded, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "lastpayoutstart:%d=%s%c", rows, (wm->workinfoidstart == last_payout_start) ? "Y" : EMPTY, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; // Setup for next shift copy_tv(&marker_end, &(wi->createdate)); K_RLOCK(workmarkers_free); } wm_item = prev_in_ktree(wm_ctx); DATA_WORKMARKERS_NULL(wm, wm_item); } K_RUNLOCK(workmarkers_free); for (i = 0; workm[i].worker; i++) { if (workm[i].everused) { snprintf(tmp, sizeof(tmp), "%d_worker=%s%s%c", i, workm[i].worker, workm[i].match ? "*" : EMPTY, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "%d_flds=%s%c", i, "diffacc,diffinv,shareacc,shareinv", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } } // Missing if all isn't selected if (where_all >= 0) { snprintf(tmp, sizeof(tmp), "prefix_all=%d_%c", where_all, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } /* rows is an upper limit of rows in each worker * 'all' starts at 0 and finishes at rows-1 * other workers start >= 0 and finish <= rows-1 */ snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "markerid,shift,start,end,rewards," "ppsvalue,ppsrewarded,lastpayoutstart", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s", "Shifts"); APPEND_REALLOC(buf, off, len, tmp); for (i = 0; workm[i].worker; i++) { if (workm[i].everused) { snprintf(tmp, sizeof(tmp), ",Worker_%d", i); APPEND_REALLOC(buf, off, len, tmp); } } snprintf(tmp, sizeof(tmp), "%carp=", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); for (i = 0; workm[i].worker; i++) { if (workm[i].everused) { snprintf(tmp, sizeof(tmp), ",%d_", i); APPEND_REALLOC(buf, off, len, tmp); } } LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); return(buf); } 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(paymentaddresses_create_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; const char *name; size_t len, off; uint64_t ram, ram2, tot = 0; K_LIST *klist; K_LISTS *klists; int rows = 0; bool istree; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); /* All but temporary lists are in klist_all * All trees are there also since all trees have a node klist */ ck_wlock(&lock_check_lock); klists = all_klists; while (klists) { klist = klists->klist; ram = sizeof(*klist); if (klist->name == tree_node_list_name) { ram += sizeof(K_TREE); istree = true; name = klist->name2; } else { istree = false; name = klist->name; } if (klist->lock) ram += sizeof(*(klist->lock)); // List of item lists ram += klist->item_mem_count * sizeof(*(klist->item_memory)); // items ram += klist->total * sizeof(K_ITEM); // List of data lists ram += klist->data_mem_count * sizeof(*(klist->data_memory)); // data ram += klist->total * klist->siz; // stores ram += klist->stores * sizeof(K_STORE); ram2 = klist->ram; snprintf(tmp, sizeof(tmp), "name:%d=%s%s%s%cinitial:%d=%d%callocated:%d=%d%c" "instore:%d=%d%cram:%d=%"PRIu64"%c" "ram2:%d=%"PRIu64"%ccull:%d=%d%c", rows, name, istree ? " (tree)" : "", klist->is_lock_only ? " (lock)" : "", FLDSEP, rows, klist->allocate, FLDSEP, rows, klist->total, FLDSEP, rows, klist->total - klist->count, FLDSEP, rows, ram, FLDSEP, rows, ram2, FLDSEP, rows, klist->cull_count, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); tot += ram + ram2; rows++; klists = klists->next; } ck_wunlock(&lock_check_lock); 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,instore,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", MIN_USERNAME, (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, NULL); } 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 and sharesummaries * 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, *i_markerid; WORKINFO *workinfo = NULL; WORKMARKERS *workmarkers; K_TREE_CTX ctx[1]; BLOCKS *blocks; MARKS *marks; char *action; int64_t workinfoid = -1, markerid = -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 = false, pps; 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, -m/genon will automatically do this * 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, NULL); 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); } DATA_WORKINFO(workinfo, w_item); ok = marks_process(conn, true, workinfo->poolinstance, 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, -m/genon will automatically do this * 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, 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 if (strcasecmp(action, "sum") == 0) { /* For the last available workmarker, * summarise it's sharesummaries into markersummaries * -m/genon will automatically do this * No parameters */ ok = make_markersummaries(true, by, code, inet, cd, trf_root); if (!ok) { snprintf(reply, siz, "%s failed", action); LOGERR("%s.%s", id, reply); return strdup(reply); } } else if (strcasecmp(action, "ready") == 0) { /* Mark a processed workmarker as ready * for fixing problems with markersummaries * Requires markerid */ i_markerid = require_name(trf_root, "markerid", 1, (char *)intpatt, reply, siz); if (!i_markerid) return strdup(reply); TXT_TO_BIGINT("markerid", transfer_data(i_markerid), markerid); K_RLOCK(workmarkers_free); wm_item = find_workmarkerid(markerid, true, '\0'); K_RUNLOCK(workmarkers_free); if (!wm_item) { snprintf(reply, siz, "unknown workmarkers with markerid %"PRId64, markerid); return strdup(reply); } DATA_WORKMARKERS(workmarkers, wm_item); if (!WMPROCESSED(workmarkers->status)) { snprintf(reply, siz, "markerid isn't processed %"PRId64, markerid); return strdup(reply); } ok = workmarkers_process(NULL, false, true, markerid, workmarkers->poolinstance, workmarkers->workinfoidend, workmarkers->workinfoidstart, workmarkers->description, MARKER_READY_STR, by, code, inet, cd, trf_root); if (!ok) { snprintf(reply, siz, "%s failed", action); LOGERR("%s.%s", id, reply); return strdup(reply); } } else if (strcasecmp(action, "processed") == 0) { /* Mark a workmarker as processed * Requires markerid */ i_markerid = require_name(trf_root, "markerid", 1, (char *)intpatt, reply, siz); if (!i_markerid) return strdup(reply); TXT_TO_BIGINT("markerid", transfer_data(i_markerid), markerid); K_RLOCK(workmarkers_free); wm_item = find_workmarkerid(markerid, true, '\0'); K_RUNLOCK(workmarkers_free); if (!wm_item) { snprintf(reply, siz, "unknown workmarkers with markerid %"PRId64, markerid); return strdup(reply); } DATA_WORKMARKERS(workmarkers, wm_item); if (WMPROCESSED(workmarkers->status)) { snprintf(reply, siz, "already processed markerid %"PRId64, markerid); return strdup(reply); } ok = workmarkers_process(NULL, false, true, markerid, workmarkers->poolinstance, workmarkers->workinfoidend, workmarkers->workinfoidstart, workmarkers->description, MARKER_PROCESSED_STR, by, code, inet, cd, trf_root); if (!ok) { snprintf(reply, siz, "%s failed", action); LOGERR("%s.%s", id, reply); return strdup(reply); } } else if (strcasecmp(action, "cancel") == 0) { /* Cancel(delete) all the markersummaries in a workmarker * This can only be done if the workmarker isn't processed * It reports on the console, summary information of the * markersummaries that were deleted * * WARNING ... if you do this after the workmarker has been * processed, after switching it to ready, there will no * longer be any matching shares or sharesummaries in ram * to regenerate the markersummaries, so you'd need to restart * ckdb to reload the shares to regenerate the markersummaries * HOWEVER, ckdb wont reload the shares if there is a later * workmarker that is already processed * * To reprocess an already processed workmarker, you'd have * to firstly turn off auto processing with genoff, then * change the required workmarker, and all after it, to * ready, then cancel them all, then finally restart ckdb * which will reload all the necessary shares and regenerate * the markersummaries * Of course if you don't have ALL the necessary shares in * the CCLs then you'd lose data doing this * * SS_to_MS will complain if any markersummaries already exist * when processing a workmarker * Normally you would use 'processed' if the markersummaries * are OK, and just the workmarker failed to be updated to * processed status * However, if there is actually something wrong with the * shift data (markersummaries) you can delete them and they * will be regenerated * This will usually only work as expected if the last * workmarker isn't marked as processed, but somehow there * are markersummaries for it in the DB, thus the reload * will reload all the shares for the workmarker then it * will print a warning every 13s on the console saying * that it can't process the workmarker * In this case you would cancel the workmarker then ckdb * will regenerate it from the shares/sharesummaries in ram * * Requires markerid */ i_markerid = require_name(trf_root, "markerid", 1, (char *)intpatt, reply, siz); if (!i_markerid) return strdup(reply); TXT_TO_BIGINT("markerid", transfer_data(i_markerid), markerid); K_RLOCK(workmarkers_free); wm_item = find_workmarkerid(markerid, true, '\0'); K_RUNLOCK(workmarkers_free); if (!wm_item) { snprintf(reply, siz, "unknown workmarkers with markerid %"PRId64, markerid); return strdup(reply); } DATA_WORKMARKERS(workmarkers, wm_item); if (WMPROCESSED(workmarkers->status)) { snprintf(reply, siz, "can't cancel a processed markerid %"PRId64, markerid); return strdup(reply); } ok = delete_markersummaries(NULL, workmarkers); if (!ok) { snprintf(reply, siz, "%s failed", action); LOGERR("%s.%s", id, reply); return strdup(reply); } } else if (strcasecmp(action, "genon") == 0) { /* Turn on auto marker generation and processing * and report the before/after status * No parameters */ bool old = markersummary_auto; markersummary_auto = true; snprintf(msg, sizeof(msg), "mark generation state was %s," " now %s", old ? "On" : "Off", markersummary_auto ? "On" : "Off"); ok = true; } else if (strcasecmp(action, "genoff") == 0) { /* Turn off auto marker generation and processing * and report the before/after status * No parameters */ bool old = markersummary_auto; markersummary_auto = false; snprintf(msg, sizeof(msg), "mark generation state was %s," " now %s", old ? "On" : "Off", markersummary_auto ? "On" : "Off"); ok = true; } else if (strcasecmp(action, "pps") == 0) { /* Recalculate a shift's rewards/rewarded * Require markerid */ i_markerid = require_name(trf_root, "markerid", 1, (char *)intpatt, reply, siz); if (!i_markerid) return strdup(reply); TXT_TO_BIGINT("markerid", transfer_data(i_markerid), markerid); K_RLOCK(workmarkers_free); wm_item = find_workmarkerid(markerid, false, MARKER_PROCESSED); K_RUNLOCK(workmarkers_free); if (!wm_item) { snprintf(reply, siz, "no markerid %"PRId64, markerid); return strdup(reply); } DATA_WORKMARKERS(workmarkers, wm_item); pps = shift_rewards(wm_item); if (pps) { snprintf(msg, sizeof(msg), "shift '%s' markerid %"PRId64" rewards %d " "rewarded %.3e pps %.3e", workmarkers->description, workmarkers->markerid, workmarkers->rewards, workmarkers->rewarded, workmarkers->pps_value); } else { snprintf(msg, sizeof(msg), "shift '%s' markerid %"PRId64" no rewards yet" " pps %.3e", workmarkers->description, workmarkers->markerid, workmarkers->pps_value); } ok = true; } 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); } // Layout the reply like cmd_shifts so the php/js code is similar static char *cmd_pshift(__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; K_ITEM *u_item, *p_item, *m_item, *wm_item, *ms_item, *wi_item; K_TREE_CTX wm_ctx[1]; WORKMARKERS *wm; WORKINFO *wi; MARKERSUMMARY *ms; PAYOUTS *payouts; USERS *users; MARKS *marks = NULL; char reply[1024] = ""; char tmp[1024]; size_t siz = sizeof(reply); char *buf; size_t len, off; tv_t marker_end = { 0L, 0L }; int rows; int64_t maxrows; double wm_count, d; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_username = adminuser(trf_root, 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); maxrows = user_sys_setting(users->userid, SHIFTS_SETTING_NAME, SHIFTS_DEFAULT, now); K_RLOCK(payouts_free); p_item = find_last_payouts(); K_RUNLOCK(payouts_free); if (p_item) { DATA_PAYOUTS(payouts, p_item); wm_count = payout_stats(payouts, "wm_count"); wm_count *= 1.42; if (maxrows < wm_count) maxrows = wm_count; } APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); rows = 0; K_RLOCK(workmarkers_free); wm_item = last_in_ktree(workmarkers_workinfoid_root, wm_ctx); DATA_WORKMARKERS_NULL(wm, wm_item); while (rows < (maxrows - 1) && wm_item) { if (CURRENT(&(wm->expirydate)) && WMPROCESSED(wm->status)) { K_RUNLOCK(workmarkers_free); K_RLOCK(marks_free); m_item = find_marks(wm->workinfoidend); K_RUNLOCK(marks_free); DATA_MARKS_NULL(marks, m_item); if (m_item == NULL) { // Log it but keep going LOGERR("%s() missing mark for markerid " "%"PRId64"/%s widend %"PRId64, __func__, wm->markerid, wm->description, wm->workinfoidend); } K_RLOCK(markersummary_free); K_RLOCK(workmarkers_free); ms_item = find_markersummary_p(wm->markerid); K_RUNLOCK(workmarkers_free); K_RUNLOCK(markersummary_free); if (ms_item) { DATA_MARKERSUMMARY(ms, ms_item); double_to_buf(ms->diffacc, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "%d_diffacc:%d=%s%c", 0, rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); d = ms->diffsta + ms->diffdup + ms->diffhi + ms->diffrej; double_to_buf(d, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "%d_diffinv:%d=%s%c", 0, rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(ms->shareacc, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "%d_shareacc:%d=%s%c", 0, rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); d = ms->sharesta + ms->sharedup + ms->sharehi + ms->sharerej; double_to_buf(d, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "%d_shareinv:%d=%s%c", 0, rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } if (marker_end.tv_sec == 0L) { wi_item = next_workinfo(wm->workinfoidend, NULL); if (!wi_item) { /* There's no workinfo after this shift * Unexpected ... estimate last wid+30s */ wi_item = find_workinfo(wm->workinfoidend, NULL); if (!wi_item) { // Nothing is currently locked LOGERR("%s() workmarker %"PRId64"/%s." " missing widend %"PRId64, __func__, wm->markerid, wm->description, wm->workinfoidend); snprintf(reply, siz, "data error 1"); free(buf); return(strdup(reply)); } DATA_WORKINFO(wi, wi_item); copy_tv(&marker_end, &(wi->createdate)); marker_end.tv_sec += 30; } else { DATA_WORKINFO(wi, wi_item); copy_tv(&marker_end, &(wi->createdate)); } } wi_item = find_workinfo(wm->workinfoidstart, NULL); if (!wi_item) { // Nothing is currently locked LOGERR("%s() workmarker %"PRId64"/%s. missing " "widstart %"PRId64, __func__, wm->markerid, wm->description, wm->workinfoidstart); snprintf(reply, siz, "data error 2"); free(buf); return(strdup(reply)); } DATA_WORKINFO(wi, wi_item); bigint_to_buf(wm->markerid, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "markerid:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); str_to_buf(wm->description, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "shift:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "endmarkextra:%d=%s%c", rows, m_item ? marks->extra : EMPTY, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ftv_to_buf(&(wi->createdate), reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "start:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ftv_to_buf(&marker_end, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "end:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; // Setup for next shift copy_tv(&marker_end, &(wi->createdate)); K_RLOCK(workmarkers_free); } wm_item = prev_in_ktree(wm_ctx); DATA_WORKMARKERS_NULL(wm, wm_item); } K_RUNLOCK(workmarkers_free); snprintf(tmp, sizeof(tmp), "%d_pool=%s%c", 0, "all", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "%d_flds=%s%c", 0, "diffacc,diffinv,shareacc,shareinv", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "prefix_all=%d_%c", 0, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "markerid,shift,start,end", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s", "Pool Shifts"); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), ",Pool_%d", 0); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "%carp=", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), ",%d_", 0); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.ok.%s", id, transfer_data(i_username)); return(buf); } /* Show a share status report on the console * Currently: sequence status and OoO info */ static char *cmd_shsta(__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 ooo_buf[256]; char buf[256]; LOGWARNING("OoO %s", ooo_status(ooo_buf, sizeof(ooo_buf))); sequence_report(true); snprintf(buf, sizeof(buf), "ok.%s", cmd); LOGDEBUG("%s.%s", id, buf); return strdup(buf); } static char *cmd_userinfo(__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 *ui_item; USERINFO *userinfo; char reply[1024] = ""; char tmp[1024]; size_t len, off; double d; char *buf; int rows; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); rows = 0; K_RLOCK(userinfo_free); ui_item = STORE_RHEAD(userinfo_store); while (ui_item) { DATA_USERINFO(userinfo, ui_item); str_to_buf(userinfo->username, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "username:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "blocks:%d=%d%c", rows, userinfo->blocks - (userinfo->orphans + userinfo->rejects), FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "orphans:%d=%d%c", rows, userinfo->orphans, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(userinfo->diffacc, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "diffacc:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); d = userinfo->diffsta + userinfo->diffdup + userinfo->diffhi + userinfo->diffrej; double_to_buf(d, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "diffinv:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); double_to_buf(userinfo->shareacc, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "shareacc:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); d = userinfo->sharesta + userinfo->sharedup + userinfo->sharehi + userinfo->sharerej; double_to_buf(d, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "shareinv:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "lastblock=%ld%c", userinfo->last_block.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; ui_item = ui_item->next; } K_RUNLOCK(userinfo_free); snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "username,blocks,orphans,diffacc,diffinv,shareacc,shareinv," "lastblock", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=", "UserInfo", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); LOGDEBUG("%s.ok.%d_rows", id, rows); return buf; } /* Set/show the BTC server settings * You must supply the btcserver to change anything * The format for userpass is username:password * If you don't supply the btcserver it will simply report the current server * If you supply btcserver but not the userpass it will use the current userpass * The reply will ONLY contain the URL, not the user/pass */ static char *cmd_btcset(__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_btcserver, *i_userpass; char *btcserver = NULL, *userpass = NULL, *tmp; char reply[1024] = ""; size_t siz = sizeof(reply); char buf[256]; i_btcserver = optional_name(trf_root, "btcserver", 1, NULL, reply, siz); if (i_btcserver) { btcserver = strdup(transfer_data(i_btcserver)); i_userpass = optional_name(trf_root, "userpass", 0, NULL, reply, siz); if (i_userpass) userpass = transfer_data(i_userpass); ck_wlock(&btc_lock); btc_server = btcserver; btcserver = NULL; if (userpass) { if (btc_auth) { tmp = btc_auth; while (*tmp) *(tmp++) = '\0'; } FREENULL(btc_auth); btc_auth = http_base64(userpass); } ck_wunlock(&btc_lock); if (userpass) { tmp = userpass; while (*tmp) *(tmp++) = '\0'; } } FREENULL(btcserver); ck_wlock(&btc_lock); snprintf(buf, sizeof(buf), "ok.btcserver=%s", btc_server); ck_wunlock(&btc_lock); LOGDEBUG("%s.%s.%s", id, cmd, buf); return strdup(buf); } /* Query CKDB for certain information * See each string compare below of 'request' for the list of queries * For non-error conditions, rows=0 means there were no matching results * for the request, and rows=n is placed last in the reply */ static char *cmd_query(__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, K_TREE *trf_root) { K_TREE_CTX ctx[1]; char cd_buf[DATE_BUFSIZ]; char reply[1024] = ""; size_t siz = sizeof(reply); char tmp[1024] = ""; char msg[1024] = ""; char *buf = NULL; size_t len, off; K_ITEM *i_request; char *request; bool ok = false; int rows = 0; LOGDEBUG("%s(): cmd '%s'", __func__, cmd); i_request = require_name(trf_root, "request", 1, NULL, reply, siz); if (!i_request) return strdup(reply); request = transfer_data(i_request); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); if (strcasecmp(request, "block") == 0) { /* return DB information for the blocks with height=value * if expired= is present, it will also return expired records */ K_ITEM *i_height, *i_expired, *b_item; bool expired = false; BLOCKS *blocks; int32_t height; 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); i_expired = optional_name(trf_root, "expired", 0, NULL, reply, siz); if (i_expired) expired = true; int_to_buf(height, reply, sizeof(reply)); snprintf(msg, sizeof(msg), "height=%s", reply); K_RLOCK(blocks_free); b_item = find_prev_blocks(height, ctx); DATA_BLOCKS_NULL(blocks, b_item); while (b_item && blocks->height <= height) { if ((expired || CURRENT(&(blocks->expirydate))) && blocks->height == height) { 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); snprintf(tmp, sizeof(tmp), "blockhash:%d=%s%c", rows, blocks->blockhash, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "confirmed:%d=%s%c", rows, blocks->confirmed, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); tv_to_buf(&(blocks->expirydate), cd_buf, sizeof(cd_buf)); snprintf(tmp, sizeof(tmp), EDDB"_str:%d=%s%c", rows, cd_buf, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); tv_to_buf(&(blocks->createdate), cd_buf, sizeof(cd_buf)); snprintf(tmp, sizeof(tmp), CDDB"_str:%d=%s%c", rows, cd_buf, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); tv_to_buf(&(blocks->blockcreatedate), cd_buf, sizeof(cd_buf)); snprintf(tmp, sizeof(tmp), "block"CDDB"_str:%d=%s%c", rows, cd_buf, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(blocks->workinfoid, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "workinfoid:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; } b_item = next_in_ktree(ctx); DATA_BLOCKS_NULL(blocks, b_item); } K_RUNLOCK(blocks_free); snprintf(tmp, sizeof(tmp), "flds=%s%c", "height,blockhash,confirmed,"EDDB"_str," CDDB"_str,block"CDDB"_str,workinfoid", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s%c", "Blocks", FLDSEP, "", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ok = true; } else if (strcasecmp(request, "workinfo") == 0) { /* return DB information for the workinfo with wid=value * if expired= is present, it will also return expired records * though ckdb doesn't expire workinfo records - only external * pgsql scripts would do that to the DB, then ckdb would * load them the next time it (re)starts */ K_ITEM *i_wid, *i_expired, *wi_item, *wm_item; bool expired = false; WORKINFO *workinfo; WORKMARKERS *wm; int64_t wid; i_wid = require_name(trf_root, "wid", 1, (char *)intpatt, reply, siz); if (!i_wid) return strdup(reply); TXT_TO_BIGINT("wid", transfer_data(i_wid), wid); i_expired = optional_name(trf_root, "expired", 0, NULL, reply, siz); if (i_expired) expired = true; bigint_to_buf(wid, reply, sizeof(reply)); snprintf(msg, sizeof(msg), "wid=%s", reply); /* We look for the 'next' (or last) workinfo then go backwards * to ensure we find all expired records in case they * were requested */ K_RLOCK(workinfo_free); wi_item = next_workinfo(wid, ctx); if (!wi_item) wi_item = last_in_ktree(workinfo_root, ctx); DATA_WORKINFO_NULL(workinfo, wi_item); while (wi_item && workinfo->workinfoid >= wid) { if ((expired || CURRENT(&(workinfo->expirydate))) && workinfo->workinfoid == wid) { bigint_to_buf(workinfo->workinfoid, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "workinfoid:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); int_to_buf(workinfo->height, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "height:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "prevhash:%d=%s%c", rows, workinfo->prevhash, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); tv_to_buf(&(workinfo->expirydate), cd_buf, sizeof(cd_buf)); snprintf(tmp, sizeof(tmp), EDDB"_str:%d=%s%c", rows, cd_buf, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); tv_to_buf(&(workinfo->createdate), cd_buf, sizeof(cd_buf)); snprintf(tmp, sizeof(tmp), CDDB"_str:%d=%s%c", rows, cd_buf, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "ndiff:%d=%.1f%c", rows, workinfo->diff_target, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "ppsvalue:%d=%.15f%c", rows, workinfo_pps(wi_item, workinfo->workinfoid), FLDSEP); APPEND_REALLOC(buf, off, len, tmp); K_RLOCK(workmarkers_free); wm_item = find_workmarkers(wid, false, MARKER_PROCESSED, NULL); K_RUNLOCK(workmarkers_free); if (!wm_item) { snprintf(tmp, sizeof(tmp), "markerid:%d=%c", rows, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "shift:%d=%c", rows, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "shiftend:%d=%c", rows, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "shiftstart:%d=%c", rows, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } else { DATA_WORKMARKERS(wm, wm_item); bigint_to_buf(wm->markerid, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "markerid:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "shift:%d=%s%c", rows, wm->description, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(wm->workinfoidend, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "shiftend:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(wm->workinfoidstart, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "shiftstart:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } rows++; } wi_item = prev_in_ktree(ctx); DATA_WORKINFO_NULL(workinfo, wi_item); } K_RUNLOCK(workinfo_free); snprintf(tmp, sizeof(tmp), "flds=%s%c", "workinfoid,height,prevhash,"EDDB"_str,"CDDB"_str," "ndiff,ppsvalue,markerid,shift,shiftend,shiftstart", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s%c", "Workinfo", FLDSEP, "", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ok = true; } else if (strcasecmp(request, "range") == 0) { /* Return the workinfoid range that has block height=height * WARNING! This will traverse workinfo from the end back to * the given height, and thus since workinfo is the 2nd * largest tree, it may access swapped data if you request * older data */ K_ITEM *i_height, *wi_item; WORKINFO *workinfo; int32_t height, this_height; int64_t idend, idstt; 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); int_to_buf(height, reply, sizeof(reply)); snprintf(msg, sizeof(msg), "height=%s", reply); idend = idstt = 0L; /* Start from the last workinfo and continue until we get * below block 'height' */ K_RLOCK(workinfo_free); wi_item = last_in_ktree(workinfo_root, ctx); DATA_WORKINFO_NULL(workinfo, wi_item); while (wi_item) { this_height = workinfo->height; if (this_height < height) break; if (CURRENT(&(workinfo->expirydate)) && this_height == height) { if (idend == 0L) idend = workinfo->workinfoid; idstt = workinfo->workinfoid; } wi_item = prev_in_ktree(ctx); DATA_WORKINFO_NULL(workinfo, wi_item); } K_RUNLOCK(workinfo_free); int_to_buf(height, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "height:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(idend, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "workinfoidend:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(idstt, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "workinfoidstart:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; snprintf(tmp, sizeof(tmp), "flds=%s%c", "height,workinfoidend,workinfoidstart", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s%c", "WorkinfoRange", FLDSEP, "", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ok = true; } else if (strcasecmp(request, "diff") == 0) { /* return the details of the next diff change after * block height=height * WARNING! This will traverse workinfo from the end back to * the given height, and thus since workinfo is the 2nd * largest tree, it may access swapped data if you request * older data */ K_ITEM *i_height, *wi_item; WORKINFO *workinfo = NULL; int32_t height, this_height; char bits[TXT_SML+1]; bool got = false; 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); int_to_buf(height, reply, sizeof(reply)); snprintf(msg, sizeof(msg), "height=%s", reply); snprintf(tmp, sizeof(tmp), "height0:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); /* Start from the last workinfo and continue until we get * below block 'height' */ K_RLOCK(workinfo_free); wi_item = last_in_ktree(workinfo_root, ctx); DATA_WORKINFO_NULL(workinfo, wi_item); while (wi_item) { if (CURRENT(&(workinfo->expirydate))) { this_height = workinfo->height; if (this_height < height) break; } wi_item = prev_in_ktree(ctx); DATA_WORKINFO_NULL(workinfo, wi_item); } // If we fell off the front use the first one if (!wi_item) wi_item = first_in_ktree(workinfo_root, ctx); DATA_WORKINFO_NULL(workinfo, wi_item); while (wi_item) { if (CURRENT(&(workinfo->expirydate))) { this_height = workinfo->height; if (this_height >= height) break; } wi_item = next_in_ktree(ctx); DATA_WORKINFO_NULL(workinfo, wi_item); } if (wi_item) { DATA_WORKINFO(workinfo, wi_item); this_height = workinfo->height; if (this_height == height) { // We have our starting point STRNCPY(bits, workinfo->bits); got = true; bigint_to_buf(workinfo->workinfoid, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "workinfoid0:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "ndiff0:%d=%.1f%c", rows, workinfo->diff_target, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); while (wi_item) { if (CURRENT(&(workinfo->expirydate))) { if (strcmp(bits, workinfo->bits) != 0) break; } wi_item = next_in_ktree(ctx); DATA_WORKINFO_NULL(workinfo, wi_item); } } else wi_item = NULL; } K_RUNLOCK(workinfo_free); if (!got) { snprintf(tmp, sizeof(tmp), "workinfoid0:%d=%c", rows, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "ndiff0:%d=%c", rows, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } if (!wi_item) { snprintf(tmp, sizeof(tmp), "height:%d=%c", rows, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "workinfoid:%d=%c", rows, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "ndiff:%d=%c", rows, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } else { this_height = workinfo->height; int_to_buf(this_height, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "height:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(workinfo->workinfoid, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "workinfoid:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "ndiff:%d=%.1f%c", rows, workinfo->diff_target, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); } rows++; snprintf(tmp, sizeof(tmp), "flds=%s%c", "height0,workinfoid0,ndiff0,height,workinfo,ndiff", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s%c", "WorkinfoRange", FLDSEP, "", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ok = true; } else if (strcasecmp(request, "payout") == 0) { /* return the details of the payouts for block height=height * if expired= is present, also return expired records */ K_ITEM *i_height, *i_expired, *p_item; PAYOUTS *payouts = NULL; bool expired = false; int32_t height; char *stats = NULL, *ptr; 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); int_to_buf(height, reply, sizeof(reply)); snprintf(msg, sizeof(msg), "height=%s", reply); i_expired = optional_name(trf_root, "expired", 0, NULL, reply, siz); if (i_expired) expired = true; K_RLOCK(payouts_free); p_item = first_payouts(height, ctx); DATA_PAYOUTS_NULL(payouts, p_item); while (p_item && payouts->height == height) { if (expired || CURRENT(&(payouts->expirydate))) { int_to_buf(payouts->height, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "height:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(payouts->payoutid, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "payoutid:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(payouts->minerreward, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "minerreward:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(payouts->workinfoidstart, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "workinfoidstart:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(payouts->workinfoidend, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "workinfoidend:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); bigint_to_buf(payouts->elapsed, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "elapsed:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "status:%d=%s%c", rows, payouts->status, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "diffwanted:%d=%f%c", rows, payouts->diffwanted, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "diffused:%d=%f%c", rows, payouts->diffused, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ptr = stats = strdup(payouts->stats); if (!stats) { quithere(1, "strdup (%"PRId64") OOM", payouts->payoutid); } while (*ptr) { if (*ptr == FLDSEP) *ptr = ' '; ptr++; } snprintf(tmp, sizeof(tmp), "stats:%d=%s%c", rows, stats, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); FREENULL(stats); tv_to_buf(&(payouts->expirydate), cd_buf, sizeof(cd_buf)); snprintf(tmp, sizeof(tmp), EDDB"_str:%d=%s%c", rows, cd_buf, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); tv_to_buf(&(payouts->createdate), cd_buf, sizeof(cd_buf)); snprintf(tmp, sizeof(tmp), CDDB"_str:%d=%s%c", rows, cd_buf, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; } p_item = next_in_ktree(ctx); DATA_PAYOUTS_NULL(payouts, p_item); } K_RUNLOCK(payouts_free); snprintf(tmp, sizeof(tmp), "flds=%s%c", "height,payoutid,minerreward,workinfoidstart," "workinfoidend,elapsed,status,diffwanted,diffused," "stats,"EDDB"_str,"CDDB"_str", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "arn=%s%carp=%s%c", "Payouts", FLDSEP, "", FLDSEP); APPEND_REALLOC(buf, off, len, tmp); ok = true; } else { free(buf); snprintf(reply, siz, "unknown request '%s'", request); LOGERR("%s() %s.%s", __func__, id, reply); return strdup(reply); } if (!ok) { free(buf); snprintf(reply, siz, "failed.%s%s%s", request, msg[0] ? " " : "", msg[0] ? msg : ""); LOGERR("%s() %s.%s", __func__, id, reply); return strdup(reply); } snprintf(tmp, sizeof(tmp), "rows=%d", rows); APPEND_REALLOC(buf, off, len, tmp); LOGWARNING("%s() %s.%s%s%s", __func__, id, request, msg[0] ? " " : "", msg[0] ? msg : ""); return buf; } // Query and disable internal lock detection code static char *cmd_locks(__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) { bool code_locks = false, code_deadlocks = false; bool was_locks = false, was_deadlocks = false; bool new_locks = false, new_deadlocks = false; char reply[1024] = ""; size_t siz = sizeof(reply); #if LOCK_CHECK K_ITEM *i_locks, *i_deadlocks; char *deadlocks; #endif LOGDEBUG("%s(): cmd '%s'", __func__, cmd); #if LOCK_CHECK code_locks = true; was_locks = new_locks = check_locks; code_deadlocks = true; was_deadlocks = new_locks = check_deadlocks; #endif /* options are * locks <- disable lock checking if it's enabled (value ignored) * deadlocks=Y/N <- enable/disable deadlock prediction * any word with any case starting with 'Y' means enable it * anything else means disable it * When you enable it, it won't re-enable it for threads that * have failed a deadlock prediction test * It will report the status of both */ #if LOCK_CHECK i_locks = optional_name(trf_root, "locks", 0, NULL, reply, siz); if (i_locks) new_locks = check_locks = false; i_deadlocks = optional_name(trf_root, "deadlocks", 0, NULL, reply, siz); if (i_deadlocks) { deadlocks = transfer_data(i_deadlocks); if (toupper(*deadlocks) == TRUE_CHR) check_deadlocks = true; else check_deadlocks = false; new_deadlocks = check_deadlocks; } #endif snprintf(reply, siz, "code_locks=%s%cwas_locks=%s%cnew_locks=%s%c" "code_deadlocks=%s%cwas_deadlocks=%s%cnew_deadlocks=%s", TFSTR(code_locks), FLDSEP, TFSTR(was_locks), FLDSEP, TFSTR(new_locks), FLDSEP, TFSTR(code_deadlocks), FLDSEP, TFSTR(was_deadlocks), FLDSEP, TFSTR(new_deadlocks)); LOGWARNING("%s() %s.%s", __func__, id, reply); return strdup(reply); } static void event_tree(K_TREE *event_tree, char *list, char *reply, size_t siz, char *buf, size_t *off, size_t *len, int *rows) { K_TREE_CTX ctx[1]; K_ITEM *e_item; EVENTS *e; e_item = first_in_ktree(event_tree, ctx); while (e_item) { DATA_EVENTS(e, e_item); if (CURRENT(&(e->expirydate))) { snprintf(reply, siz, "list:%d=%s%c", *rows, list, FLDSEP); APPEND_REALLOC(buf, *off, *len, reply); snprintf(reply, siz, "id:%d=%d%c", *rows, e->id, FLDSEP); APPEND_REALLOC(buf, *off, *len, reply); snprintf(reply, siz, "user:%d=%s%c", *rows, e->createby, FLDSEP); APPEND_REALLOC(buf, *off, *len, reply); if (event_tree == events_ipc_root) { snprintf(reply, siz, "ipc:%d=%s%c", *rows, e->ipc, FLDSEP); APPEND_REALLOC(buf, *off, *len, reply); } else { snprintf(reply, siz, "ip:%d=%s%c", *rows, e->createinet, FLDSEP); APPEND_REALLOC(buf, *off, *len, reply); } if (event_tree == events_hash_root) { snprintf(reply, siz, "hash:%d=%.8s%c", *rows, e->hash, FLDSEP); APPEND_REALLOC(buf, *off, *len, reply); } snprintf(reply, siz, CDTRF":%d=%ld%c", (*rows)++, e->createdate.tv_sec, FLDSEP); APPEND_REALLOC(buf, *off, *len, reply); } e_item = next_in_ktree(ctx); } } // Events status/settings static char *cmd_events(__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, K_TREE *trf_root) { K_ITEM *i_action, *i_cmd, *i_list, *i_ip, *i_eventname, *i_lifetime; K_ITEM *i_des, *i_item; K_TREE_CTX ctx[1]; IPS *ips; char *action, *alert_cmd, *list, *ip, *eventname, *des; char reply[1024] = ""; size_t siz = sizeof(reply); char tmp[1024] = ""; char *buf = NULL; size_t len, off; int i, rows, oldlife, lifetime; 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, "cmd") == 0) { /* Change ckdb_alert_cmd to 'cmd' * blank to disable it */ i_cmd = require_name(trf_root, "cmd", 0, NULL, reply, siz); if (!i_cmd) return strdup(reply); alert_cmd = transfer_data(i_cmd); if (strlen(alert_cmd) > MAX_ALERT_CMD) return strdup("Invalid cmd length - limit " STRINT(MAX_ALERT_CMD)); K_WLOCK(event_limits_free); FREENULL(ckdb_alert_cmd); if (*alert_cmd) ckdb_alert_cmd = strdup(alert_cmd); K_WUNLOCK(event_limits_free); APPEND_REALLOC_INIT(buf, off, len); if (*alert_cmd) APPEND_REALLOC(buf, off, len, "ok.cmd set"); else APPEND_REALLOC(buf, off, len, "ok.cmd disabled"); } else if (strcasecmp(action, "settings") == 0) { // Return all current event settings APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); K_RLOCK(event_limits_free); i = -1; while (e_limits[++i].name) { #define EVENTFLD(_fld) do { \ snprintf(tmp, sizeof(tmp), "%s_" #_fld "=%d%c", \ e_limits[i].name, e_limits[i]._fld, FLDSEP); \ APPEND_REALLOC(buf, off, len, tmp); \ } while (0) EVENTFLD(user_low_time); EVENTFLD(user_low_time_limit); EVENTFLD(user_hi_time); EVENTFLD(user_hi_time_limit); EVENTFLD(ip_low_time); EVENTFLD(ip_low_time_limit); EVENTFLD(ip_hi_time); EVENTFLD(ip_hi_time_limit); EVENTFLD(lifetime); } snprintf(tmp, sizeof(tmp), "event_limits_hash_lifetime=%d", event_limits_hash_lifetime); APPEND_REALLOC(buf, off, len, tmp); K_RUNLOCK(event_limits_free); } else if (strcasecmp(action, "events") == 0) { /* List the event tree contents * List is 'all' or one of: hash, user, ip or ipc <- tree names * Output can be large - check web Admin->ckp for tree sizes */ bool all, one = false; i_list = require_name(trf_root, "list", 1, NULL, reply, siz); if (!i_list) return strdup(reply); list = transfer_data(i_list); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); rows = 0; all = (strcmp(list, "all") == 0); K_RLOCK(events_free); if (all || strcmp(list, "user") == 0) { one = true; event_tree(events_user_root, "user", reply, siz, buf, &off, &len, &rows); } if (all || strcmp(list, "ip") == 0) { one = true; event_tree(events_ip_root, "ip", reply, siz, buf, &off, &len, &rows); } if (all || strcmp(list, "ipc") == 0) { one = true; event_tree(events_ipc_root, "ipc", reply, siz, buf, &off, &len, &rows); } if (all || strcmp(list, "hash") == 0) { one = true; event_tree(events_hash_root, "hash", reply, siz, buf, &off, &len, &rows); } K_RUNLOCK(events_free); if (!one) { free(buf); snprintf(reply, siz, "unknown stats list '%s'", list); LOGERR("%s() %s.%s", __func__, id, reply); return strdup(reply); } snprintf(tmp, sizeof(tmp), "rows=%d", rows); APPEND_REALLOC(buf, off, len, tmp); } else if (strcasecmp(action, "ips") == 0) { // List the ips tree contents APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); rows = 0; K_RLOCK(ips_free); i_item = first_in_ktree(ips_root, ctx); while (i_item) { DATA_IPS(ips, i_item); if (CURRENT(&(ips->expirydate))) { snprintf(tmp, sizeof(tmp), "group:%d=%s%c", rows, ips->group, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "ip:%d=%s%c", rows, ips->ip, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "description:%d=%s%c", rows, ips->description ? : EMPTY, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "lifetime:%d=%d%c", rows, ips->lifetime, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "log:%d=%c%c", rows, ips->log ? TRUE_CHR : FALSE_CHR, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); snprintf(reply, siz, CDTRF":%d=%ld%c", rows++, ips->createdate.tv_sec, FLDSEP); APPEND_REALLOC(buf, off, len, reply); } i_item = next_in_ktree(ctx); } K_RUNLOCK(ips_free); snprintf(tmp, sizeof(tmp), "rows=%d", rows); APPEND_REALLOC(buf, off, len, tmp); } else if (strcasecmp(action, "ban") == 0) { /* Ban the ip with optional eventname and lifetime * N.B. this doesn't survive a CKDB restart * use just cmd_setopts for permanent bans */ bool found = false; oldlife = 0; i_ip = require_name(trf_root, "ip", 1, NULL, reply, siz); if (!i_ip) return strdup(reply); ip = transfer_data(i_ip); i_eventname = optional_name(trf_root, "eventname", 1, NULL, reply, siz); if (i_eventname) eventname = transfer_data(i_eventname); else { if (*reply) return strdup(reply); eventname = EVENTNAME_ALL; } i_lifetime = optional_name(trf_root, "lifetime", 1, (char *)intpatt, reply, siz); if (i_lifetime) lifetime = atoi(transfer_data(i_lifetime)); else { if (*reply) return strdup(reply); // default to almost 42 years :) lifetime = 60*60*24*365*42; } i_des = optional_name(trf_root, "des", 1, NULL, reply, siz); if (i_des) des = transfer_data(i_des); else { if (*reply) return strdup(reply); des = NULL; } K_WLOCK(ips_free); i_item = find_ips(IPS_GROUP_BAN, ip, eventname, NULL); if (i_item) { DATA_IPS(ips, i_item); found = true; oldlife = ips->lifetime; ips->lifetime = lifetime; // Don't change it if it's not supplied if (des) { LIST_MEM_SUB(ips_free, ips->description); FREENULL(ips->description); ips->description = strdup(des); LIST_MEM_ADD(ips_free, ips->description); } } else { ips_add(IPS_GROUP_BAN, ip, eventname, is_elimitname(eventname, true), des, true, false, lifetime, true); } K_WUNLOCK(ips_free); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); if (found) { snprintf(tmp, sizeof(tmp), "already %s/%s %d->%d", ip, eventname, oldlife, lifetime); } else { snprintf(tmp, sizeof(tmp), "ban %s/%s %d", ip, eventname, lifetime); } APPEND_REALLOC(buf, off, len, tmp); } else if (strcasecmp(action, "unban") == 0) { /* Unban the ip+eventname - sets lifetime to 1 meaning * it expires 1 second after it was created * so next access will remove the ban and succeed * N.B. if it was a permanent 'cmd_setopts' ban, the unban * won't survive a CKDB restart. * You need to BOTH use this AND remove the optioncontrol * record from the database to permanently remove a ban * (since there's no cmd_expopts ... yet) */ bool found = false; i_ip = require_name(trf_root, "ip", 1, NULL, reply, siz); if (!i_ip) return strdup(reply); ip = transfer_data(i_ip); i_eventname = require_name(trf_root, "eventname", 1, NULL, reply, siz); if (!i_eventname) return strdup(reply); eventname = transfer_data(i_eventname); K_WLOCK(ips_free); i_item = find_ips(IPS_GROUP_BAN, ip, eventname, NULL); if (i_item) { found = true; DATA_IPS(ips, i_item); ips->lifetime = 1; } K_WUNLOCK(ips_free); APPEND_REALLOC_INIT(buf, off, len); if (found) { APPEND_REALLOC(buf, off, len, "ok."); APPEND_REALLOC(buf, off, len, ip); APPEND_REALLOC(buf, off, len, "/"); APPEND_REALLOC(buf, off, len, eventname); APPEND_REALLOC(buf, off, len, " unbanned"); } else { APPEND_REALLOC(buf, off, len, "ERR.unknown ip+eventname"); } } else { snprintf(reply, siz, "unknown action '%s'", action); LOGERR("%s() %s.%s", __func__, id, reply); return strdup(reply); } return buf; } /* 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 * * terminate * .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 seq access struct CMDS ckdb_cmds[] = { { CMD_TERMINATE, "terminate", true, false, NULL, SEQ_NONE, ACCESS_SYSTEM }, { CMD_PING, "ping", true, false, NULL, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, { CMD_VERSION, "version", true, false, NULL, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, { CMD_LOGLEVEL, "loglevel", true, false, NULL, SEQ_NONE, ACCESS_SYSTEM }, { CMD_FLUSH, "flush", true, false, NULL, SEQ_NONE, ACCESS_SYSTEM }, { CMD_SHARELOG, STR_WORKINFO, false, true, cmd_sharelog, SEQ_WORKINFO, ACCESS_POOL }, { CMD_SHARELOG, STR_SHARES, false, true, cmd_sharelog, SEQ_SHARES, ACCESS_POOL }, { CMD_SHARELOG, STR_SHAREERRORS,false, true, cmd_sharelog, SEQ_SHAREERRORS,ACCESS_POOL }, { CMD_SHARELOG, STR_AGEWORKINFO,false, true, cmd_sharelog, SEQ_AGEWORKINFO,ACCESS_POOL }, { CMD_AUTH, "authorise", false, true, cmd_auth, SEQ_AUTH, ACCESS_POOL }, { CMD_ADDRAUTH, "addrauth", false, true, cmd_addrauth, SEQ_ADDRAUTH, ACCESS_POOL }, { CMD_HEARTBEAT,"heartbeat", false, true, cmd_heartbeat, SEQ_HEARTBEAT, ACCESS_POOL }, { CMD_ADDUSER, "adduser", false, false, cmd_adduser, SEQ_NONE, ACCESS_WEB }, { CMD_NEWPASS, "newpass", false, false, cmd_newpass, SEQ_NONE, ACCESS_WEB }, { CMD_CHKPASS, "chkpass", false, false, cmd_chkpass, SEQ_NONE, ACCESS_WEB }, { CMD_2FA, "2fa", false, false, cmd_2fa, SEQ_NONE, ACCESS_WEB }, { CMD_USERSET, "usersettings", false, false, cmd_userset, SEQ_NONE, ACCESS_WEB }, { CMD_WORKERSET,"workerset", false, false, cmd_workerset, SEQ_NONE, ACCESS_WEB }, { CMD_POOLSTAT, "poolstats", false, true, cmd_poolstats, SEQ_POOLSTATS, ACCESS_POOL }, { CMD_USERSTAT, "userstats", false, true, cmd_userstats, SEQ_NONE, ACCESS_POOL }, { CMD_WORKERSTAT,"workerstats", false, true, cmd_workerstats,SEQ_WORKERSTAT, ACCESS_POOL }, { CMD_BLOCK, "block", false, true, cmd_blocks, SEQ_BLOCK, ACCESS_POOL }, { CMD_BLOCKLIST,"blocklist", false, false, cmd_blocklist, SEQ_NONE, ACCESS_WEB }, { CMD_BLOCKSTATUS,"blockstatus",false, false, cmd_blockstatus,SEQ_NONE, ACCESS_SYSTEM }, { CMD_NEWID, "newid", false, false, cmd_newid, SEQ_NONE, ACCESS_SYSTEM }, { CMD_PAYMENTS, "payments", false, false, cmd_payments, SEQ_NONE, ACCESS_WEB }, { CMD_WORKERS, "workers", false, false, cmd_workers, SEQ_NONE, ACCESS_WEB }, { CMD_ALLUSERS, "allusers", false, false, cmd_allusers, SEQ_NONE, ACCESS_WEB }, { CMD_HOMEPAGE, "homepage", false, false, cmd_homepage, SEQ_NONE, ACCESS_WEB }, { CMD_GETATTS, "getatts", false, false, cmd_getatts, SEQ_NONE, ACCESS_WEB }, { CMD_SETATTS, "setatts", false, false, cmd_setatts, SEQ_NONE, ACCESS_WEB }, { CMD_EXPATTS, "expatts", false, false, cmd_expatts, SEQ_NONE, ACCESS_WEB }, { CMD_GETOPTS, "getopts", false, false, cmd_getopts, SEQ_NONE, ACCESS_WEB }, { CMD_SETOPTS, "setopts", false, false, cmd_setopts, SEQ_NONE, ACCESS_WEB }, { CMD_DSP, "dsp", false, false, cmd_dsp, SEQ_NONE, ACCESS_SYSTEM }, { CMD_STATS, "stats", true, false, cmd_stats, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, { CMD_PPLNS, "pplns", false, false, cmd_pplns, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, { CMD_PPLNS2, "pplns2", false, false, cmd_pplns2, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, { CMD_PAYOUTS, "payouts", false, false, cmd_payouts, SEQ_NONE, ACCESS_SYSTEM }, { CMD_MPAYOUTS, "mpayouts", false, false, cmd_mpayouts, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, { CMD_SHIFTS, "shifts", false, false, cmd_shifts, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, { CMD_USERSTATUS,"userstatus", false, false, cmd_userstatus, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, { CMD_MARKS, "marks", false, false, cmd_marks, SEQ_NONE, ACCESS_SYSTEM }, { CMD_PSHIFT, "pshift", false, false, cmd_pshift, SEQ_NONE, ACCESS_SYSTEM | ACCESS_WEB }, { CMD_SHSTA, "shsta", true, false, cmd_shsta, SEQ_NONE, ACCESS_SYSTEM }, { CMD_USERINFO, "userinfo", false, false, cmd_userinfo, SEQ_NONE, ACCESS_WEB }, { CMD_BTCSET, "btcset", false, false, cmd_btcset, SEQ_NONE, ACCESS_SYSTEM }, { CMD_QUERY, "query", false, false, cmd_query, SEQ_NONE, ACCESS_SYSTEM }, { CMD_LOCKS, "locks", false, false, cmd_locks, SEQ_NONE, ACCESS_SYSTEM }, { CMD_EVENTS, "events", false, false, cmd_events, SEQ_NONE, ACCESS_SYSTEM }, { CMD_END, NULL, false, false, NULL, SEQ_NONE, 0 } };