diff --git a/src/ckdb.c b/src/ckdb.c index 581f1967..2be37ab4 100644 --- a/src/ckdb.c +++ b/src/ckdb.c @@ -2909,7 +2909,7 @@ static void summarise_blocks() diffacc = diffinv = shareacc = shareinv = 0; elapsed = 0; K_RLOCK(blocks_free); - b_prev = find_prev_blocks(blocks->height); + b_prev = find_prev_blocks(blocks->height, NULL); K_RUNLOCK(blocks_free); if (!b_prev) { wi_start = 0; @@ -4128,6 +4128,7 @@ static void *socketer(__maybe_unused void *arg) /* Process, but reject (loading) until startup_complete * and don't test for duplicates */ case CMD_MARKS: + case CMD_QUERY: if (!startup_complete) { snprintf(reply, sizeof(reply), "%s.%ld.loading.%s", @@ -4356,6 +4357,7 @@ static void reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) case CMD_SHSTA: case CMD_USERINFO: case CMD_BTCSET: + case CMD_QUERY: LOGERR("%s() INVALID message line %"PRIu64 " ignored '%.42s...", __func__, count, diff --git a/src/ckdb.h b/src/ckdb.h index cda98b56..17bd6b70 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -55,7 +55,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.3" -#define CKDB_VERSION DB_VERSION"-1.334" +#define CKDB_VERSION DB_VERSION"-1.340" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ @@ -410,6 +410,7 @@ enum cmd_values { CMD_SHSTA, CMD_USERINFO, CMD_BTCSET, + CMD_QUERY, CMD_END }; @@ -2332,7 +2333,7 @@ extern double _blockhash_diff(char *hash, WHERE_FFL_ARGS); extern void dsp_blocks(K_ITEM *item, FILE *stream); extern cmp_t cmp_blocks(K_ITEM *a, K_ITEM *b); extern K_ITEM *find_blocks(int32_t height, char *blockhash, K_TREE_CTX *ctx); -extern K_ITEM *find_prev_blocks(int32_t height); +extern K_ITEM *find_prev_blocks(int32_t height, K_TREE_CTX *ctx); extern const char *blocks_confirmed(char *confirmed); extern void zero_on_new_block(); extern void set_block_share_counters(); diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index d1ec179c..8dccf664 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -5795,7 +5795,7 @@ static char *cmd_marks(PGconn *conn, char *cmd, char *id, TXT_TO_INT("height", transfer_data(i_height), height); K_RLOCK(blocks_free); - b_item = find_prev_blocks(height+1); + b_item = find_prev_blocks(height+1, NULL); K_RUNLOCK(blocks_free); if (b_item) { DATA_BLOCKS(blocks, b_item); @@ -6571,6 +6571,488 @@ static char *cmd_btcset(__maybe_unused PGconn *conn, char *cmd, char *id, 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; + char ndiffbin[TXT_SML+1]; + 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(coinbase1height(workinfo->coinbase1), + 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); + hex2bin(ndiffbin, workinfo->bits, 4); + snprintf(tmp, sizeof(tmp), + "ndiff:%d=%.1f%c", rows, + diff_from_nbits(ndiffbin), + FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + snprintf(tmp, sizeof(tmp), + "ppsvalue:%d=%.15f%c", rows, + workinfo_pps(wi_item, + workinfo->workinfoid, + true), + 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 = coinbase1height(workinfo->coinbase1); + 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 ndiffbin[TXT_SML+1]; + 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 = coinbase1height(workinfo->coinbase1); + 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 = coinbase1height(workinfo->coinbase1); + 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 = coinbase1height(workinfo->coinbase1); + 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); + hex2bin(ndiffbin, workinfo->bits, 4); + snprintf(tmp, sizeof(tmp), + "ndiff0:%d=%.1f%c", rows, + diff_from_nbits(ndiffbin), + 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 = coinbase1height(workinfo->coinbase1); + 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); + hex2bin(ndiffbin, workinfo->bits, 4); + snprintf(tmp, sizeof(tmp), "ndiff:%d=%.1f%c", + rows, + diff_from_nbits(ndiffbin), + 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 { + 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; +} + /* The socket command format is as follows: * Basic structure: * cmd.ID.fld1=value1 FLDSEP fld2=value2 FLDSEP fld3=... @@ -6679,5 +7161,6 @@ struct CMDS ckdb_cmds[] = { { 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_END, NULL, false, false, NULL, SEQ_NONE, 0 } }; diff --git a/src/ckdb_data.c b/src/ckdb_data.c index dd32fb69..eb2572c5 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -2702,12 +2702,15 @@ K_ITEM *find_blocks(int32_t height, char *blockhash, K_TREE_CTX *ctx) } // Must be R or W locked before call -K_ITEM *find_prev_blocks(int32_t height) +K_ITEM *find_prev_blocks(int32_t height, K_TREE_CTX *ctx) { BLOCKS lookblocks, *blocks; - K_TREE_CTX ctx[1]; + K_TREE_CTX ctx0[1]; K_ITEM look, *b_item; + if (ctx == NULL) + ctx = ctx0; + /* TODO: For self orphaned (if that ever happens) * this will find based on blockhash order if it has two, * not NEW, blocks, which might not find the right one */