diff --git a/src/ckdb.c b/src/ckdb.c index e91844de..7a469159 100644 --- a/src/ckdb.c +++ b/src/ckdb.c @@ -805,7 +805,7 @@ static bool getdata3() goto sukamudai; if (!(ok = markersummary_fill(conn)) || everyone_die) goto sukamudai; - if (!confirm_sharesummary) + if (!confirm_sharesummary && !everyone_die) ok = poolstats_fill(conn); sukamudai: diff --git a/src/ckdb.h b/src/ckdb.h index 31f546f2..7409d93f 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -55,7 +55,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.105" +#define CKDB_VERSION DB_VERSION"-1.110" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ @@ -1974,6 +1974,8 @@ extern void sequence_report(bool lock); #define PPLNSDIFFTIMES "pplns_diff_times" #define PPLNSDIFFADD "pplns_diff_add" +#define REWARDOVERRIDE "MinerReward" + // Data free functions (first) extern void free_msgline_data(K_ITEM *item, bool t_lock, bool t_cull); extern void free_workinfo_data(K_ITEM *item); @@ -2371,6 +2373,8 @@ extern void payouts_add_ram(bool ok, K_ITEM *p_item, K_ITEM *old_p_item, extern bool payouts_add(PGconn *conn, bool add, K_ITEM *p_item, K_ITEM **old_p_item, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root, bool already); +extern K_ITEM *payouts_full_expire(PGconn *conn, int64_t payoutid, tv_t *now, + bool lock); extern bool payouts_fill(PGconn *conn); extern bool auths_add(PGconn *conn, char *poolinstance, char *username, char *workername, char *clientid, char *enonce1, diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index 9d83bd24..b3e48434 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -4520,34 +4520,27 @@ static char *cmd_payouts(PGconn *conn, char *cmd, char *id, tv_t *now, "%"PRId32"/%s", payoutid, old_payouts2->status, payouts2->status, payouts2->height, payouts2->blockhash); -/* } else if (strcasecmp(action, "expire") == 0) { - / TODO: Expire the payout - effectively deletes it + /* Expire the payout - effectively deletes it * Require payoutid - * If any payments are paid then don't allow it / + * 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); - K_WLOCK(payouts_free); - p_item = find_payoutid(payoutid); + p_item = payouts_full_expire(conn, payoutid, now, true); if (!p_item) { - K_WUNLOCK(payouts_free); - snprintf(reply, siz, - "no payout with id %"PRId64, payoutid); + snprintf(reply, siz, "failed payout %"PRId64, payoutid); return strdup(reply); } - p2_item = k_unlink_head(payouts_free); - K_WUNLOCK(payouts_free); - - DATA_PAYOUTS(payouts2, p2_item); - bzero(payouts2, sizeof(*payouts2)); - payouts2->payoutid = payouts->payoutid; - - ... -*/ + 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 diff --git a/src/ckdb_data.c b/src/ckdb_data.c index 6b097d6c..96af3b90 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -1563,6 +1563,28 @@ cmp_t cmp_optioncontrol(K_ITEM *a, K_ITEM *b) return c; } +#define reward_override_name(_height, _buf, _siz) \ + _reward_override_name(_height, _buf, _siz, WHERE_FFL_HERE) +static bool _reward_override_name(int32_t height, char *buf, size_t siz, + WHERE_FFL_ARGS) +{ + char tmp[128]; + size_t len; + + snprintf(tmp, sizeof(tmp), REWARDOVERRIDE"_%"PRId32, height); + + // Code bug - detect and notify truncation coz that would be bad :P + len = strlen(tmp) + 1; + if (len > siz) { + LOGEMERG("%s(): Invalid size %d passed - required %d" WHERE_FFL, + __func__, (int)siz, (int)len, WHERE_FFL_PASS); + return false; + } + + strcpy(buf, tmp); + return true; +} + // Must be R or W locked before call K_ITEM *find_optioncontrol(char *optionname, tv_t *now, int32_t height) { @@ -2996,7 +3018,7 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) K_TREE *mu_root = NULL; int usercount; double ndiff, total_diff, diff_want, elapsed; - char ndiffbin[TXT_SML+1]; + char ndiffbin[TXT_SML+1], rewardbuf[32]; double diff_times, diff_add; char cd_buf[CDATE_BUFSIZ]; tv_t end_tv = { 0L, 0L }; @@ -3365,6 +3387,39 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) d64 = blocks->reward * 9 / 1000; g64 = blocks->reward - d64; payouts->minerreward = g64; + + /* We can hard code a miner reward for a block in optioncontrol + * if it ever needs adjusting - so just expire the payout and + * re-process the reward ... before it's paid */ + bool oname; + oname = reward_override_name(blocks->height, rewardbuf, + sizeof(rewardbuf)); + if (oname) { + OPTIONCONTROL *oc; + K_ITEM *oc_item; + // optioncontrol must be default limits or below these limits + oc_item = find_optioncontrol(rewardbuf, &now, blocks->height+1); + if (oc_item) { + int64_t override, delta; + char *moar = "more"; + double per; + DATA_OPTIONCONTROL(oc, oc_item); + override = (int64_t)atol(oc->optionvalue); + delta = override - g64; + if (delta < 0) { + moar = "less"; + delta = -delta; + } + per = 100.0 * (double)delta / (double)g64; + LOGWARNING("%s(): *** block %"PRId32" payout reward" + " overridden, was %"PRId64" now %"PRId64 + " = %"PRId64" (%.4f%%) %s", + __func__, blocks->height, + g64, override, delta, per, moar); + payouts->minerreward = override; + } + } + payouts->workinfoidstart = begin_workinfoid; payouts->workinfoidend = end_workinfoid; payouts->elapsed = elapsed; diff --git a/src/ckdb_dbio.c b/src/ckdb_dbio.c index ca07374c..044218a5 100644 --- a/src/ckdb_dbio.c +++ b/src/ckdb_dbio.c @@ -2302,6 +2302,7 @@ nostart: DATA_OPTIONCONTROL(optioncontrol, old_item); optioncontrol_root = remove_from_ktree(optioncontrol_root, old_item, cmp_optioncontrol); + k_unlink_item(optioncontrol_store, old_item); FREENULL(optioncontrol->optionvalue); k_add_head(optioncontrol_free, old_item); } @@ -3605,44 +3606,6 @@ bool sharesummaries_to_markersummaries(PGconn *conn, WORKMARKERS *workmarkers, ms_item = ms_item->next; } -#if 0 - int deleted = -7; - char *tuples = NULL; - char *del; - - // No longer in the DB - if (old_sharesummary_store->count > 0) { - par = 0; - params[par++] = bigint_to_buf(workmarkers->workinfoidstart, NULL, 0); - params[par++] = bigint_to_buf(workmarkers->workinfoidend, NULL, 0); - PARCHK(par, params); - - del = "delete from sharesummary " - "where workinfoid >= $1 and workinfoid <= $2"; - - res = PQexecParams(conn, del, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); - rescode = PQresultStatus(res); - if (PGOK(rescode)) { - tuples = PQcmdTuples(res); - if (tuples && *tuples) - deleted = atoi(tuples); - } - PQclear(res); - if (!PGOK(rescode)) { - PGLOGERR("Delete", rescode, conn); - reason = "delete failure"; - goto rollback; - } - - if (deleted != old_sharesummary_store->count) { - LOGERR("%s() processed sharesummaries=%d but deleted=%d", - shortname, old_sharesummary_store->count, deleted); - reason = "delete mismatch"; - goto rollback; - } - } -#endif - ok = workmarkers_process(conn, true, true, workmarkers->markerid, workmarkers->poolinstance, @@ -5123,6 +5086,245 @@ unparam: return ok; } +/* Expire the entire payout, miningpayouts and payments + * If it returns false, nothing was changed + * and a console message will say why */ +K_ITEM *payouts_full_expire(PGconn *conn, int64_t payoutid, tv_t *now, bool lock) +{ + bool locked = false, conned = false, begun = false, ok = false; + K_TREE_CTX mp_ctx[1], pm_ctx[1]; + K_ITEM *po_item = NULL, *mp_item, *pm_item, *next_item; + PAYMENTS *payments = NULL; + MININGPAYOUTS *mp = NULL; + PAYOUTS *payouts = NULL; + ExecStatusType rescode; + PGresult *res; + char *params[8]; + int n, par = 0; + char *upd, *tuples = NULL; + int po_upd, mp_upd, pm_upd; + + // If not already done before calling + if (lock) + ck_wlock(&process_pplns_lock); + + // This will be rare so a full lock is best + K_WLOCK(payouts_free); + K_WLOCK(miningpayouts_free); + K_WLOCK(payments_free); + locked = true; + + po_item = find_payoutid(payoutid); + if (!po_item) { + LOGERR("%s(): unknown payoutid %"PRId64, __func__, payoutid); + goto matane; + } + + conned = CKPQConn(&conn); + + begun = CKPQBegin(conn); + if (!begun) + goto matane; + + upd = "update payouts set "EDDB"=$1 where payoutid=$2 and "EDDB"=$3"; + par = 0; + params[par++] = tv_to_buf(now, NULL, 0); + params[par++] = bigint_to_buf(payoutid, NULL, 0); + params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); + PARCHKVAL(par, 3, params); + + res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + if (PGOK(rescode)) { + tuples = PQcmdTuples(res); + if (tuples && *tuples) { + po_upd = atoi(tuples); + if (po_upd != 1) { + LOGERR("%s() updated payouts should be 1" + " but updated=%d", + __func__, po_upd); + goto matane; + } + } + } + PQclear(res); + if (!PGOK(rescode)) { + PGLOGERR("Update payouts", rescode, conn); + goto matane; + } + + for (n = 0; n < par; n++) + free(params[n]); + + upd = "update miningpayouts set "EDDB"=$1 where payoutid=$2 and "EDDB"=$3"; + par = 0; + params[par++] = tv_to_buf(now, NULL, 0); + params[par++] = bigint_to_buf(payoutid, NULL, 0); + params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); + PARCHKVAL(par, 3, params); + + res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + if (PGOK(rescode)) { + tuples = PQcmdTuples(res); + if (tuples && *tuples) + mp_upd = atoi(tuples); + } + PQclear(res); + if (!PGOK(rescode)) { + PGLOGERR("Update miningpayouts", rescode, conn); + goto matane; + } + + for (n = 0; n < par; n++) + free(params[n]); + + upd = "update payments set "EDDB"=$1 where payoutid=$2 and "EDDB"=$3"; + par = 0; + params[par++] = tv_to_buf(now, NULL, 0); + params[par++] = bigint_to_buf(payoutid, NULL, 0); + params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); + PARCHKVAL(par, 3, params); + + res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + if (PGOK(rescode)) { + tuples = PQcmdTuples(res); + if (tuples && *tuples) + pm_upd = atoi(tuples); + } + PQclear(res); + if (!PGOK(rescode)) { + PGLOGERR("Update payments", rescode, conn); + goto matane; + } + + for (n = 0; n < par; n++) + free(params[n]); + par = 0; + + // Check miningpayouts failure condition + mp_item = first_miningpayouts(payoutid, mp_ctx); + if (!mp_item) { + if (mp_upd != 0) { + LOGERR("%s() updated miningpayouts should be 0 but" + " updated=%d", + __func__, mp_upd); + goto matane; + } + } else { + int count = 0; + DATA_MININGPAYOUTS(mp, mp_item); + while (mp_item && mp->payoutid == payoutid) { + if (CURRENT(&(mp->expirydate))) + count++; + mp_item = next_in_ktree(mp_ctx); + DATA_MININGPAYOUTS_NULL(mp, mp_item); + } + if (count != mp_upd) { + LOGERR("%s() updated miningpayouts should be %d but" + " updated=%d", + __func__, count, mp_upd); + goto matane; + } + } + + /* Check payments failure condition + * + * This does a full table search since there is no index + * This should be so rare that adding an index/tree for it + * would be a waste */ + pm_item = first_in_ktree(payments_root, pm_ctx); + if (!pm_item) { + if (pm_upd != 0) { + LOGERR("%s() updated payments should be 0 but" + " updated=%d", + __func__, pm_upd); + goto matane; + } + } else { + int count = 0; + DATA_PAYMENTS(payments, pm_item); + while (pm_item) { + if (payments->payoutid == payoutid && + CURRENT(&(payments->expirydate))) { + count++; + } + pm_item = next_in_ktree(pm_ctx); + DATA_PAYMENTS_NULL(payments, pm_item); + } + if (count != pm_upd) { + LOGERR("%s() updated payments should be %d but" + " updated=%d", + __func__, count, pm_upd); + goto matane; + } + } + + // No more possible errors, so update the ram tables + DATA_PAYOUTS(payouts, po_item); + payouts_root = remove_from_ktree(payouts_root, po_item, cmp_payouts); + payouts_id_root = remove_from_ktree(payouts_id_root, po_item, cmp_payouts_id); + copy_tv(&(payouts->expirydate), now); + payouts_root = add_to_ktree(payouts_root, po_item, cmp_payouts); + payouts_id_root = add_to_ktree(payouts_id_root, po_item, cmp_payouts_id); + + mp_item = first_miningpayouts(payoutid, mp_ctx); + DATA_MININGPAYOUTS_NULL(mp, mp_item); + while (mp_item && mp->payoutid == payoutid) { + if (CURRENT(&(mp->expirydate))) { + next_item = next_in_ktree(mp_ctx); + miningpayouts_root = remove_from_ktree(miningpayouts_root, mp_item, cmp_miningpayouts); + copy_tv(&(mp->expirydate), now); + miningpayouts_root = add_to_ktree(miningpayouts_root, mp_item, cmp_miningpayouts); + mp_item = next_item; + } else + mp_item = next_in_ktree(mp_ctx); + + DATA_MININGPAYOUTS_NULL(mp, mp_item); + } + + pm_item = first_in_ktree(payments_root, pm_ctx); + DATA_PAYMENTS_NULL(payments, pm_item); + while (pm_item) { + if (payments->payoutid == payoutid && + CURRENT(&(payments->expirydate))) { + next_item = next_in_ktree(pm_ctx); + payments_root = remove_from_ktree(payments_root, pm_item, cmp_payments); + copy_tv(&(payments->expirydate), now); + payments_root = add_to_ktree(payments_root, pm_item, cmp_payments); + pm_item = next_item; + } else + pm_item = next_in_ktree(pm_ctx); + + DATA_PAYMENTS_NULL(payments, pm_item); + } + + ok = true; +matane: + if (begun) + CKPQEnd(conn, ok); + + if (locked) { + K_WUNLOCK(payments_free); + K_WUNLOCK(miningpayouts_free); + K_WUNLOCK(payouts_free); + } + + CKPQDisco(&conn, conned); + + if (lock) + ck_wunlock(&process_pplns_lock); + + for (n = 0; n < par; n++) + free(params[n]); + + if (ok) + return po_item; + else + return NULL; +} + bool payouts_fill(PGconn *conn) { ExecStatusType rescode;