diff --git a/pool/base.php b/pool/base.php
index 828454a3..3e4c7c02 100644
--- a/pool/base.php
+++ b/pool/base.php
@@ -126,9 +126,12 @@ function btcfmt($amt)
return number_format($amt, 8);
}
#
-function utcd($when)
+function utcd($when, $brief = false)
{
- return gmdate('Y-m-d H:i:s+00', round($when));
+ if ($brief)
+ return gmdate('M-d H:i:s', round($when));
+ else
+ return gmdate('Y-m-d H:i:s+00', round($when));
}
#
global $sipre;
diff --git a/pool/page_shifts.php b/pool/page_shifts.php
index 1be0d587..63ae6087 100644
--- a/pool/page_shifts.php
+++ b/pool/page_shifts.php
@@ -4,10 +4,11 @@ function doshifts($data, $user)
{
$ans = getShifts($user);
- $pg = "
\n";
+ $pg = "Click here to jump to the start of the last payout
";
+ $pg .= "\n";
$pg .= "";
$pg .= "Shift | ";
- $pg .= "Start | ";
+ $pg .= "Start UTC | ";
$pg .= "Length | ";
$pg .= "Your Diff | ";
$pg .= "Inv Diff | ";
@@ -15,6 +16,8 @@ function doshifts($data, $user)
$pg .= "Shares | ";
$pg .= "Avg Share | ";
$pg .= "Rewards | ";
+ $pg .= "PPS* | ";
+ $pg .= "PPS% | ";
$pg .= "
\n";
if (($ans['STATUS'] != 'ok') || !isset($ans['prefix_all']))
@@ -28,9 +31,13 @@ function doshifts($data, $user)
for ($i = 0; $i < $count; $i++)
{
$u = '';
+ $mark = '';
if (isset($ans['lastpayoutstart:'.$i])
&& $ans['lastpayoutstart:'.$i] != '')
+ {
$u = 'u';
+ $mark = '';
+ }
if (($i % 2) == 0)
$row = "even$u";
else
@@ -52,9 +59,9 @@ function doshifts($data, $user)
$btc = ' ';
else
$btc = '';
- $pg .= "$shif$btc | ";
+ $pg .= "$shif$btc$mark | ";
$start = $ans['start:'.$i];
- $pg .= ''.utcd($start).' | ';
+ $pg .= ''.utcd($start, true).' | ';
$nd = $ans['end:'.$i];
$elapsed = $nd - $start;
$pg .= ''.howmanyhrs($elapsed).' | ';
@@ -72,10 +79,23 @@ function doshifts($data, $user)
$avgsh = 0;
$pg .= ''.number_format($avgsh, 2).' | ';
$pg .= ''.$ans['rewards:'.$i].' | ';
+ $ppsr = (float)$ans['ppsrewarded:'.$i];
+ if ($ppsr > 0)
+ $ppsd = sprintf('%.3e', $ppsr);
+ else
+ $ppsd = '0';
+ $pg .= "$ppsd | ";
+ $ppsv = (float)$ans['ppsvalue:'.$i];
+ if ($ppsv > 0)
+ $pgot = number_format(100.0 * $ppsr / $ppsv, 2).'%';
+ else
+ $pgot = '?';
+ $pg .= "$pgot | ";
$pg .= "\n";
}
}
$pg .= "
\n";
+ $pg .= "* The PPS value unit is satoshis
";
return $pg;
}
diff --git a/src/ckdb.h b/src/ckdb.h
index cefe3dbe..4bb19b5b 100644
--- a/src/ckdb.h
+++ b/src/ckdb.h
@@ -55,7 +55,7 @@
#define DB_VLOCK "1"
#define DB_VERSION "1.0.2"
-#define CKDB_VERSION DB_VERSION"-1.300"
+#define CKDB_VERSION DB_VERSION"-1.301"
#define WHERE_FFL " - from %s %s() line %d"
#define WHERE_FFL_HERE __FILE__, __func__, __LINE__
@@ -1294,6 +1294,7 @@ typedef struct optioncontrol {
// Value it must default to (to work properly)
#define OPTIONCONTROL_HEIGHT 1
+#define MAX_HEIGHT 999999999
// Test it here rather than obscuring the #define elsewhere
#if ((OPTIONCONTROL_HEIGHT+1) != START_POOL_HEIGHT)
@@ -1346,6 +1347,13 @@ extern tv_t last_bc;
// current network diff
extern double current_ndiff;
+// Offset in binary coinbase1 of the block number
+#define BLOCKNUM_OFFSET 42
+// Initial block reward (satoshi)
+#define REWARD_BASE 5000000000.0
+// How many blocks per halving
+#define REWARD_HALVE 210000.0
+
// SHARES shares.id.json={...}
typedef struct shares {
int64_t workinfoid;
@@ -2090,6 +2098,8 @@ extern void sequence_report(bool lock);
#define REWARDOVERRIDE "MinerReward"
+#define PPSOVERRIDE "PPSValue"
+
// Data free functions (first)
#define FREE_ITEM(item) do { } while(0)
// TODO: make a macro for all other to use above macro
@@ -2264,7 +2274,7 @@ extern K_ITEM *find_first_paypayid(int64_t userid, int64_t payoutid, K_TREE_CTX
extern cmp_t cmp_accountbalance(K_ITEM *a, K_ITEM *b);
extern K_ITEM *find_accountbalance(int64_t userid);
extern cmp_t cmp_optioncontrol(K_ITEM *a, K_ITEM *b);
-extern K_ITEM *find_optioncontrol(char *optionname, tv_t *now, int32_t height);
+extern K_ITEM *find_optioncontrol(char *optionname, const tv_t *now, int32_t height);
extern int64_t user_sys_setting(int64_t userid, char *setting_name,
int64_t setting_default, tv_t *now);
extern cmp_t cmp_workinfo(K_ITEM *a, K_ITEM *b);
@@ -2279,6 +2289,7 @@ extern bool workinfo_age(int64_t workinfoid, char *poolinstance, char *by,
char *code, char *inet, tv_t *cd, tv_t *ss_first,
tv_t *ss_last, int64_t *ss_count, int64_t *s_count,
int64_t *s_diff);
+extern double workinfo_pps(K_ITEM *w_item, int64_t workinfoid, bool lock);
extern cmp_t cmp_shares(K_ITEM *a, K_ITEM *b);
extern cmp_t cmp_shareerrors(K_ITEM *a, K_ITEM *b);
extern void dsp_sharesummary(K_ITEM *item, FILE *stream);
diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c
index 0e1c23e7..935adca5 100644
--- a/src/ckdb_cmd.c
+++ b/src/ckdb_cmd.c
@@ -5383,6 +5383,16 @@ static char *cmd_shifts(__maybe_unused PGconn *conn, char *cmd, char *id,
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 ==
@@ -5431,7 +5441,8 @@ static char *cmd_shifts(__maybe_unused PGconn *conn, char *cmd, char *id,
snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c",
rows, FLDSEP,
"markerid,shift,start,end,rewards,"
- "lastpayoutstart", FLDSEP);
+ "ppsvalue,ppsrewarded,lastpayoutstart",
+ FLDSEP);
APPEND_REALLOC(buf, off, len, tmp);
snprintf(tmp, sizeof(tmp), "arn=%s", "Shifts");
diff --git a/src/ckdb_data.c b/src/ckdb_data.c
index a7cc7eb3..720f3a8e 100644
--- a/src/ckdb_data.c
+++ b/src/ckdb_data.c
@@ -1822,7 +1822,7 @@ static bool _reward_override_name(int32_t height, char *buf, size_t siz,
}
// Must be R or W locked before call
-K_ITEM *find_optioncontrol(char *optionname, tv_t *now, int32_t height)
+K_ITEM *find_optioncontrol(char *optionname, const tv_t *now, int32_t height)
{
OPTIONCONTROL optioncontrol, *oc, *ocbest;
K_TREE_CTX ctx[1];
@@ -1832,6 +1832,9 @@ K_ITEM *find_optioncontrol(char *optionname, tv_t *now, int32_t height)
* 1) activationdate is <= now
* and
* 2) height <= specified height (pool.height = current)
+ * The logic being: if 'now' is after the record activation date
+ * and 'height' is after the record activation height then
+ * the record is active
* Remember the active record with the newest activationdate
* If two records have the same activation date, then
* remember the active record with the highest height
@@ -2197,6 +2200,61 @@ bye:
return ok;
}
+
+// The PPS value of a 1diff share for the given workinfoid
+double workinfo_pps(K_ITEM *w_item, int64_t workinfoid, bool lock)
+{
+ OPTIONCONTROL *optioncontrol;
+ K_ITEM *oc_item;
+ char oc_name[TXT_SML+1];
+ char coinbase1bin[TXT_SML+1];
+ char ndiffbin[TXT_SML+1];
+ WORKINFO *workinfo;
+ double w_diff;
+ int w_blocknum;
+ size_t len;
+
+ // Allow optioncontrol override for a given workinfoid
+ snprintf(oc_name, sizeof(oc_name), PPSOVERRIDE"_%"PRId64, workinfoid);
+ if (lock)
+ K_RLOCK(optioncontrol_free);
+ // No time/height control is used, just find the latest record
+ oc_item = find_optioncontrol(oc_name, &date_eot, MAX_HEIGHT);
+ if (lock)
+ K_RUNLOCK(optioncontrol_free);
+
+ // Value is a floating point double of satoshi
+ if (oc_item) {
+ DATA_OPTIONCONTROL(optioncontrol, oc_item);
+ return atof(optioncontrol->optionvalue);
+ }
+
+ if (!w_item) {
+ LOGERR("%s(): missing workinfo %"PRId64,
+ __func__, workinfoid);
+ return 0.0;
+ }
+
+ DATA_WORKINFO(workinfo, w_item);
+ len = strlen(workinfo->coinbase1);
+ if (len < (BLOCKNUM_OFFSET * 2 + 4) || (len & 1)) {
+ LOGERR("%s(): Invalid coinbase1 len %d - "
+ "should be >= %d and even - for wid %"PRId64,
+ __func__, (int)len, (BLOCKNUM_OFFSET * 2 + 4),
+ workinfoid);
+ return 0.0;
+ }
+
+ hex2bin(ndiffbin, workinfo->bits, 4);
+ w_diff = diff_from_nbits(ndiffbin);
+ hex2bin(coinbase1bin, workinfo->coinbase1 + (BLOCKNUM_OFFSET * 2),
+ (len - (BLOCKNUM_OFFSET * 2)) >> 1);
+ w_blocknum = get_sernumber((uchar *)coinbase1bin);
+
+ // BASE halving to determine coinbase reward then divided by difficulty
+ return(REWARD_BASE * pow(0.5, floor((double)w_blocknum / REWARD_HALVE)) / w_diff);
+}
+
// order by workinfoid asc,userid asc,workername asc,createdate asc,nonce asc,expirydate desc
cmp_t cmp_shares(K_ITEM *a, K_ITEM *b)
{
@@ -4767,6 +4825,10 @@ bool reward_shifts(PAYOUTS *payouts, bool lock, int delta)
K_ITEM *wm_item;
WORKMARKERS *wm;
bool did_one = false;
+ double payout_pps;
+
+ payout_pps = (double)delta * (double)(payouts->minerreward) /
+ payouts->diffused;
if (lock)
K_WLOCK(workmarkers_free);
@@ -4781,6 +4843,7 @@ bool reward_shifts(PAYOUTS *payouts, bool lock, int delta)
* onto the PROCESSED status if it isn't already processed */
if (CURRENT(&(wm->expirydate))) {
wm->rewards += delta;
+ wm->rewarded += payout_pps;
did_one = true;
}
wm_item = next_in_ktree(ctx);
@@ -4792,7 +4855,13 @@ bool reward_shifts(PAYOUTS *payouts, bool lock, int delta)
return did_one;
}
-// (re)calculate rewards for a shift
+/* (re)calculate rewards for a shift
+ * N.B. we don't need to zero/undo a workmarkers rewards directly
+ * since this is just a counter of how many times it's been rewarded
+ * and thus if the shift is expired the counter is ignored
+ * We only need to (re)calculate it when the workmarker is created
+ * Payouts code processing will increment/decrement all current rewards as
+ * needed with reward_shifts() when payouts are added/changed/removed */
bool shift_rewards(K_ITEM *wm_item)
{
PAYOUTS *payouts = NULL;
diff --git a/src/ckdb_dbio.c b/src/ckdb_dbio.c
index 881f9b3f..db2ed64f 100644
--- a/src/ckdb_dbio.c
+++ b/src/ckdb_dbio.c
@@ -6628,12 +6628,13 @@ bool _workmarkers_process(PGconn *conn, bool already, bool add,
WHERE_FFL_PASS);
goto rollback;
}
- w_item = find_workinfo(workinfoidend, NULL);
+ w_item = find_workinfo(workinfoidstart, NULL);
if (!w_item)
goto rollback;
- w_item = find_workinfo(workinfoidstart, NULL);
+ w_item = find_workinfo(workinfoidend, NULL);
if (!w_item)
goto rollback;
+
K_WLOCK(workmarkers_free);
wm_item = k_unlink_head(workmarkers_free);
K_WUNLOCK(workmarkers_free);
@@ -6698,6 +6699,7 @@ bool _workmarkers_process(PGconn *conn, bool already, bool add,
PGLOGERR("Insert", rescode, conn);
goto rollback;
}
+ row->pps_value = workinfo_pps(w_item, workinfoidend, true);
}
ok = true;
@@ -6837,9 +6839,18 @@ bool workmarkers_fill(PGconn *conn)
add_to_ktree(workmarkers_workinfoid_root, item);
k_add_head(workmarkers_store, item);
+ wi_item = find_workinfo(row->workinfoidend, NULL);
+ if (!wi_item) {
+ LOGERR("%s(): ERROR workmarkerid %"PRId64
+ " wid end %"PRId64" doesn't exist! "
+ "PPS value will be zero",
+ __func__, row->markerid,
+ row->workinfoidend);
+ }
+ row->pps_value = workinfo_pps(wi_item, row->workinfoidend, false);
+
if (dbstatus.newest_workmarker_workinfoid < row->workinfoidend) {
dbstatus.newest_workmarker_workinfoid = row->workinfoidend;
- wi_item = find_workinfo(row->workinfoidend, NULL);
if (!wi_item) {
LOGEMERG("%s(): FAILURE workmarkerid %"PRId64
" wid end %"PRId64" doesn't exist! "