From a0753a3965e03de3f662e12e10bb9ad05947fa35 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Sun, 8 Feb 2015 10:12:04 +1100 Subject: [PATCH 01/39] Handle other forms of read_socket_line ending after message complete as not a failure --- src/ckpool.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ckpool.c b/src/ckpool.c index e90d0bb5..55f9185d 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -419,9 +419,9 @@ int read_socket_line(connsock_t *cs, const int timeout) char *newbuf; ret = wait_read_select(fd, eom ? 0 : timeout); - if (eom && !ret) - break; if (ret < 1) { + if (eom) + break; if (!ret) LOGDEBUG("Select timed out in read_socket_line"); else @@ -431,7 +431,7 @@ int read_socket_line(connsock_t *cs, const int timeout) ret = recv(fd, readbuf, PAGESIZE - 4, 0); if (ret < 1) { /* Closed socket after valid message */ - if (!ret && eom) + if (eom) break; LOGERR("Failed to recv in read_socket_line"); ret = -1; From 08ef8ef3debe889d6d5423ddf0b7f97d0229c786 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Sat, 14 Feb 2015 11:25:33 +1100 Subject: [PATCH 02/39] Differentiate pong from other spurious messages from clients --- src/stratifier.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/stratifier.c b/src/stratifier.c index a62530e7..57d73a0a 100644 --- a/src/stratifier.c +++ b/src/stratifier.c @@ -3414,7 +3414,11 @@ static void parse_instance_msg(sdata_t *sdata, smsg_t *msg, stratum_instance_t * if (res_val) { const char *result = json_string_value(res_val); - LOGDEBUG("Received spurious response %s", result ? result : ""); + if (!safecmp(result, "pong")) + LOGDEBUG("Received pong from client %"PRId64, client_id); + else + LOGDEBUG("Received spurious response %s from client %"PRId64, + result ? result : "", client_id); goto out; } send_json_err(sdata, client_id, id_val, "-3:method not found"); From 2b97f1833faabdf56b6a4f79cc1aad68f4e16751 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Fri, 20 Feb 2015 15:57:55 +1100 Subject: [PATCH 03/39] Shutdown instead of closing a socket after sending a unix message allowing the receiving end to close the socket after receiving the data --- src/ckpool.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ckpool.c b/src/ckpool.c index 55f9185d..0d176bac 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -525,7 +525,7 @@ bool _send_proc(proc_instance_t *pi, const char *msg, const char *file, const ch LOGWARNING("Failed to send %s to socket %s", msg, path); else ret = true; - Close(sockd); + shutdown(sockd, SHUT_WR); out: if (unlikely(!ret)) { LOGERR("Failure in send_proc from %s %s:%d", file, func, line); From 316ceba75bbc17df77cfdb471de23dc5aecbdd04 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Fri, 20 Feb 2015 17:11:38 +1100 Subject: [PATCH 04/39] Check for pid in send_recv_proc as well --- src/ckpool.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ckpool.c b/src/ckpool.c index 0d176bac..d6091ff8 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -549,6 +549,8 @@ char *_send_recv_proc(proc_instance_t *pi, const char *msg, const char *file, co LOGERR("Attempted to send null message to socket %s in send_proc", path); goto out; } + if (unlikely(!pi->pid)) + pi->pid = get_proc_pid(pi); if (unlikely(kill_pid(pi->pid, 0))) { LOGALERT("Attempting to send message %s to dead process %s", msg, pi->processname); goto out; From 90c682177f67e86e19c477c1831c9d03f5edbfd8 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Fri, 20 Feb 2015 17:49:56 +1100 Subject: [PATCH 05/39] Wait for the other end to close a unix socket to ensure the message has gone through --- src/ckpool.c | 2 ++ src/libckpool.c | 14 ++++++++++++++ src/libckpool.h | 1 + 3 files changed, 17 insertions(+) diff --git a/src/ckpool.c b/src/ckpool.c index d6091ff8..004a09a3 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -526,6 +526,8 @@ bool _send_proc(proc_instance_t *pi, const char *msg, const char *file, const ch else ret = true; shutdown(sockd, SHUT_WR); + if (!wait_close(sockd, 5)) + LOGWARNING("send_proc did not close from %s %s:%d", file, func, line); out: if (unlikely(!ret)) { LOGERR("Failure in send_proc from %s %s:%d", file, func, line); diff --git a/src/libckpool.c b/src/libckpool.c index 19fe19ed..cae50bda 100644 --- a/src/libckpool.c +++ b/src/libckpool.c @@ -910,6 +910,20 @@ out: return sockd; } +/* Wait till a socket has been closed at the other end */ +int wait_close(int sockd, int timeout) +{ + struct pollfd sfd; + + if (unlikely(sockd < 0)) + return -1; + sfd.fd = sockd; + sfd.events = POLLHUP; + sfd.revents = 0; + timeout *= 1000; + return poll(&sfd, 1, timeout); +} + /* Emulate a select read wait for high fds that select doesn't support */ int wait_read_select(int sockd, int timeout) { diff --git a/src/libckpool.h b/src/libckpool.h index e21acc96..79f0f8fd 100644 --- a/src/libckpool.h +++ b/src/libckpool.h @@ -493,6 +493,7 @@ int _open_unix_server(const char *server_path, const char *file, const char *fun #define open_unix_server(server_path) _open_unix_server(server_path, __FILE__, __func__, __LINE__) int _open_unix_client(const char *server_path, const char *file, const char *func, const int line); #define open_unix_client(server_path) _open_unix_client(server_path, __FILE__, __func__, __LINE__) +int wait_close(int sockd, int timeout); int wait_read_select(int sockd, int timeout); int read_length(int sockd, void *buf, int len); char *_recv_unix_msg(int sockd, int timeout1, int timeout2, const char *file, const char *func, const int line); From 236634239e9b680b01f3be88b87c55131396c510 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Fri, 20 Feb 2015 20:04:03 +1100 Subject: [PATCH 06/39] Close our end of the socket in send_proc --- src/ckpool.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ckpool.c b/src/ckpool.c index 004a09a3..3b433d44 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -525,9 +525,9 @@ bool _send_proc(proc_instance_t *pi, const char *msg, const char *file, const ch LOGWARNING("Failed to send %s to socket %s", msg, path); else ret = true; - shutdown(sockd, SHUT_WR); if (!wait_close(sockd, 5)) LOGWARNING("send_proc did not close from %s %s:%d", file, func, line); + Close(sockd); out: if (unlikely(!ret)) { LOGERR("Failure in send_proc from %s %s:%d", file, func, line); From 07874e9f3027cf6723baa594d70892fdf91a607c Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Fri, 20 Feb 2015 23:59:42 +1100 Subject: [PATCH 07/39] Show message associated with no close fd detection --- src/ckpool.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ckpool.c b/src/ckpool.c index 3b433d44..70d33adb 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -526,7 +526,7 @@ bool _send_proc(proc_instance_t *pi, const char *msg, const char *file, const ch else ret = true; if (!wait_close(sockd, 5)) - LOGWARNING("send_proc did not close from %s %s:%d", file, func, line); + LOGWARNING("send_proc %s did not detect close from %s %s:%d", msg, file, func, line); Close(sockd); out: if (unlikely(!ret)) { From 85b17f1b78563baa3705b5e0b152e30bd6c6ff7f Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Sat, 21 Feb 2015 00:34:48 +1100 Subject: [PATCH 08/39] Check for correct condition in wait_close --- src/libckpool.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libckpool.c b/src/libckpool.c index cae50bda..7fc3017b 100644 --- a/src/libckpool.c +++ b/src/libckpool.c @@ -914,14 +914,18 @@ out: int wait_close(int sockd, int timeout) { struct pollfd sfd; + int ret; if (unlikely(sockd < 0)) return -1; sfd.fd = sockd; - sfd.events = POLLHUP; + sfd.events = POLLIN; sfd.revents = 0; timeout *= 1000; - return poll(&sfd, 1, timeout); + ret = poll(&sfd, 1, timeout); + if (ret < 1) + return 0; + return sfd.revents & POLLHUP; } /* Emulate a select read wait for high fds that select doesn't support */ From a23060d786600ee337aad92b1ca9089a064096a2 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Sat, 21 Feb 2015 01:08:26 +1100 Subject: [PATCH 09/39] Fix buf dereference error --- src/stratifier.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stratifier.c b/src/stratifier.c index 57d73a0a..9bf2ab98 100644 --- a/src/stratifier.c +++ b/src/stratifier.c @@ -4342,8 +4342,8 @@ int stratifier(proc_instance_t *pi) ckpool_t *ckp = pi->ckp; int ret = 1, threads; int64_t randomiser; + char *buf = NULL; sdata_t *sdata; - char *buf; LOGWARNING("%s stratifier starting", ckp->name); sdata = ckzalloc(sizeof(sdata_t)); From 487e918ff7eaafaeade6b57d7a9e6d26e882946c Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Sat, 21 Feb 2015 01:16:05 +1100 Subject: [PATCH 10/39] Return value of send_proc is never used --- src/ckpool.c | 3 +-- src/ckpool.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ckpool.c b/src/ckpool.c index 70d33adb..4b753074 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -494,7 +494,7 @@ out: /* Send a single message to a process instance when there will be no response, * closing the socket immediately. */ -bool _send_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line) +void _send_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line) { char *path = pi->us.path; bool ret = false; @@ -533,7 +533,6 @@ out: LOGERR("Failure in send_proc from %s %s:%d", file, func, line); childsighandler(15); } - return ret; } /* Send a single message to a process instance and retrieve the response, then diff --git a/src/ckpool.h b/src/ckpool.h index 797beb24..771ac692 100644 --- a/src/ckpool.h +++ b/src/ckpool.h @@ -212,7 +212,7 @@ ckpool_t *global_ckp; bool ping_main(ckpool_t *ckp); void empty_buffer(connsock_t *cs); int read_socket_line(connsock_t *cs, const int timeout); -bool _send_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line); +void _send_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line); #define send_proc(pi, msg) _send_proc(pi, msg, __FILE__, __func__, __LINE__) char *_send_recv_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line); #define send_recv_proc(pi, msg) _send_recv_proc(pi, msg, __FILE__, __func__, __LINE__) From bd68f928b710816c984ec3a8dbc852f10ed3f6b4 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Sat, 21 Feb 2015 01:49:24 +1100 Subject: [PATCH 11/39] Make all one way send_procs asynchronous to avoid message response deadlocks --- src/ckpool.c | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/ckpool.c b/src/ckpool.c index 4b753074..989b1b65 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -492,14 +492,32 @@ out: return pid; } -/* Send a single message to a process instance when there will be no response, - * closing the socket immediately. */ -void _send_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line) +struct proc_message { + proc_instance_t *pi; + char *msg; + const char *file; + const char *func; + int line; +}; + +/* Send all one way messages asynchronously so we can wait till the receiving + * end closes the socket to ensure all messages are received but no deadlocks + * can occur with 2 processes waiting for each other's socket closure. */ +void *async_send_proc(void *arg) { + struct proc_message *pm = (struct proc_message *)arg; + proc_instance_t *pi = pm->pi; + char *msg = pm->msg; + const char *file = pm->file; + const char *func = pm->func; + int line = pm->line; + char *path = pi->us.path; bool ret = false; int sockd; + pthread_detach(pthread_self()); + if (unlikely(!path || !strlen(path))) { LOGERR("Attempted to send message %s to null path in send_proc", msg ? msg : ""); goto out; @@ -533,6 +551,24 @@ out: LOGERR("Failure in send_proc from %s %s:%d", file, func, line); childsighandler(15); } + free(msg); + free(pm); + return NULL; +} + +/* Send a single message to a process instance when there will be no response, + * closing the socket immediately. */ +void _send_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line) +{ + struct proc_message *pm = ckalloc(sizeof(struct proc_message)); + pthread_t pth; + + pm->pi = pi; + pm->msg = strdup(msg); + pm->file = file; + pm->func = func; + pm->line = line; + create_pthread(&pth, async_send_proc, pm); } /* Send a single message to a process instance and retrieve the response, then From 33508b2243818bea6c96037800ced4a59da3d0c5 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Sat, 21 Feb 2015 09:47:37 +1100 Subject: [PATCH 12/39] Reset the pi pid after a failure to find the process alive so we can look it up again in case it has changed --- src/ckpool.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ckpool.c b/src/ckpool.c index 989b1b65..b4b280f4 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -589,6 +589,9 @@ char *_send_recv_proc(proc_instance_t *pi, const char *msg, const char *file, co if (unlikely(!pi->pid)) pi->pid = get_proc_pid(pi); if (unlikely(kill_pid(pi->pid, 0))) { + /* Reset the pid value in case we are still looking for an old + * process */ + pi->pid = 0; LOGALERT("Attempting to send message %s to dead process %s", msg, pi->processname); goto out; } From 646d4a95602ead1e6cad397e3ef18c62f11d8e29 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Sat, 21 Feb 2015 17:50:50 +1100 Subject: [PATCH 13/39] Cope with unknown pids in various send msg commands without terminal failure --- src/ckpool.c | 60 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/src/ckpool.c b/src/ckpool.c index b4b280f4..56d1c385 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -237,7 +237,25 @@ static int pid_wait(const pid_t pid, const int ms) return ret; } -static int send_procmsg(const proc_instance_t *pi, const char *buf) +static int get_proc_pid(const proc_instance_t *pi) +{ + int ret, pid = 0; + char path[256]; + FILE *fp; + + sprintf(path, "%s%s.pid", pi->ckp->socket_dir, pi->processname); + fp = fopen(path, "re"); + if (!fp) + goto out; + ret = fscanf(fp, "%d", &pid); + if (ret < 1) + pid = 0; + fclose(fp); +out: + return pid; +} + +static int send_procmsg(proc_instance_t *pi, const char *buf) { char *path = pi->us.path; int ret = -1; @@ -251,6 +269,12 @@ static int send_procmsg(const proc_instance_t *pi, const char *buf) LOGERR("Attempted to send null message to socket %s in send_proc", path); goto out; } + if (unlikely(!pi->pid)) { + pi->pid = get_proc_pid(pi); + if (!pi->pid) + goto out; + + } if (unlikely(kill_pid(pi->pid, 0))) { LOGALERT("Attempting to send message %s to dead process %s", buf, pi->processname); goto out; @@ -474,24 +498,6 @@ out: static void childsighandler(const int sig); -static int get_proc_pid(const proc_instance_t *pi) -{ - int ret, pid = 0; - char path[256]; - FILE *fp; - - sprintf(path, "%s%s.pid", pi->ckp->socket_dir, pi->processname); - fp = fopen(path, "re"); - if (!fp) - goto out; - ret = fscanf(fp, "%d", &pid); - if (ret < 1) - pid = 0; - fclose(fp); -out: - return pid; -} - struct proc_message { proc_instance_t *pi; char *msg; @@ -528,10 +534,16 @@ void *async_send_proc(void *arg) } /* At startup the pid fields are not set up before some processes are * forked so they never inherit them. */ - if (unlikely(!pi->pid)) + if (unlikely(!pi->pid)) { pi->pid = get_proc_pid(pi); + if (!pi->pid) { + LOGALERT("Attempting to send message %s to non existent process %s", msg, pi->processname); + goto out_nofail; + } + } if (unlikely(kill_pid(pi->pid, 0))) { - LOGALERT("Attempting to send message %s to non existent process %s", msg, pi->processname); + LOGALERT("Attempting to send message %s to non existent process %s pid %d", + msg, pi->processname, pi->pid); goto out; } sockd = open_unix_client(path); @@ -551,6 +563,7 @@ out: LOGERR("Failure in send_proc from %s %s:%d", file, func, line); childsighandler(15); } +out_nofail: free(msg); free(pm); return NULL; @@ -586,8 +599,11 @@ char *_send_recv_proc(proc_instance_t *pi, const char *msg, const char *file, co LOGERR("Attempted to send null message to socket %s in send_proc", path); goto out; } - if (unlikely(!pi->pid)) + if (unlikely(!pi->pid)) { pi->pid = get_proc_pid(pi); + if (!pi->pid) + goto out; + } if (unlikely(kill_pid(pi->pid, 0))) { /* Reset the pid value in case we are still looking for an old * process */ From d15ccdf54d713da45e734833b448e7c60b6ebeea Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Sat, 21 Feb 2015 17:59:59 +1100 Subject: [PATCH 14/39] Remove the old pid file per process when preparing the new child processes --- src/ckpool.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ckpool.c b/src/ckpool.c index 56d1c385..a1c4a514 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -1260,6 +1260,8 @@ static proc_instance_t *prepare_child(ckpool_t *ckp, int (*process)(), char *nam pi->process = process; create_process_unixsock(pi); manage_old_child(ckp, pi); + /* Remove the old pid file if we've succeeded in coming this far */ + rm_namepid(pi); return pi; } From d594f86520731384278ba32f3190bef6ba64ea9f Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Mon, 23 Feb 2015 09:54:39 +1100 Subject: [PATCH 15/39] Create generic workqueue function and message receiving and parsing helpers --- src/ckpool.c | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/ckpool.h | 29 ++++++++++++++++++--- 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/src/ckpool.c b/src/ckpool.c index a1c4a514..6b8fa480 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -134,6 +134,39 @@ static void *ckmsg_queue(void *arg) return NULL; } +/* Generic workqueue function and message receiving and parsing thread */ +static void *ckwq_queue(void *arg) +{ + ckwq_t *ckmsgq = (ckwq_t *)arg; + ckpool_t *ckp = ckmsgq->ckp; + + pthread_detach(pthread_self()); + rename_proc(ckmsgq->name); + + while (42) { + ckwqmsg_t *wqmsg; + tv_t now; + ts_t abs; + + mutex_lock(ckmsgq->lock); + tv_time(&now); + tv_to_ts(&abs, &now); + abs.tv_sec++; + if (!ckmsgq->wqmsgs) + cond_timedwait(ckmsgq->cond, ckmsgq->lock, &abs); + wqmsg = ckmsgq->wqmsgs; + if (wqmsg) + DL_DELETE(ckmsgq->wqmsgs, wqmsg); + mutex_unlock(ckmsgq->lock); + + if (!wqmsg) + continue; + wqmsg->func(ckp, wqmsg->data); + free(wqmsg); + } + return NULL; +} + ckmsgq_t *create_ckmsgq(ckpool_t *ckp, const char *name, const void *func) { ckmsgq_t *ckmsgq = ckzalloc(sizeof(ckmsgq_t)); @@ -174,6 +207,29 @@ ckmsgq_t *create_ckmsgqs(ckpool_t *ckp, const char *name, const void *func, cons return ckmsgq; } +ckwq_t *create_ckwqs(ckpool_t *ckp, const char *name, const int count) +{ + ckwq_t *ckwq = ckzalloc(sizeof(ckmsgq_t) * count); + mutex_t *lock; + pthread_cond_t *cond; + int i; + + lock = ckalloc(sizeof(mutex_t)); + cond = ckalloc(sizeof(pthread_cond_t)); + mutex_init(lock); + cond_init(cond); + + for (i = 0; i < count; i++) { + snprintf(ckwq[i].name, 15, "%.6swq%d", name, i); + ckwq[i].ckp = ckp; + ckwq[i].lock = lock; + ckwq[i].cond = cond; + create_pthread(&ckwq[i].pth, ckwq_queue, &ckwq[i]); + } + + return ckwq; +} + /* Generic function for adding messages to a ckmsgq linked list and signal the * ckmsgq parsing thread to wake up and process it. */ void ckmsgq_add(ckmsgq_t *ckmsgq, void *data) @@ -185,10 +241,24 @@ void ckmsgq_add(ckmsgq_t *ckmsgq, void *data) mutex_lock(ckmsgq->lock); ckmsgq->messages++; DL_APPEND(ckmsgq->msgs, msg); - pthread_cond_signal(ckmsgq->cond); + pthread_cond_broadcast(ckmsgq->cond); mutex_unlock(ckmsgq->lock); } +void ckwq_add(ckwq_t *ckwq, const void *func, void *data) +{ + ckwqmsg_t *wqmsg = ckalloc(sizeof(ckwqmsg_t)); + + wqmsg->func = func; + wqmsg->data = data; + + mutex_lock(ckwq->lock); + ckwq->messages++; + DL_APPEND(ckwq->wqmsgs, wqmsg); + pthread_cond_broadcast(ckwq->cond); + mutex_unlock(ckwq->lock); +} + /* Return whether there are any messages queued in the ckmsgq linked list. */ bool ckmsgq_empty(ckmsgq_t *ckmsgq) { diff --git a/src/ckpool.h b/src/ckpool.h index 771ac692..eaf166fd 100644 --- a/src/ckpool.h +++ b/src/ckpool.h @@ -22,13 +22,22 @@ struct ckpool_instance; typedef struct ckpool_instance ckpool_t; +typedef struct ckmsg ckmsg_t; + struct ckmsg { - struct ckmsg *next; - struct ckmsg *prev; + ckmsg_t *next; + ckmsg_t *prev; void *data; }; -typedef struct ckmsg ckmsg_t; +typedef struct ckwqmsg ckwqmsg_t; + +struct ckwqmsg { + ckwqmsg_t *next; + ckwqmsg_t *prev; + void *data; + void (*func)(ckpool_t *, void *); +}; struct ckmsgq { ckpool_t *ckp; @@ -43,6 +52,18 @@ struct ckmsgq { typedef struct ckmsgq ckmsgq_t; +struct ckwq { + ckpool_t *ckp; + char name[16]; + pthread_t pth; + mutex_t *lock; + pthread_cond_t *cond; + ckwqmsg_t *wqmsgs; + int64_t messages; +}; + +typedef struct ckwq ckwq_t; + struct proc_instance { ckpool_t *ckp; unixsock_t us; @@ -204,7 +225,9 @@ struct ckpool_instance { ckmsgq_t *create_ckmsgq(ckpool_t *ckp, const char *name, const void *func); ckmsgq_t *create_ckmsgqs(ckpool_t *ckp, const char *name, const void *func, const int count); +ckwq_t *create_ckwqs(ckpool_t *ckp, const char *name, const int count); void ckmsgq_add(ckmsgq_t *ckmsgq, void *data); +void ckwq_add(ckwq_t *ckwq, const void *func, void *data); bool ckmsgq_empty(ckmsgq_t *ckmsgq); ckpool_t *global_ckp; From 2d94b18b99b2cef3bc5dc5c987eff267ff50f123 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Mon, 23 Feb 2015 10:17:17 +1100 Subject: [PATCH 16/39] Create a pool of workqueue threads for use by the stratifier using them for share processing, stratum receiving and transaction processing --- src/stratifier.c | 47 ++++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/stratifier.c b/src/stratifier.c index 9bf2ab98..442175d2 100644 --- a/src/stratifier.c +++ b/src/stratifier.c @@ -313,12 +313,10 @@ struct stratifier_data { char lasthash[68]; char lastswaphash[68]; + ckwq_t *ckwqs; // Generic workqueues ckmsgq_t *ssends; // Stratum sends - ckmsgq_t *srecvs; // Stratum receives ckmsgq_t *ckdbq; // ckdb - ckmsgq_t *sshareq; // Stratum share sends ckmsgq_t *sauthq; // Stratum authorisations - ckmsgq_t *stxnq; // Transaction requests int64_t user_instance_id; @@ -1644,6 +1642,21 @@ static void ckmsgq_stats(ckmsgq_t *ckmsgq, const int size, json_t **val) JSON_CPACK(*val, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); } +static void ckwq_stats(ckwq_t *ckwq, const int size, json_t **val) +{ + int objects, generated; + int64_t memsize; + ckwqmsg_t *wqmsg; + + mutex_lock(ckwq->lock); + DL_COUNT(ckwq->wqmsgs, wqmsg, objects); + generated = ckwq->messages; + mutex_unlock(ckwq->lock); + + memsize = (sizeof(ckwqmsg_t) + size) * objects; + JSON_CPACK(*val, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); +} + static char *stratifier_stats(ckpool_t *ckp, sdata_t *sdata) { json_t *val = json_object(), *subval; @@ -1690,15 +1703,14 @@ static char *stratifier_stats(ckpool_t *ckp, sdata_t *sdata) ckmsgq_stats(sdata->ssends, sizeof(smsg_t), &subval); json_set_object(val, "ssends", subval); + /* Don't know exactly how big the string is so just count the pointer for now */ - ckmsgq_stats(sdata->srecvs, sizeof(char *), &subval); - json_set_object(val, "srecvs", subval); + ckwq_stats(sdata->ckwqs, sizeof(char *) + sizeof(void *), &subval); + json_set_object(val, "ckwqs", subval); if (!CKP_STANDALONE(ckp)) { ckmsgq_stats(sdata->ckdbq, sizeof(char *), &subval); json_set_object(val, "ckdbq", subval); } - ckmsgq_stats(sdata->stxnq, sizeof(json_params_t), &subval); - json_set_object(val, "stxnq", subval); buf = json_dumps(val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER); json_decref(val); @@ -1706,6 +1718,8 @@ static char *stratifier_stats(ckpool_t *ckp, sdata_t *sdata) return buf; } +static void srecv_process(ckpool_t *ckp, char *buf); + static int stratum_loop(ckpool_t *ckp, proc_instance_t *pi) { int sockd, ret = 0, selret = 0; @@ -1760,7 +1774,7 @@ retry: /* The bulk of the messages will be received json from the * connector so look for this first. The srecv_process frees * the buf heap ram */ - ckmsgq_add(sdata->srecvs, buf); + ckwq_add(sdata->ckwqs, &srecv_process, buf); Close(sockd); buf = NULL; goto retry; @@ -3282,6 +3296,9 @@ static void suggest_diff(stratum_instance_t *client, const char *method, const j stratum_send_diff(sdata, client); } +static void sshare_process(ckpool_t *ckp, json_params_t *jp); +static void send_transactions(ckpool_t *ckp, json_params_t *jp); + /* Enter with client holding ref count */ static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64_t client_id, json_t *id_val, json_t *method_val, json_t *params_val, const char *address) @@ -3296,7 +3313,7 @@ static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64 if (likely(cmdmatch(method, "mining.submit") && client->authorised)) { json_params_t *jp = create_json_params(client_id, method_val, params_val, id_val, address); - ckmsgq_add(sdata->sshareq, jp); + ckwq_add(sdata->ckwqs, &sshare_process, jp); return; } @@ -3375,7 +3392,7 @@ static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64 if (cmdmatch(method, "mining.get")) { json_params_t *jp = create_json_params(client_id, method_val, params_val, id_val, address); - ckmsgq_add(sdata->stxnq, jp); + ckwq_add(sdata->ckwqs, &send_transactions, jp); return; } /* Unhandled message here */ @@ -4391,14 +4408,10 @@ int stratifier(proc_instance_t *pi) mutex_init(&sdata->ckdb_lock); sdata->ssends = create_ckmsgq(ckp, "ssender", &ssend_process); - /* Create half as many share processing threads as there are CPUs */ - threads = sysconf(_SC_NPROCESSORS_ONLN) / 2 ? : 1; - sdata->sshareq = create_ckmsgqs(ckp, "sprocessor", &sshare_process, threads); - /* Create 1/4 as many stratum processing threads as there are CPUs */ - threads = threads / 2 ? : 1; - sdata->srecvs = create_ckmsgqs(ckp, "sreceiver", &srecv_process, threads); + /* Create as many generic workqueue threads as there are CPUs */ + threads = sysconf(_SC_NPROCESSORS_ONLN); + sdata->ckwqs = create_ckwqs(ckp, "strat", threads); sdata->sauthq = create_ckmsgq(ckp, "authoriser", &sauth_process); - sdata->stxnq = create_ckmsgq(ckp, "stxnq", &send_transactions); if (!CKP_STANDALONE(ckp)) { sdata->ckdbq = create_ckmsgq(ckp, "ckdbqueue", &ckdbq_process); create_pthread(&pth_heartbeat, ckdb_heartbeat, ckp); From 4c4b48795bdd4fea53762078ad39a57344c3b84f Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Mon, 23 Feb 2015 10:27:22 +1100 Subject: [PATCH 17/39] Use the generic workqueues for do_update --- src/stratifier.c | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/src/stratifier.c b/src/stratifier.c index 442175d2..2956ad36 100644 --- a/src/stratifier.c +++ b/src/stratifier.c @@ -800,33 +800,21 @@ static void send_generator(ckpool_t *ckp, const char *msg, const int prio) sdata->gen_priority = 0; } -struct update_req { - pthread_t *pth; - ckpool_t *ckp; - int prio; -}; - static void broadcast_ping(sdata_t *sdata); /* This function assumes it will only receive a valid json gbt base template * since checking should have been done earlier, and creates the base template * for generating work templates. */ -static void *do_update(void *arg) +static void do_update(ckpool_t *ckp, int *prio) { - struct update_req *ur = (struct update_req *)arg; - ckpool_t *ckp = ur->ckp; sdata_t *sdata = ckp->data; bool new_block = false; - int prio = ur->prio; bool ret = false; workbase_t *wb; json_t *val; char *buf; - pthread_detach(pthread_self()); - rename_proc("updater"); - - buf = send_recv_generator(ckp, "getbase", prio); + buf = send_recv_generator(ckp, "getbase", *prio); if (unlikely(!buf)) { LOGNOTICE("Get base in update_base delayed due to higher priority request"); goto out; @@ -886,21 +874,17 @@ out: LOGINFO("Broadcast ping due to failed stratum base update"); broadcast_ping(sdata); } - dealloc(buf); - free(ur->pth); - free(ur); - return NULL; + free(buf); + free(prio); } static void update_base(ckpool_t *ckp, const int prio) { - struct update_req *ur = ckalloc(sizeof(struct update_req)); - pthread_t *pth = ckalloc(sizeof(pthread_t)); + int *pprio = ckalloc(sizeof(int)); + sdata_t *sdata = ckp->data; - ur->pth = pth; - ur->ckp = ckp; - ur->prio = prio; - create_pthread(pth, do_update, ur); + *pprio = prio; + ckwq_add(sdata->ckwqs, &do_update, pprio); } static void __kill_instance(stratum_instance_t *client) From 5f9f01e89442c85cd927f5fed82af7b08394ec17 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Mon, 23 Feb 2015 11:11:00 +1100 Subject: [PATCH 18/39] Revert to synchronous proc messages in anticipation of new async functions --- src/ckpool.c | 55 ++++++++-------------------------------------------- 1 file changed, 8 insertions(+), 47 deletions(-) diff --git a/src/ckpool.c b/src/ckpool.c index 6b8fa480..2108e515 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -568,32 +568,14 @@ out: static void childsighandler(const int sig); -struct proc_message { - proc_instance_t *pi; - char *msg; - const char *file; - const char *func; - int line; -}; - -/* Send all one way messages asynchronously so we can wait till the receiving - * end closes the socket to ensure all messages are received but no deadlocks - * can occur with 2 processes waiting for each other's socket closure. */ -void *async_send_proc(void *arg) +/* Send a single message to a process instance when there will be no response, + * closing the socket immediately. */ +void _send_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line) { - struct proc_message *pm = (struct proc_message *)arg; - proc_instance_t *pi = pm->pi; - char *msg = pm->msg; - const char *file = pm->file; - const char *func = pm->func; - int line = pm->line; - char *path = pi->us.path; bool ret = false; int sockd; - pthread_detach(pthread_self()); - if (unlikely(!path || !strlen(path))) { LOGERR("Attempted to send message %s to null path in send_proc", msg ? msg : ""); goto out; @@ -604,16 +586,14 @@ void *async_send_proc(void *arg) } /* At startup the pid fields are not set up before some processes are * forked so they never inherit them. */ - if (unlikely(!pi->pid)) { + if (unlikely(!pi->pid)) pi->pid = get_proc_pid(pi); - if (!pi->pid) { - LOGALERT("Attempting to send message %s to non existent process %s", msg, pi->processname); - goto out_nofail; - } + if (!pi->pid) { + LOGALERT("Attempting to send message %s to non existent process %s", msg, pi->processname); + return; } if (unlikely(kill_pid(pi->pid, 0))) { - LOGALERT("Attempting to send message %s to non existent process %s pid %d", - msg, pi->processname, pi->pid); + LOGALERT("Attempting to send message %s to non existent process %s", msg, pi->processname); goto out; } sockd = open_unix_client(path); @@ -633,25 +613,6 @@ out: LOGERR("Failure in send_proc from %s %s:%d", file, func, line); childsighandler(15); } -out_nofail: - free(msg); - free(pm); - return NULL; -} - -/* Send a single message to a process instance when there will be no response, - * closing the socket immediately. */ -void _send_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line) -{ - struct proc_message *pm = ckalloc(sizeof(struct proc_message)); - pthread_t pth; - - pm->pi = pi; - pm->msg = strdup(msg); - pm->file = file; - pm->func = func; - pm->line = line; - create_pthread(&pth, async_send_proc, pm); } /* Send a single message to a process instance and retrieve the response, then From 2865a0378fe7e60b5ac9dcc43f6f99421c4bb068 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Mon, 23 Feb 2015 11:15:44 +1100 Subject: [PATCH 19/39] Keep track of per process ckwqs in the ckpool structure --- src/ckpool.h | 2 ++ src/stratifier.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ckpool.h b/src/ckpool.h index eaf166fd..a734258a 100644 --- a/src/ckpool.h +++ b/src/ckpool.h @@ -213,6 +213,8 @@ struct ckpool_instance { /* Private data for each process */ void *data; + /* Private generic workqueues if this process has them */ + ckwq_t *ckwqs; }; #ifdef USE_CKDB diff --git a/src/stratifier.c b/src/stratifier.c index 2956ad36..94c70c24 100644 --- a/src/stratifier.c +++ b/src/stratifier.c @@ -4394,7 +4394,7 @@ int stratifier(proc_instance_t *pi) sdata->ssends = create_ckmsgq(ckp, "ssender", &ssend_process); /* Create as many generic workqueue threads as there are CPUs */ threads = sysconf(_SC_NPROCESSORS_ONLN); - sdata->ckwqs = create_ckwqs(ckp, "strat", threads); + ckp->ckwqs = sdata->ckwqs = create_ckwqs(ckp, "strat", threads); sdata->sauthq = create_ckmsgq(ckp, "authoriser", &sauth_process); if (!CKP_STANDALONE(ckp)) { sdata->ckdbq = create_ckmsgq(ckp, "ckdbqueue", &ckdbq_process); From 428cabdfc4c8fe0cf3be17aef5033295eeffb50f Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Mon, 23 Feb 2015 11:26:10 +1100 Subject: [PATCH 20/39] Add an asynchronous send proc function which uses each process' generic workqueues if they exist --- src/ckpool.c | 35 +++++++++++++++++++++++++++++++++++ src/ckpool.h | 2 ++ 2 files changed, 37 insertions(+) diff --git a/src/ckpool.c b/src/ckpool.c index 2108e515..1d676aa7 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -615,6 +615,41 @@ out: } } +struct proc_message { + proc_instance_t *pi; + char *msg; + const char *file; + const char *func; + int line; +}; + +static void asp_send(ckpool_t __maybe_unused *ckp, struct proc_message *pm) +{ + _send_proc(pm->pi, pm->msg, pm->file, pm->func, pm->line); + free(pm->msg); + free(pm); +} + +/* Fore sending asynchronous messages to another process, the sending process + * must have ckwqs of its own, referenced in the ckpool structure */ +void _async_send_proc(ckpool_t *ckp, proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line) +{ + struct proc_message *pm; + + if (unlikely(!ckp->ckwqs)) { + LOGALERT("Workqueues not set up in async_send_proc!"); + _send_proc(pi, msg, file, func, line); + return; + } + pm = ckzalloc(sizeof(struct proc_message)); + pm->pi = pi; + pm->msg = strdup(msg); + pm->file = file; + pm->func = func; + pm->line = line; + ckwq_add(ckp->ckwqs, &asp_send, pm); +} + /* Send a single message to a process instance and retrieve the response, then * close the socket. */ char *_send_recv_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line) diff --git a/src/ckpool.h b/src/ckpool.h index a734258a..74dc5e3e 100644 --- a/src/ckpool.h +++ b/src/ckpool.h @@ -239,6 +239,8 @@ void empty_buffer(connsock_t *cs); int read_socket_line(connsock_t *cs, const int timeout); void _send_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line); #define send_proc(pi, msg) _send_proc(pi, msg, __FILE__, __func__, __LINE__) +void _async_send_proc(ckpool_t *ckp, proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line); +#define async_send_proc(ckp, pi, msg) _async_send_proc(ckp, pi, msg, __FILE__, __func__, __LINE__) char *_send_recv_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line); #define send_recv_proc(pi, msg) _send_recv_proc(pi, msg, __FILE__, __func__, __LINE__) char *_send_recv_ckdb(const ckpool_t *ckp, const char *msg, const char *file, const char *func, const int line); From cabc01d7cda3e674ec5a6e8e1a7e9d83b870098e Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Mon, 23 Feb 2015 11:36:46 +1100 Subject: [PATCH 21/39] Use asynchronous send_proc in the stratifier --- src/stratifier.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/stratifier.c b/src/stratifier.c index 94c70c24..c844c1dd 100644 --- a/src/stratifier.c +++ b/src/stratifier.c @@ -795,7 +795,7 @@ static void send_generator(ckpool_t *ckp, const char *msg, const int prio) set = true; } else set = false; - send_proc(ckp->generator, msg); + async_send_proc(ckp, ckp->generator, msg); if (set) sdata->gen_priority = 0; } @@ -928,7 +928,7 @@ static void drop_allclients(ckpool_t *ckp) client->dropped = true; kills++; sprintf(buf, "dropclient=%"PRId64, client_id); - send_proc(ckp->connector, buf); + async_send_proc(ckp, ckp->connector, buf); } HASH_ITER(hh, sdata->disconnected_instances, client, tmp) { disconnects++; @@ -3287,6 +3287,7 @@ static void send_transactions(ckpool_t *ckp, json_params_t *jp); static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64_t client_id, json_t *id_val, json_t *method_val, json_t *params_val, const char *address) { + ckpool_t *ckp = client->ckp; const char *method; char buf[256]; @@ -3332,7 +3333,7 @@ static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64 * to it since it's unauthorised. Set the flag just in case. */ client->authorised = false; snprintf(buf, 255, "passthrough=%"PRId64, client_id); - send_proc(client->ckp->connector, buf); + async_send_proc(ckp, client->ckp->connector, buf); return; } @@ -3340,7 +3341,7 @@ static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64 if (!client->subscribed) { LOGINFO("Dropping unsubscribed client %"PRId64, client_id); snprintf(buf, 255, "dropclient=%"PRId64, client_id); - send_proc(client->ckp->connector, buf); + async_send_proc(ckp, client->ckp->connector, buf); return; } @@ -3363,7 +3364,7 @@ static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64 * the stratum instance data. Clients will just reconnect. */ LOGINFO("Dropping unauthorised client %"PRId64, client_id); snprintf(buf, 255, "dropclient=%"PRId64, client_id); - send_proc(client->ckp->connector, buf); + async_send_proc(ckp, client->ckp->connector, buf); return; } @@ -3395,12 +3396,13 @@ static void parse_instance_msg(sdata_t *sdata, smsg_t *msg, stratum_instance_t * { json_t *val = msg->json_msg, *id_val, *method, *params; int64_t client_id = msg->client_id; + ckpool_t *ckp = client->ckp; char buf[256]; if (unlikely(client->reject == 2)) { LOGINFO("Dropping client %"PRId64" tagged for lazy invalidation", client_id); snprintf(buf, 255, "dropclient=%"PRId64, client_id); - send_proc(client->ckp->connector, buf); + async_send_proc(ckp, ckp->connector, buf); goto out; } @@ -3534,7 +3536,7 @@ static void ssend_process(ckpool_t *ckp, smsg_t *msg) * connector process to be delivered */ json_object_set_new_nocheck(msg->json_msg, "client_id", json_integer(msg->client_id)); s = json_dumps(msg->json_msg, 0); - send_proc(ckp->connector, s); + async_send_proc(ckp, ckp->connector, s); free(s); free_smsg(msg); } From a465e47f857058e2c6d7b2993d0b9869fed5e695 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Mon, 23 Feb 2015 11:40:02 +1100 Subject: [PATCH 22/39] Use async send proc in the connector --- src/connector.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/connector.c b/src/connector.c index f6d9f1ab..ece13099 100644 --- a/src/connector.c +++ b/src/connector.c @@ -97,6 +97,8 @@ struct connector_data { /* For protecting the pending sends list */ mutex_t sender_lock; pthread_cond_t sender_cond; + + ckwq_t *ckwqs; }; typedef struct connector_data cdata_t; @@ -242,7 +244,7 @@ static void stratifier_drop_client(ckpool_t *ckp, int64_t id) char buf[256]; sprintf(buf, "dropclient=%"PRId64, id); - send_proc(ckp->stratifier, buf); + async_send_proc(ckp, ckp->stratifier, buf); } /* Invalidate this instance. Remove them from the hashtables we look up @@ -361,9 +363,9 @@ reparse: * filtered by the stratifier. */ if (likely(client->fd != -1)) { if (ckp->passthrough) - send_proc(ckp->generator, s); + async_send_proc(ckp, ckp->generator, s); else - send_proc(ckp->stratifier, s); + async_send_proc(ckp, ckp->stratifier, s); } free(s); @@ -861,6 +863,8 @@ int connector(proc_instance_t *pi) LOGWARNING("%s connector starting", ckp->name); ckp->data = cdata; cdata->ckp = ckp; + /* Connector only requires one work queue */ + ckp->ckwqs = cdata->ckwqs = create_ckwqs(ckp, "conn", 1); if (!ckp->serverurls) cdata->serverfd = ckalloc(sizeof(int *)); From cada930aa8b612535d47616a07bc8e9288796520 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Mon, 23 Feb 2015 11:42:39 +1100 Subject: [PATCH 23/39] Use async senc proc in the generator --- src/generator.c | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/generator.c b/src/generator.c index 90f054f4..233f25e6 100644 --- a/src/generator.c +++ b/src/generator.c @@ -132,6 +132,7 @@ struct generator_data { proxy_instance_t *proxy_list; /* Linked list of all active proxies */ int proxy_notify_id; // Globally increasing notify id ckmsgq_t *srvchk; // Server check message queue + ckwq_t *ckwqs; }; typedef struct generator_data gdata_t; @@ -221,7 +222,7 @@ retry: cs = &alive->cs; LOGINFO("Connected to live server %s:%s", cs->url, cs->port); out: - send_proc(ckp->connector, alive ? "accept" : "reject"); + async_send_proc(ckp, ckp->connector, alive ? "accept" : "reject"); return alive; } @@ -367,7 +368,7 @@ retry: ret = submit_block(cs, buf + 12 + 64 + 1); memset(buf + 12 + 64, 0, 1); sprintf(blockmsg, "%sblock:%s", ret ? "" : "no", buf + 12); - send_proc(ckp->stratifier, blockmsg); + async_send_proc(ckp, ckp->stratifier, blockmsg); } else if (cmdmatch(buf, "checkaddr:")) { if (validate_address(cs, buf + 10)) send_unix_msg(sockd, "true"); @@ -1297,22 +1298,22 @@ static void *proxy_recv(void *arg) if (ret < 1) { /* Send ourselves a reconnect message */ LOGWARNING("Failed to read_socket_line in proxy_recv, attempting reconnect"); - send_proc(ckp->generator, "reconnect"); + async_send_proc(ckp, ckp->generator, "reconnect"); break; } if (parse_method(proxi, cs->buf)) { if (proxi->notified) { - send_proc(ckp->stratifier, "notify"); + async_send_proc(ckp, ckp->stratifier, "notify"); proxi->notified = false; } if (proxi->diffed) { - send_proc(ckp->stratifier, "diff"); + async_send_proc(ckp, ckp->stratifier, "diff"); proxi->diffed = false; } if (proxi->reconnect) { proxi->reconnect = false; LOGWARNING("Reconnect issue, dropping existing connection"); - send_proc(ckp->generator, "reconnect"); + async_send_proc(ckp, ckp->generator, "reconnect"); break; } continue; @@ -1402,13 +1403,13 @@ static void *passthrough_recv(void *arg) if (ret < 1) { /* Send ourselves a reconnect message */ LOGWARNING("Failed to read_socket_line in proxy_recv, attempting reconnect"); - send_proc(ckp->generator, "reconnect"); + async_send_proc(ckp, ckp->generator, "reconnect"); break; } /* Simply forward the message on, as is, to the connector to * process. Possibly parse parameters sent by upstream pool * here */ - send_proc(ckp->connector, cs->buf); + async_send_proc(ckp, ckp->connector, cs->buf); } return NULL; } @@ -1523,8 +1524,8 @@ retry: } } if (!alive) { - send_proc(ckp->connector, "reject"); - send_proc(ckp->stratifier, "dropall"); + async_send_proc(ckp, ckp->connector, "reject"); + async_send_proc(ckp, ckp->stratifier, "dropall"); LOGWARNING("Failed to connect to any servers as proxy, retrying in 5s!"); sleep(5); goto retry; @@ -1543,7 +1544,7 @@ retry: create_pthread(&alive->pth_psend, proxy_send, alive); } out: - send_proc(ckp->connector, alive ? "accept" : "reject"); + async_send_proc(ckp, ckp->connector, alive ? "accept" : "reject"); return alive; } @@ -1552,8 +1553,8 @@ static void kill_proxy(ckpool_t *ckp, proxy_instance_t *proxi) notify_instance_t *ni, *tmp; connsock_t *cs; - send_proc(ckp->stratifier, "reconnect"); - send_proc(ckp->connector, "reject"); + async_send_proc(ckp, ckp->stratifier, "reconnect"); + async_send_proc(ckp, ckp->connector, "reject"); if (!proxi) // This shouldn't happen return; @@ -1603,8 +1604,8 @@ reconnect: /* We've just subscribed and authorised so tell the stratifier to * retrieve the first subscription. */ if (!ckp->passthrough) { - send_proc(ckp->stratifier, "subscribe"); - send_proc(ckp->stratifier, "notify"); + async_send_proc(ckp, ckp->stratifier, "subscribe"); + async_send_proc(ckp, ckp->stratifier, "notify"); proxi->notified = false; } @@ -1792,7 +1793,7 @@ static void server_watchdog(ckpool_t *ckp, server_instance_t *cursi) break; } if (alive) - send_proc(ckp->generator, "reconnect"); + async_send_proc(ckp, ckp->generator, "reconnect"); } static void proxy_watchdog(ckpool_t *ckp, server_instance_t *cursi) @@ -1838,7 +1839,7 @@ static void proxy_watchdog(ckpool_t *ckp, server_instance_t *cursi) break; } if (alive) - send_proc(ckp->generator, "reconnect"); + async_send_proc(ckp, ckp->generator, "reconnect"); } @@ -1851,6 +1852,8 @@ int generator(proc_instance_t *pi) LOGWARNING("%s generator starting", ckp->name); gdata = ckzalloc(sizeof(gdata_t)); ckp->data = gdata; + /* Generator only requires one work queue */ + ckp->ckwqs = gdata->ckwqs = create_ckwqs(ckp, "gen", 1); if (ckp->proxy) { gdata->srvchk = create_ckmsgq(ckp, "prxchk", &proxy_watchdog); ret = proxy_mode(ckp, pi); From 862e3df8215c68c9c1420decdd3e45d14cd84f5e Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Mon, 23 Feb 2015 17:07:34 +1100 Subject: [PATCH 24/39] Fix use of ckwq variables --- src/ckpool.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ckpool.c b/src/ckpool.c index 1d676aa7..cf79794f 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -137,27 +137,27 @@ static void *ckmsg_queue(void *arg) /* Generic workqueue function and message receiving and parsing thread */ static void *ckwq_queue(void *arg) { - ckwq_t *ckmsgq = (ckwq_t *)arg; - ckpool_t *ckp = ckmsgq->ckp; + ckwq_t *ckwq = (ckwq_t *)arg; + ckpool_t *ckp = ckwq->ckp; pthread_detach(pthread_self()); - rename_proc(ckmsgq->name); + rename_proc(ckwq->name); while (42) { ckwqmsg_t *wqmsg; tv_t now; ts_t abs; - mutex_lock(ckmsgq->lock); + mutex_lock(ckwq->lock); tv_time(&now); tv_to_ts(&abs, &now); abs.tv_sec++; - if (!ckmsgq->wqmsgs) - cond_timedwait(ckmsgq->cond, ckmsgq->lock, &abs); - wqmsg = ckmsgq->wqmsgs; + if (!ckwq->wqmsgs) + cond_timedwait(ckwq->cond, ckwq->lock, &abs); + wqmsg = ckwq->wqmsgs; if (wqmsg) - DL_DELETE(ckmsgq->wqmsgs, wqmsg); - mutex_unlock(ckmsgq->lock); + DL_DELETE(ckwq->wqmsgs, wqmsg); + mutex_unlock(ckwq->lock); if (!wqmsg) continue; @@ -209,7 +209,7 @@ ckmsgq_t *create_ckmsgqs(ckpool_t *ckp, const char *name, const void *func, cons ckwq_t *create_ckwqs(ckpool_t *ckp, const char *name, const int count) { - ckwq_t *ckwq = ckzalloc(sizeof(ckmsgq_t) * count); + ckwq_t *ckwq = ckzalloc(sizeof(ckwq_t) * count); mutex_t *lock; pthread_cond_t *cond; int i; From 21194cfbb427b6d6b75e43fefc6ea7e4af4f0d6c Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Mon, 23 Feb 2015 20:21:05 +1100 Subject: [PATCH 25/39] Count dropped workers in _dec_instance_ref Conflicts: src/stratifier.c --- src/stratifier.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/stratifier.c b/src/stratifier.c index c844c1dd..5752ef2f 100644 --- a/src/stratifier.c +++ b/src/stratifier.c @@ -1223,11 +1223,15 @@ static void client_drop_message(const int64_t client_id, const int dropped, cons } } +static void dec_worker(ckpool_t *ckp, user_instance_t *instance); + /* Decrease the reference count of instance. */ static void _dec_instance_ref(sdata_t *sdata, stratum_instance_t *client, const char *file, const char *func, const int line) { + user_instance_t *user = client->user_instance; int64_t client_id = client->id; + ckpool_t *ckp = client->ckp; int dropped = 0, ref; ck_wlock(&sdata->instance_lock); @@ -1235,7 +1239,7 @@ static void _dec_instance_ref(sdata_t *sdata, stratum_instance_t *client, const /* See if there are any instances that were dropped that could not be * moved due to holding a reference and drop them now. */ if (unlikely(client->dropped && !ref)) - dropped = __drop_client(sdata, client, client->user_instance); + dropped = __drop_client(sdata, client, user); ck_wunlock(&sdata->instance_lock); client_drop_message(client_id, dropped, true); @@ -1243,6 +1247,9 @@ static void _dec_instance_ref(sdata_t *sdata, stratum_instance_t *client, const /* This should never happen */ if (unlikely(ref < 0)) LOGERR("Instance ref count dropped below zero from %s %s:%d", file, func, line); + + if (dropped) + dec_worker(ckp, user); } #define dec_instance_ref(sdata, instance) _dec_instance_ref(sdata, instance, __FILE__, __func__, __LINE__) From 7fa3dc25298d648b113b7db001d5fcf7c32abf9d Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Tue, 24 Feb 2015 10:32:51 +1100 Subject: [PATCH 26/39] Only dec worker if user exists --- src/stratifier.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stratifier.c b/src/stratifier.c index 5752ef2f..b46bafc0 100644 --- a/src/stratifier.c +++ b/src/stratifier.c @@ -1248,7 +1248,7 @@ static void _dec_instance_ref(sdata_t *sdata, stratum_instance_t *client, const if (unlikely(ref < 0)) LOGERR("Instance ref count dropped below zero from %s %s:%d", file, func, line); - if (dropped) + if (dropped && user) dec_worker(ckp, user); } From d4304de798ea8d86d77c07f3b0fac0f03c7d3086 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Tue, 24 Feb 2015 23:46:34 +1100 Subject: [PATCH 27/39] These weren't the droids we were looking for. --- src/ckpool.c | 142 +++++++++++++---------------------------------- src/ckpool.h | 33 +---------- src/connector.c | 10 +--- src/generator.c | 37 ++++++------ src/stratifier.c | 95 +++++++++++++++---------------- 5 files changed, 109 insertions(+), 208 deletions(-) diff --git a/src/ckpool.c b/src/ckpool.c index cf79794f..a1c4a514 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -134,39 +134,6 @@ static void *ckmsg_queue(void *arg) return NULL; } -/* Generic workqueue function and message receiving and parsing thread */ -static void *ckwq_queue(void *arg) -{ - ckwq_t *ckwq = (ckwq_t *)arg; - ckpool_t *ckp = ckwq->ckp; - - pthread_detach(pthread_self()); - rename_proc(ckwq->name); - - while (42) { - ckwqmsg_t *wqmsg; - tv_t now; - ts_t abs; - - mutex_lock(ckwq->lock); - tv_time(&now); - tv_to_ts(&abs, &now); - abs.tv_sec++; - if (!ckwq->wqmsgs) - cond_timedwait(ckwq->cond, ckwq->lock, &abs); - wqmsg = ckwq->wqmsgs; - if (wqmsg) - DL_DELETE(ckwq->wqmsgs, wqmsg); - mutex_unlock(ckwq->lock); - - if (!wqmsg) - continue; - wqmsg->func(ckp, wqmsg->data); - free(wqmsg); - } - return NULL; -} - ckmsgq_t *create_ckmsgq(ckpool_t *ckp, const char *name, const void *func) { ckmsgq_t *ckmsgq = ckzalloc(sizeof(ckmsgq_t)); @@ -207,29 +174,6 @@ ckmsgq_t *create_ckmsgqs(ckpool_t *ckp, const char *name, const void *func, cons return ckmsgq; } -ckwq_t *create_ckwqs(ckpool_t *ckp, const char *name, const int count) -{ - ckwq_t *ckwq = ckzalloc(sizeof(ckwq_t) * count); - mutex_t *lock; - pthread_cond_t *cond; - int i; - - lock = ckalloc(sizeof(mutex_t)); - cond = ckalloc(sizeof(pthread_cond_t)); - mutex_init(lock); - cond_init(cond); - - for (i = 0; i < count; i++) { - snprintf(ckwq[i].name, 15, "%.6swq%d", name, i); - ckwq[i].ckp = ckp; - ckwq[i].lock = lock; - ckwq[i].cond = cond; - create_pthread(&ckwq[i].pth, ckwq_queue, &ckwq[i]); - } - - return ckwq; -} - /* Generic function for adding messages to a ckmsgq linked list and signal the * ckmsgq parsing thread to wake up and process it. */ void ckmsgq_add(ckmsgq_t *ckmsgq, void *data) @@ -241,24 +185,10 @@ void ckmsgq_add(ckmsgq_t *ckmsgq, void *data) mutex_lock(ckmsgq->lock); ckmsgq->messages++; DL_APPEND(ckmsgq->msgs, msg); - pthread_cond_broadcast(ckmsgq->cond); + pthread_cond_signal(ckmsgq->cond); mutex_unlock(ckmsgq->lock); } -void ckwq_add(ckwq_t *ckwq, const void *func, void *data) -{ - ckwqmsg_t *wqmsg = ckalloc(sizeof(ckwqmsg_t)); - - wqmsg->func = func; - wqmsg->data = data; - - mutex_lock(ckwq->lock); - ckwq->messages++; - DL_APPEND(ckwq->wqmsgs, wqmsg); - pthread_cond_broadcast(ckwq->cond); - mutex_unlock(ckwq->lock); -} - /* Return whether there are any messages queued in the ckmsgq linked list. */ bool ckmsgq_empty(ckmsgq_t *ckmsgq) { @@ -568,14 +498,32 @@ out: static void childsighandler(const int sig); -/* Send a single message to a process instance when there will be no response, - * closing the socket immediately. */ -void _send_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line) +struct proc_message { + proc_instance_t *pi; + char *msg; + const char *file; + const char *func; + int line; +}; + +/* Send all one way messages asynchronously so we can wait till the receiving + * end closes the socket to ensure all messages are received but no deadlocks + * can occur with 2 processes waiting for each other's socket closure. */ +void *async_send_proc(void *arg) { + struct proc_message *pm = (struct proc_message *)arg; + proc_instance_t *pi = pm->pi; + char *msg = pm->msg; + const char *file = pm->file; + const char *func = pm->func; + int line = pm->line; + char *path = pi->us.path; bool ret = false; int sockd; + pthread_detach(pthread_self()); + if (unlikely(!path || !strlen(path))) { LOGERR("Attempted to send message %s to null path in send_proc", msg ? msg : ""); goto out; @@ -586,14 +534,16 @@ void _send_proc(proc_instance_t *pi, const char *msg, const char *file, const ch } /* At startup the pid fields are not set up before some processes are * forked so they never inherit them. */ - if (unlikely(!pi->pid)) + if (unlikely(!pi->pid)) { pi->pid = get_proc_pid(pi); - if (!pi->pid) { - LOGALERT("Attempting to send message %s to non existent process %s", msg, pi->processname); - return; + if (!pi->pid) { + LOGALERT("Attempting to send message %s to non existent process %s", msg, pi->processname); + goto out_nofail; + } } if (unlikely(kill_pid(pi->pid, 0))) { - LOGALERT("Attempting to send message %s to non existent process %s", msg, pi->processname); + LOGALERT("Attempting to send message %s to non existent process %s pid %d", + msg, pi->processname, pi->pid); goto out; } sockd = open_unix_client(path); @@ -613,41 +563,25 @@ out: LOGERR("Failure in send_proc from %s %s:%d", file, func, line); childsighandler(15); } -} - -struct proc_message { - proc_instance_t *pi; - char *msg; - const char *file; - const char *func; - int line; -}; - -static void asp_send(ckpool_t __maybe_unused *ckp, struct proc_message *pm) -{ - _send_proc(pm->pi, pm->msg, pm->file, pm->func, pm->line); - free(pm->msg); +out_nofail: + free(msg); free(pm); + return NULL; } -/* Fore sending asynchronous messages to another process, the sending process - * must have ckwqs of its own, referenced in the ckpool structure */ -void _async_send_proc(ckpool_t *ckp, proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line) +/* Send a single message to a process instance when there will be no response, + * closing the socket immediately. */ +void _send_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line) { - struct proc_message *pm; + struct proc_message *pm = ckalloc(sizeof(struct proc_message)); + pthread_t pth; - if (unlikely(!ckp->ckwqs)) { - LOGALERT("Workqueues not set up in async_send_proc!"); - _send_proc(pi, msg, file, func, line); - return; - } - pm = ckzalloc(sizeof(struct proc_message)); pm->pi = pi; pm->msg = strdup(msg); pm->file = file; pm->func = func; pm->line = line; - ckwq_add(ckp->ckwqs, &asp_send, pm); + create_pthread(&pth, async_send_proc, pm); } /* Send a single message to a process instance and retrieve the response, then diff --git a/src/ckpool.h b/src/ckpool.h index 74dc5e3e..771ac692 100644 --- a/src/ckpool.h +++ b/src/ckpool.h @@ -22,22 +22,13 @@ struct ckpool_instance; typedef struct ckpool_instance ckpool_t; -typedef struct ckmsg ckmsg_t; - struct ckmsg { - ckmsg_t *next; - ckmsg_t *prev; + struct ckmsg *next; + struct ckmsg *prev; void *data; }; -typedef struct ckwqmsg ckwqmsg_t; - -struct ckwqmsg { - ckwqmsg_t *next; - ckwqmsg_t *prev; - void *data; - void (*func)(ckpool_t *, void *); -}; +typedef struct ckmsg ckmsg_t; struct ckmsgq { ckpool_t *ckp; @@ -52,18 +43,6 @@ struct ckmsgq { typedef struct ckmsgq ckmsgq_t; -struct ckwq { - ckpool_t *ckp; - char name[16]; - pthread_t pth; - mutex_t *lock; - pthread_cond_t *cond; - ckwqmsg_t *wqmsgs; - int64_t messages; -}; - -typedef struct ckwq ckwq_t; - struct proc_instance { ckpool_t *ckp; unixsock_t us; @@ -213,8 +192,6 @@ struct ckpool_instance { /* Private data for each process */ void *data; - /* Private generic workqueues if this process has them */ - ckwq_t *ckwqs; }; #ifdef USE_CKDB @@ -227,9 +204,7 @@ struct ckpool_instance { ckmsgq_t *create_ckmsgq(ckpool_t *ckp, const char *name, const void *func); ckmsgq_t *create_ckmsgqs(ckpool_t *ckp, const char *name, const void *func, const int count); -ckwq_t *create_ckwqs(ckpool_t *ckp, const char *name, const int count); void ckmsgq_add(ckmsgq_t *ckmsgq, void *data); -void ckwq_add(ckwq_t *ckwq, const void *func, void *data); bool ckmsgq_empty(ckmsgq_t *ckmsgq); ckpool_t *global_ckp; @@ -239,8 +214,6 @@ void empty_buffer(connsock_t *cs); int read_socket_line(connsock_t *cs, const int timeout); void _send_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line); #define send_proc(pi, msg) _send_proc(pi, msg, __FILE__, __func__, __LINE__) -void _async_send_proc(ckpool_t *ckp, proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line); -#define async_send_proc(ckp, pi, msg) _async_send_proc(ckp, pi, msg, __FILE__, __func__, __LINE__) char *_send_recv_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line); #define send_recv_proc(pi, msg) _send_recv_proc(pi, msg, __FILE__, __func__, __LINE__) char *_send_recv_ckdb(const ckpool_t *ckp, const char *msg, const char *file, const char *func, const int line); diff --git a/src/connector.c b/src/connector.c index ece13099..f6d9f1ab 100644 --- a/src/connector.c +++ b/src/connector.c @@ -97,8 +97,6 @@ struct connector_data { /* For protecting the pending sends list */ mutex_t sender_lock; pthread_cond_t sender_cond; - - ckwq_t *ckwqs; }; typedef struct connector_data cdata_t; @@ -244,7 +242,7 @@ static void stratifier_drop_client(ckpool_t *ckp, int64_t id) char buf[256]; sprintf(buf, "dropclient=%"PRId64, id); - async_send_proc(ckp, ckp->stratifier, buf); + send_proc(ckp->stratifier, buf); } /* Invalidate this instance. Remove them from the hashtables we look up @@ -363,9 +361,9 @@ reparse: * filtered by the stratifier. */ if (likely(client->fd != -1)) { if (ckp->passthrough) - async_send_proc(ckp, ckp->generator, s); + send_proc(ckp->generator, s); else - async_send_proc(ckp, ckp->stratifier, s); + send_proc(ckp->stratifier, s); } free(s); @@ -863,8 +861,6 @@ int connector(proc_instance_t *pi) LOGWARNING("%s connector starting", ckp->name); ckp->data = cdata; cdata->ckp = ckp; - /* Connector only requires one work queue */ - ckp->ckwqs = cdata->ckwqs = create_ckwqs(ckp, "conn", 1); if (!ckp->serverurls) cdata->serverfd = ckalloc(sizeof(int *)); diff --git a/src/generator.c b/src/generator.c index 233f25e6..90f054f4 100644 --- a/src/generator.c +++ b/src/generator.c @@ -132,7 +132,6 @@ struct generator_data { proxy_instance_t *proxy_list; /* Linked list of all active proxies */ int proxy_notify_id; // Globally increasing notify id ckmsgq_t *srvchk; // Server check message queue - ckwq_t *ckwqs; }; typedef struct generator_data gdata_t; @@ -222,7 +221,7 @@ retry: cs = &alive->cs; LOGINFO("Connected to live server %s:%s", cs->url, cs->port); out: - async_send_proc(ckp, ckp->connector, alive ? "accept" : "reject"); + send_proc(ckp->connector, alive ? "accept" : "reject"); return alive; } @@ -368,7 +367,7 @@ retry: ret = submit_block(cs, buf + 12 + 64 + 1); memset(buf + 12 + 64, 0, 1); sprintf(blockmsg, "%sblock:%s", ret ? "" : "no", buf + 12); - async_send_proc(ckp, ckp->stratifier, blockmsg); + send_proc(ckp->stratifier, blockmsg); } else if (cmdmatch(buf, "checkaddr:")) { if (validate_address(cs, buf + 10)) send_unix_msg(sockd, "true"); @@ -1298,22 +1297,22 @@ static void *proxy_recv(void *arg) if (ret < 1) { /* Send ourselves a reconnect message */ LOGWARNING("Failed to read_socket_line in proxy_recv, attempting reconnect"); - async_send_proc(ckp, ckp->generator, "reconnect"); + send_proc(ckp->generator, "reconnect"); break; } if (parse_method(proxi, cs->buf)) { if (proxi->notified) { - async_send_proc(ckp, ckp->stratifier, "notify"); + send_proc(ckp->stratifier, "notify"); proxi->notified = false; } if (proxi->diffed) { - async_send_proc(ckp, ckp->stratifier, "diff"); + send_proc(ckp->stratifier, "diff"); proxi->diffed = false; } if (proxi->reconnect) { proxi->reconnect = false; LOGWARNING("Reconnect issue, dropping existing connection"); - async_send_proc(ckp, ckp->generator, "reconnect"); + send_proc(ckp->generator, "reconnect"); break; } continue; @@ -1403,13 +1402,13 @@ static void *passthrough_recv(void *arg) if (ret < 1) { /* Send ourselves a reconnect message */ LOGWARNING("Failed to read_socket_line in proxy_recv, attempting reconnect"); - async_send_proc(ckp, ckp->generator, "reconnect"); + send_proc(ckp->generator, "reconnect"); break; } /* Simply forward the message on, as is, to the connector to * process. Possibly parse parameters sent by upstream pool * here */ - async_send_proc(ckp, ckp->connector, cs->buf); + send_proc(ckp->connector, cs->buf); } return NULL; } @@ -1524,8 +1523,8 @@ retry: } } if (!alive) { - async_send_proc(ckp, ckp->connector, "reject"); - async_send_proc(ckp, ckp->stratifier, "dropall"); + send_proc(ckp->connector, "reject"); + send_proc(ckp->stratifier, "dropall"); LOGWARNING("Failed to connect to any servers as proxy, retrying in 5s!"); sleep(5); goto retry; @@ -1544,7 +1543,7 @@ retry: create_pthread(&alive->pth_psend, proxy_send, alive); } out: - async_send_proc(ckp, ckp->connector, alive ? "accept" : "reject"); + send_proc(ckp->connector, alive ? "accept" : "reject"); return alive; } @@ -1553,8 +1552,8 @@ static void kill_proxy(ckpool_t *ckp, proxy_instance_t *proxi) notify_instance_t *ni, *tmp; connsock_t *cs; - async_send_proc(ckp, ckp->stratifier, "reconnect"); - async_send_proc(ckp, ckp->connector, "reject"); + send_proc(ckp->stratifier, "reconnect"); + send_proc(ckp->connector, "reject"); if (!proxi) // This shouldn't happen return; @@ -1604,8 +1603,8 @@ reconnect: /* We've just subscribed and authorised so tell the stratifier to * retrieve the first subscription. */ if (!ckp->passthrough) { - async_send_proc(ckp, ckp->stratifier, "subscribe"); - async_send_proc(ckp, ckp->stratifier, "notify"); + send_proc(ckp->stratifier, "subscribe"); + send_proc(ckp->stratifier, "notify"); proxi->notified = false; } @@ -1793,7 +1792,7 @@ static void server_watchdog(ckpool_t *ckp, server_instance_t *cursi) break; } if (alive) - async_send_proc(ckp, ckp->generator, "reconnect"); + send_proc(ckp->generator, "reconnect"); } static void proxy_watchdog(ckpool_t *ckp, server_instance_t *cursi) @@ -1839,7 +1838,7 @@ static void proxy_watchdog(ckpool_t *ckp, server_instance_t *cursi) break; } if (alive) - async_send_proc(ckp, ckp->generator, "reconnect"); + send_proc(ckp->generator, "reconnect"); } @@ -1852,8 +1851,6 @@ int generator(proc_instance_t *pi) LOGWARNING("%s generator starting", ckp->name); gdata = ckzalloc(sizeof(gdata_t)); ckp->data = gdata; - /* Generator only requires one work queue */ - ckp->ckwqs = gdata->ckwqs = create_ckwqs(ckp, "gen", 1); if (ckp->proxy) { gdata->srvchk = create_ckmsgq(ckp, "prxchk", &proxy_watchdog); ret = proxy_mode(ckp, pi); diff --git a/src/stratifier.c b/src/stratifier.c index b46bafc0..859edc6e 100644 --- a/src/stratifier.c +++ b/src/stratifier.c @@ -313,10 +313,12 @@ struct stratifier_data { char lasthash[68]; char lastswaphash[68]; - ckwq_t *ckwqs; // Generic workqueues ckmsgq_t *ssends; // Stratum sends + ckmsgq_t *srecvs; // Stratum receives ckmsgq_t *ckdbq; // ckdb + ckmsgq_t *sshareq; // Stratum share sends ckmsgq_t *sauthq; // Stratum authorisations + ckmsgq_t *stxnq; // Transaction requests int64_t user_instance_id; @@ -795,26 +797,38 @@ static void send_generator(ckpool_t *ckp, const char *msg, const int prio) set = true; } else set = false; - async_send_proc(ckp, ckp->generator, msg); + send_proc(ckp->generator, msg); if (set) sdata->gen_priority = 0; } +struct update_req { + pthread_t *pth; + ckpool_t *ckp; + int prio; +}; + static void broadcast_ping(sdata_t *sdata); /* This function assumes it will only receive a valid json gbt base template * since checking should have been done earlier, and creates the base template * for generating work templates. */ -static void do_update(ckpool_t *ckp, int *prio) +static void *do_update(void *arg) { + struct update_req *ur = (struct update_req *)arg; + ckpool_t *ckp = ur->ckp; sdata_t *sdata = ckp->data; bool new_block = false; + int prio = ur->prio; bool ret = false; workbase_t *wb; json_t *val; char *buf; - buf = send_recv_generator(ckp, "getbase", *prio); + pthread_detach(pthread_self()); + rename_proc("updater"); + + buf = send_recv_generator(ckp, "getbase", prio); if (unlikely(!buf)) { LOGNOTICE("Get base in update_base delayed due to higher priority request"); goto out; @@ -874,17 +888,21 @@ out: LOGINFO("Broadcast ping due to failed stratum base update"); broadcast_ping(sdata); } - free(buf); - free(prio); + dealloc(buf); + free(ur->pth); + free(ur); + return NULL; } static void update_base(ckpool_t *ckp, const int prio) { - int *pprio = ckalloc(sizeof(int)); - sdata_t *sdata = ckp->data; + struct update_req *ur = ckalloc(sizeof(struct update_req)); + pthread_t *pth = ckalloc(sizeof(pthread_t)); - *pprio = prio; - ckwq_add(sdata->ckwqs, &do_update, pprio); + ur->pth = pth; + ur->ckp = ckp; + ur->prio = prio; + create_pthread(pth, do_update, ur); } static void __kill_instance(stratum_instance_t *client) @@ -928,7 +946,7 @@ static void drop_allclients(ckpool_t *ckp) client->dropped = true; kills++; sprintf(buf, "dropclient=%"PRId64, client_id); - async_send_proc(ckp, ckp->connector, buf); + send_proc(ckp->connector, buf); } HASH_ITER(hh, sdata->disconnected_instances, client, tmp) { disconnects++; @@ -1633,21 +1651,6 @@ static void ckmsgq_stats(ckmsgq_t *ckmsgq, const int size, json_t **val) JSON_CPACK(*val, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); } -static void ckwq_stats(ckwq_t *ckwq, const int size, json_t **val) -{ - int objects, generated; - int64_t memsize; - ckwqmsg_t *wqmsg; - - mutex_lock(ckwq->lock); - DL_COUNT(ckwq->wqmsgs, wqmsg, objects); - generated = ckwq->messages; - mutex_unlock(ckwq->lock); - - memsize = (sizeof(ckwqmsg_t) + size) * objects; - JSON_CPACK(*val, "{si,si,si}", "count", objects, "memory", memsize, "generated", generated); -} - static char *stratifier_stats(ckpool_t *ckp, sdata_t *sdata) { json_t *val = json_object(), *subval; @@ -1694,14 +1697,15 @@ static char *stratifier_stats(ckpool_t *ckp, sdata_t *sdata) ckmsgq_stats(sdata->ssends, sizeof(smsg_t), &subval); json_set_object(val, "ssends", subval); - /* Don't know exactly how big the string is so just count the pointer for now */ - ckwq_stats(sdata->ckwqs, sizeof(char *) + sizeof(void *), &subval); - json_set_object(val, "ckwqs", subval); + ckmsgq_stats(sdata->srecvs, sizeof(char *), &subval); + json_set_object(val, "srecvs", subval); if (!CKP_STANDALONE(ckp)) { ckmsgq_stats(sdata->ckdbq, sizeof(char *), &subval); json_set_object(val, "ckdbq", subval); } + ckmsgq_stats(sdata->stxnq, sizeof(json_params_t), &subval); + json_set_object(val, "stxnq", subval); buf = json_dumps(val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER); json_decref(val); @@ -1709,8 +1713,6 @@ static char *stratifier_stats(ckpool_t *ckp, sdata_t *sdata) return buf; } -static void srecv_process(ckpool_t *ckp, char *buf); - static int stratum_loop(ckpool_t *ckp, proc_instance_t *pi) { int sockd, ret = 0, selret = 0; @@ -1765,7 +1767,7 @@ retry: /* The bulk of the messages will be received json from the * connector so look for this first. The srecv_process frees * the buf heap ram */ - ckwq_add(sdata->ckwqs, &srecv_process, buf); + ckmsgq_add(sdata->srecvs, buf); Close(sockd); buf = NULL; goto retry; @@ -3287,14 +3289,10 @@ static void suggest_diff(stratum_instance_t *client, const char *method, const j stratum_send_diff(sdata, client); } -static void sshare_process(ckpool_t *ckp, json_params_t *jp); -static void send_transactions(ckpool_t *ckp, json_params_t *jp); - /* Enter with client holding ref count */ static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64_t client_id, json_t *id_val, json_t *method_val, json_t *params_val, const char *address) { - ckpool_t *ckp = client->ckp; const char *method; char buf[256]; @@ -3305,7 +3303,7 @@ static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64 if (likely(cmdmatch(method, "mining.submit") && client->authorised)) { json_params_t *jp = create_json_params(client_id, method_val, params_val, id_val, address); - ckwq_add(sdata->ckwqs, &sshare_process, jp); + ckmsgq_add(sdata->sshareq, jp); return; } @@ -3340,7 +3338,7 @@ static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64 * to it since it's unauthorised. Set the flag just in case. */ client->authorised = false; snprintf(buf, 255, "passthrough=%"PRId64, client_id); - async_send_proc(ckp, client->ckp->connector, buf); + send_proc(client->ckp->connector, buf); return; } @@ -3348,7 +3346,7 @@ static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64 if (!client->subscribed) { LOGINFO("Dropping unsubscribed client %"PRId64, client_id); snprintf(buf, 255, "dropclient=%"PRId64, client_id); - async_send_proc(ckp, client->ckp->connector, buf); + send_proc(client->ckp->connector, buf); return; } @@ -3371,7 +3369,7 @@ static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64 * the stratum instance data. Clients will just reconnect. */ LOGINFO("Dropping unauthorised client %"PRId64, client_id); snprintf(buf, 255, "dropclient=%"PRId64, client_id); - async_send_proc(ckp, client->ckp->connector, buf); + send_proc(client->ckp->connector, buf); return; } @@ -3384,7 +3382,7 @@ static void parse_method(sdata_t *sdata, stratum_instance_t *client, const int64 if (cmdmatch(method, "mining.get")) { json_params_t *jp = create_json_params(client_id, method_val, params_val, id_val, address); - ckwq_add(sdata->ckwqs, &send_transactions, jp); + ckmsgq_add(sdata->stxnq, jp); return; } /* Unhandled message here */ @@ -3403,13 +3401,12 @@ static void parse_instance_msg(sdata_t *sdata, smsg_t *msg, stratum_instance_t * { json_t *val = msg->json_msg, *id_val, *method, *params; int64_t client_id = msg->client_id; - ckpool_t *ckp = client->ckp; char buf[256]; if (unlikely(client->reject == 2)) { LOGINFO("Dropping client %"PRId64" tagged for lazy invalidation", client_id); snprintf(buf, 255, "dropclient=%"PRId64, client_id); - async_send_proc(ckp, ckp->connector, buf); + send_proc(client->ckp->connector, buf); goto out; } @@ -3543,7 +3540,7 @@ static void ssend_process(ckpool_t *ckp, smsg_t *msg) * connector process to be delivered */ json_object_set_new_nocheck(msg->json_msg, "client_id", json_integer(msg->client_id)); s = json_dumps(msg->json_msg, 0); - async_send_proc(ckp, ckp->connector, s); + send_proc(ckp->connector, s); free(s); free_smsg(msg); } @@ -4401,10 +4398,14 @@ int stratifier(proc_instance_t *pi) mutex_init(&sdata->ckdb_lock); sdata->ssends = create_ckmsgq(ckp, "ssender", &ssend_process); - /* Create as many generic workqueue threads as there are CPUs */ - threads = sysconf(_SC_NPROCESSORS_ONLN); - ckp->ckwqs = sdata->ckwqs = create_ckwqs(ckp, "strat", threads); + /* Create half as many share processing threads as there are CPUs */ + threads = sysconf(_SC_NPROCESSORS_ONLN) / 2 ? : 1; + sdata->sshareq = create_ckmsgqs(ckp, "sprocessor", &sshare_process, threads); + /* Create 1/4 as many stratum processing threads as there are CPUs */ + threads = threads / 2 ? : 1; + sdata->srecvs = create_ckmsgqs(ckp, "sreceiver", &srecv_process, threads); sdata->sauthq = create_ckmsgq(ckp, "authoriser", &sauth_process); + sdata->stxnq = create_ckmsgq(ckp, "stxnq", &send_transactions); if (!CKP_STANDALONE(ckp)) { sdata->ckdbq = create_ckmsgq(ckp, "ckdbqueue", &ckdbq_process); create_pthread(&pth_heartbeat, ckdb_heartbeat, ckp); From aa7c479c0302267b29059013cd2aea0c7242b032 Mon Sep 17 00:00:00 2001 From: ckolivas Date: Wed, 25 Feb 2015 15:08:41 +1100 Subject: [PATCH 28/39] Cope with null message in send_proc --- src/ckpool.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ckpool.c b/src/ckpool.c index a1c4a514..1ffe45f4 100644 --- a/src/ckpool.c +++ b/src/ckpool.c @@ -528,10 +528,6 @@ void *async_send_proc(void *arg) LOGERR("Attempted to send message %s to null path in send_proc", msg ? msg : ""); goto out; } - if (unlikely(!msg || !strlen(msg))) { - LOGERR("Attempted to send null message to socket %s in send_proc", path); - goto out; - } /* At startup the pid fields are not set up before some processes are * forked so they never inherit them. */ if (unlikely(!pi->pid)) { @@ -573,9 +569,14 @@ out_nofail: * closing the socket immediately. */ void _send_proc(proc_instance_t *pi, const char *msg, const char *file, const char *func, const int line) { - struct proc_message *pm = ckalloc(sizeof(struct proc_message)); + struct proc_message *pm; pthread_t pth; + if (unlikely(!msg || !strlen(msg))) { + LOGERR("Attempted to send null message to %s in send_proc", pi->processname); + return; + } + pm = ckalloc(sizeof(struct proc_message)); pm->pi = pi; pm->msg = strdup(msg); pm->file = file; From 2035db91b62bbda72aafbab4b65500da851eaa1b Mon Sep 17 00:00:00 2001 From: kanoi Date: Thu, 26 Feb 2015 16:07:29 +1100 Subject: [PATCH 29/39] ckdb/php/sql - db storage of payout details and calculations --- pool/page_pplns2.php | 327 +++++++++++++ pool/prime.php | 1 + sql/ckdb.sql | 51 +- sql/initid.sh | 1 + sql/v0.9.6-v1.0.0.sql | 101 ++++ src/ckdb.c | 78 +++- src/ckdb.h | 125 ++++- src/ckdb_cmd.c | 366 +++++++++++---- src/ckdb_data.c | 1040 ++++++++++++++++++++++++++++++++++++++++- src/ckdb_dbio.c | 757 +++++++++++++++++++++++++----- 10 files changed, 2593 insertions(+), 254 deletions(-) create mode 100644 pool/page_pplns2.php create mode 100644 sql/v0.9.6-v1.0.0.sql diff --git a/pool/page_pplns2.php b/pool/page_pplns2.php new file mode 100644 index 00000000..01c4c4ca --- /dev/null +++ b/pool/page_pplns2.php @@ -0,0 +1,327 @@ + 99999999) + $b4 = ''; + else if ($num > 9999999) + $b4 = ''; + if ($b4 != '') + $af = ''; + return $b4.$fmt.$af; +} +# +# ... Of course ... check the output and add the txin ... etc. +function calctx($ans, $count, $miner_sat, $diffacc_total) +{ + $pg = '
'; + $pg .= '
'; + + $dust = getparam('dust', true); + if (nuem($dust) || $dust <= 0) + $dust = 10000; + + $fee = getparam('fee', true); + if (nuem($fee) || $fee < 0) + $fee = 0; + $fee *= 100000000; + + $adr = array(); + $ers = ''; + $unpaid = 0; + $change = $miner_sat; + $dust_amt = 0; # not included in $change + for ($i = 0; $i < $count; $i++) + { + $username = $ans['user:'.$i]; + $diffacc_user = $ans['diffacc:'.$i]; + $pay_sat = $ans['amount:'.$i]; + $payaddress = $ans['payaddress:'.$i]; + if ($payaddress == 'none') + { + $c0 = substr($username, 0, 1); + $parts = explode('.', $username); + $len = strlen($parts[0]); + if (($c0 == '1' || $c0 == '3') && $len > 26 && $len < 37) + $payaddress = $parts[0]; + else + { + if ($pay_sat > 0) + { + $dd = ''; + if ($pay_sat < $dust) + $dd = ' (dust)'; + $ers .= "No address for '$username'$dd
"; + } + $unpaid += $pay_sat; + continue; + } + } + if (isset($adr[$payaddress])) + $adr[$payaddress] += $pay_sat; + else + $adr[$payaddress] = $pay_sat; + + $change -= $pay_sat; + } + + $txout = ''; + $comma = ''; + foreach ($adr as $payaddress => $pay_sat) + { + if ($pay_sat < $dust) + $dust_amt += $pay_sat; + else + { + $txout .= "$comma\"$payaddress\":".btcfmt($pay_sat); + $comma = ', '; + } + } + + if ($change > 0 || $dust_amt > 0 || $change < $fee) + { + $pg .= "Dust limit = $dust = ".btcfmt($dust); + $pg .= ", Dust amount = $dust_amt = ".btcfmt($dust_amt); + $pg .= ",
Upaid = $unpaid = ".btcfmt($unpaid); + $pg .= ", Change = $change = ".btcfmt($change); + $pg .= ",
Fee = $fee = ".btcfmt($fee)."

"; + + if ($change < $fee) + $ers .= "Change ($change) is less than Fee ($fee)
"; + + if (($dust_amt + $change - $fee) > 0) + { + $txout .= "$comma\"<changeaddress>\":"; + $txout .= btcfmt($dust_amt + $change - $fee); + $comma = ', '; + } + } + + if (strlen($ers) > 0) + $pg .= "$ers
"; + + $txn = '[{"txid":"<txid1>","vout":<n>},'; + $txn .= '{"txid":"<txid2>","vout":<n>}] '; + $txn .= '{'.$txout.'}
'; + + $pg .= $txn.'
'; + return $pg; +} +# +function fmtdata($code, $val) +{ + switch ($code) + { + case ',': + $ret = number_format($val); + break; + case '.': + $ret = number_format($val, 1); + break; + default: + $ret = $val; + } + return $ret; +} +# +function dopplns2($data, $user) +{ + global $send_sep; + + $pg = '

CKPool

'; + + $blk = getparam('blk', true); + if (nuem($blk)) + { + $tx = ''; + # so can make a link + $blkuse = getparam('blkuse', true); + if (nuem($blkuse)) + $blkuse = ''; + else + $tx = 'y'; + $pg = '
'.makeForm('pplns2')." +Block: +  Tx: +  Dust (Satoshi): +  Fee (BTC): +"; + } + else + { + $tx = getparam('tx', true); + if (nuem($tx) || substr($tx, 0, 1) != 'y') + $dotx = false; + else + $dotx = true; + + $flds = array('height' => $blk); + $msg = msgEncode('pplns2', 'pplns2', $flds, $user); + $rep = sendsockreply('pplns2', $msg, 4); + if ($rep == false) + $ans = array(); + else + $ans = repDecode($rep); + + + if ($ans['ERROR'] != null) + return '
'.$ans['STATUS'].': '.$ans['ERROR'].'
'; + + if (!isset($ans['pplns_last'])) + return '
Partial data returned
'; + + $reward_sat = $ans['block_reward']; + $miner_sat = $ans['miner_reward']; + $ans['miner_sat'] = $miner_sat; + + $data = array( 'Block' => 'block', + 'Block Status' => 'block_status', + 'Block Hash' => 'block_hash', + 'Block Reward (Satoshis)' => 'block_reward', + 'Miner Reward (Satoshis)' => 'miner_sat', + 'PPLNS Wanted' => '.diff_want', + 'PPLNS Used' => '.diffacc_total', + 'Elapsed Seconds' => ',pplns_elapsed', + 'Users' => 'rows', + 'Oldest Workinfoid' => 'begin_workinfoid', +# 'Oldest Time' => 'begin_stamp', +# 'Oldest Epoch' => 'begin_epoch', + 'Block Workinfoid' => 'block_workinfoid', +# 'Block Time' => 'block_stamp', +# 'Block Epoch' => 'block_epoch', + 'Newest Workinfoid' => 'end_workinfoid', +# 'Newest Share Time' => 'end_stamp', +# 'Newest Share Epoch' => 'end_epoch', + 'Network Difficulty' => 'block_ndiff', + 'PPLNS Factor' => 'diff_times', + 'PPLNS Added' => 'diff_add', + 'Accepted Share Count' => ',acc_share_count', + 'Total Share Count' => ',total_share_count', + 'ShareSummary Count' => ',ss_count', + 'WorkMarkers Count' => ',wm_count', + 'MarkerSummary Count' => ',ms_count'); + + $pg = '
Blockchain '.$ans['block']."
\n"; + + if (strlen($ans['marks_status']) > 0) + { + $pg .= '
'; + $msg = $ans['marks_status']; + $pg .= str_replace(' ', ' ', $msg)."
\n"; + } + + if (strlen($ans['block_extra']) > 0) + { + $pg .= '
'; + $msg = $ans['block_status'].' - '.$ans['block_extra']; + $pg .= str_replace(' ', ' ', $msg)."
\n"; + } + + if (strlen($ans['share_status']) > 0) + { + $pg .= '
'; + $msg = $ans['share_status']." - Can't be paid out yet"; + $pg .= str_replace(' ', ' ', $msg)."
\n"; + } + + $pg .= "
\n"; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= "\n"; + $i = 0; + foreach ($data as $dsp => $name) + { + if (($i++ % 2) == 0) + $row = 'even'; + else + $row = 'odd'; + + $pg .= ""; + $pg .= ""; + switch ($name[0]) + { + case ',': + case '.': + $nm = substr($name, 1); + $fmt = fmtdata($name[0], $ans[$nm]); + break; + default: + $fmt = $ans[$name]; + break; + } + $pg .= ""; + $pg .= "\n"; + } + + $pg .= "
NameValue
$dsp$fmt

\n"; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= "\n"; + + $diffacc_total = $ans['diffacc_total']; + if ($diffacc_total == 0) + $diffacc_total = pow(10,15); + $elapsed = $ans['pplns_elapsed']; + $count = $ans['rows']; + $tot_pay = 0; + for ($i = 0; $i < $count; $i++) + { + $diffacc_user = $ans['diffacc:'.$i]; + $diffacc_percent = number_format(100.0 * $diffacc_user / $diffacc_total, 3).'%'; + $avg_hash = number_format($diffacc_user / $elapsed * pow(2,32), 0); + $pay_sat = $ans['amount:'.$i]; + $payaddress = $ans['payaddress:'.$i]; + + if (($i % 2) == 0) + $row = 'even'; + else + $row = 'odd'; + + $pg .= ""; + $pg .= ''; + $pg .= ""; + $pg .= ""; + $pg .= ""; + $pg .= ''; + $pg .= ""; + $pg .= "\n"; + + $tot_pay += $pay_sat; + } + if (($i % 2) == 0) + $row = 'even'; + else + $row = 'odd'; + + $pg .= ""; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= ''; + $pg .= "\n"; + $pg .= "
UserDiff Accepted%Avg HashrateBTC -0.9%Address
'.$ans['user:'.$i].'$diffacc_user$diffacc_percent$avg_hash'.btcfmt($pay_sat).'$payaddress
'.btcfmt($tot_pay).'
\n"; + + if ($dotx === true) + $pg .= calctx($ans, $count, $miner_sat, $diffacc_total); + } + + return $pg; +} +# +function show_pplns2($info, $page, $menu, $name, $user) +{ + gopage($info, NULL, 'dopplns2', $page, $menu, $name, $user); +} +# +?> diff --git a/pool/prime.php b/pool/prime.php index ebecb23e..0cfe79af 100644 --- a/pool/prime.php +++ b/pool/prime.php @@ -19,6 +19,7 @@ function process($p, $user, $menu) if ($user == 'Kano' || $user == 'ckolivas') { $menu['Admin']['ckp'] = 'ckp'; + $menu['Admin']['PPLNS2'] = 'pplns2'; $menu['Admin']['PPLNS'] = 'pplns'; $menu['Admin']['AllWork'] = 'allwork'; } diff --git a/sql/ckdb.sql b/sql/ckdb.sql index 441fbc90..a14896e9 100644 --- a/sql/ckdb.sql +++ b/sql/ckdb.sql @@ -84,11 +84,14 @@ CREATE UNIQUE INDEX payadduserid ON paymentaddresses USING btree (userid, payadd CREATE TABLE payments ( paymentid bigint NOT NULL, -- unique per record + payoutid bigint NOT NULL, userid bigint NOT NULL, + subname character varying(256) NOT NULL, paydate timestamp with time zone NOT NULL, payaddress character varying(256) DEFAULT ''::character varying NOT NULL, originaltxn character varying(256) DEFAULT ''::character varying NOT NULL, amount bigint NOT NULL, -- satoshis + diffacc float DEFAULT 0 NOT NULL, committxn character varying(256) DEFAULT ''::character varying NOT NULL, commitblockhash character varying(256) DEFAULT ''::character varying NOT NULL, createdate timestamp with time zone NOT NULL, @@ -98,10 +101,10 @@ CREATE TABLE payments ( expirydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', PRIMARY KEY (paymentid, expirydate) ); -CREATE UNIQUE INDEX payuserid ON payments USING btree (userid, payaddress, originaltxn, expirydate); +CREATE UNIQUE INDEX payuserid ON payments USING btree (payoutid, userid, subname, payaddress, originaltxn, expirydate); -CREATE TABLE accountbalance ( -- summarised from miningpayouts and payments +CREATE TABLE accountbalance ( -- summarised from miningpayouts and payments - RAM only userid bigint NOT NULL, confirmedpaid bigint DEFAULT 0 NOT NULL, -- satoshis confirmedunpaid bigint DEFAULT 0 NOT NULL, -- satoshis @@ -111,8 +114,11 @@ CREATE TABLE accountbalance ( -- summarised from miningpayouts and payments createby character varying(64) DEFAULT ''::character varying NOT NULL, createcode character varying(128) DEFAULT ''::character varying NOT NULL, createinet character varying(128) DEFAULT ''::character varying NOT NULL, - expirydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', - PRIMARY KEY (userid, expirydate) + modifydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', + modifyby character varying(64) DEFAULT ''::character varying NOT NULL, + modifycode character varying(128) DEFAULT ''::character varying NOT NULL, + modifyinet character varying(128) DEFAULT ''::character varying NOT NULL, + PRIMARY KEY (userid) ); @@ -120,6 +126,7 @@ CREATE TABLE accountadjustment ( -- manual corrections userid bigint NOT NULL, authority character varying(256) NOT NULL, reason text NOT NULL, + message character varying(256) NOT NULL, amount bigint DEFAULT 0 NOT NULL, -- satoshis createdate timestamp with time zone NOT NULL, createby character varying(64) DEFAULT ''::character varying NOT NULL, @@ -341,22 +348,42 @@ CREATE TABLE blocks ( ); --- calculation for the given block - orphans will be here also (not deleted later) --- rules for orphans/next block will be pool dependent CREATE TABLE miningpayouts ( - miningpayoutid bigint NOT NULL, -- unique per record + payoutid bigint NOT NULL, userid bigint NOT NULL, + diffacc float DEFAULT 0 NOT NULL, + amount bigint DEFAULT 0 NOT NULL, -- satoshis + createdate timestamp with time zone NOT NULL, + createby character varying(64) DEFAULT ''::character varying NOT NULL, + createcode character varying(128) DEFAULT ''::character varying NOT NULL, + createinet character varying(128) DEFAULT ''::character varying NOT NULL, + expirydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', + PRIMARY KEY (payoutid, userid, expirydate) +); + + +CREATE TABLE payouts ( + payoutid bigint NOT NULL, -- unique per record height integer not NULL, - blockhash character varying(256) DEFAULT ''::character varying NOT NULL, - amount bigint DEFAULT 0 NOT NULL, + blockhash character varying(256) NOT NULL, + minerreward bigint NOT NULL, -- satoshis + workinfoidstart bigint NOT NULL, + workinfoidend bigint NOT NULL, -- should be block workinfoid + elapsed bigint NOT NULL, + status char DEFAULT ' ' NOT NULL, + diffwanted float DEFAULT 0 NOT NULL, + diffused float DEFAULT 0 NOT NULL, + shareacc float DEFAULT 0 NOT NULL, + lastshareacc timestamp with time zone NOT NULL, + stats text DEFAULT ''::text NOT NULL, createdate timestamp with time zone NOT NULL, createby character varying(64) DEFAULT ''::character varying NOT NULL, createcode character varying(128) DEFAULT ''::character varying NOT NULL, createinet character varying(128) DEFAULT ''::character varying NOT NULL, expirydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', - PRIMARY KEY (miningpayoutid, expirydate) + PRIMARY KEY (payoutid, expirydate) ); -CREATE UNIQUE INDEX minpayuserid ON miningpayouts USING btree (userid, blockhash, expirydate); +CREATE UNIQUE INDEX payoutsblock ON payouts USING btree (height, blockhash, expirydate); CREATE TABLE eventlog ( @@ -433,4 +460,4 @@ CREATE TABLE version ( PRIMARY KEY (vlock) ); -insert into version (vlock,version) values (1,'0.9.6'); +insert into version (vlock,version) values (1,'1.0.0'); diff --git a/sql/initid.sh b/sql/initid.sh index 31c119d9..278559a8 100755 --- a/sql/initid.sh +++ b/sql/initid.sh @@ -20,3 +20,4 @@ addid authid ${now}300000 addid userid ${now}400000 addid markerid ${now}500000 addid paymentaddressid ${now}600000 +addid payoutid ${now}700000 diff --git a/sql/v0.9.6-v1.0.0.sql b/sql/v0.9.6-v1.0.0.sql new file mode 100644 index 00000000..35b780dc --- /dev/null +++ b/sql/v0.9.6-v1.0.0.sql @@ -0,0 +1,101 @@ +SET SESSION AUTHORIZATION 'postgres'; + +BEGIN transaction; + +DO $$ +DECLARE ver TEXT; +BEGIN + + UPDATE version set version='1.0.0' where vlock=1 and version='0.9.6'; + + IF found THEN + RETURN; + END IF; + + SELECT version into ver from version + WHERE vlock=1; + + RAISE EXCEPTION 'Wrong DB version - expect "0.9.6" - found "%"', ver; + +END $$; + +DROP TABLE payments; +CREATE TABLE payments ( + paymentid bigint NOT NULL, -- unique per record + payoutid bigint NOT NULL, + userid bigint NOT NULL, + subname character varying(256) NOT NULL, + paydate timestamp with time zone NOT NULL, + payaddress character varying(256) DEFAULT ''::character varying NOT NULL, + originaltxn character varying(256) DEFAULT ''::character varying NOT NULL, + amount bigint NOT NULL, -- satoshis + diffacc float DEFAULT 0 NOT NULL, + committxn character varying(256) DEFAULT ''::character varying NOT NULL, + commitblockhash character varying(256) DEFAULT ''::character varying NOT NULL, + createdate timestamp with time zone NOT NULL, + createby character varying(64) DEFAULT ''::character varying NOT NULL, + createcode character varying(128) DEFAULT ''::character varying NOT NULL, + createinet character varying(128) DEFAULT ''::character varying NOT NULL, + expirydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', + PRIMARY KEY (paymentid, expirydate) +); +CREATE UNIQUE INDEX payuserid ON payments USING btree (payoutid, userid, subname, payaddress, originaltxn, expirydate); + +DROP TABLE accountbalance; +CREATE TABLE accountbalance ( -- summarised from miningpayouts and payments - RAM only + userid bigint NOT NULL, + confirmedpaid bigint DEFAULT 0 NOT NULL, -- satoshis + confirmedunpaid bigint DEFAULT 0 NOT NULL, -- satoshis + pendingconfirm bigint DEFAULT 0 NOT NULL, -- satoshis + heightupdate integer not NULL, + createdate timestamp with time zone NOT NULL, + createby character varying(64) DEFAULT ''::character varying NOT NULL, + createcode character varying(128) DEFAULT ''::character varying NOT NULL, + createinet character varying(128) DEFAULT ''::character varying NOT NULL, + modifydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', + modifyby character varying(64) DEFAULT ''::character varying NOT NULL, + modifycode character varying(128) DEFAULT ''::character varying NOT NULL, + modifyinet character varying(128) DEFAULT ''::character varying NOT NULL, + PRIMARY KEY (userid) +); + +DROP TABLE miningpayouts; +CREATE TABLE miningpayouts ( + payoutid bigint NOT NULL, + userid bigint NOT NULL, + diffacc float DEFAULT 0 NOT NULL, + amount bigint DEFAULT 0 NOT NULL, -- satoshis + createdate timestamp with time zone NOT NULL, + createby character varying(64) DEFAULT ''::character varying NOT NULL, + createcode character varying(128) DEFAULT ''::character varying NOT NULL, + createinet character varying(128) DEFAULT ''::character varying NOT NULL, + expirydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', + PRIMARY KEY (payoutid, userid, expirydate) +); + +CREATE TABLE payouts ( + payoutid bigint NOT NULL, -- unique per record + height integer not NULL, + blockhash character varying(256) NOT NULL, + minerreward bigint NOT NULL, -- satoshis + workinfoidstart bigint NOT NULL, + workinfoidend bigint NOT NULL, -- should be block workinfoid + elapsed bigint NOT NULL, + status char DEFAULT ' ' NOT NULL, + diffwanted float DEFAULT 0 NOT NULL, + diffused float DEFAULT 0 NOT NULL, + shareacc float DEFAULT 0 NOT NULL, + lastshareacc timestamp with time zone NOT NULL, + stats text DEFAULT ''::text NOT NULL, + createdate timestamp with time zone NOT NULL, + createby character varying(64) DEFAULT ''::character varying NOT NULL, + createcode character varying(128) DEFAULT ''::character varying NOT NULL, + createinet character varying(128) DEFAULT ''::character varying NOT NULL, + expirydate timestamp with time zone DEFAULT '6666-06-06 06:06:06+00', + PRIMARY KEY (payoutid, expirydate) +); +CREATE UNIQUE INDEX payoutsblock ON payouts USING btree (height, blockhash, expirydate); + +insert into idcontrol (idname,lastid,createdate,createby) values ('payoutid',999,now(),'1.0.0update'); + +END transaction; diff --git a/src/ckdb.c b/src/ckdb.c index ca6f1e33..824aef28 100644 --- a/src/ckdb.c +++ b/src/ckdb.c @@ -27,7 +27,7 @@ * and ckpool only verifies authorise responses * Thus we can queue all messages: * workinfo, shares, shareerror, ageworkinfo, poolstats, userstats - * and block + * and blocks * with an ok.queued reply to ckpool, to be processed after the reload * completes and just process authorise messages immediately while the * reload runs @@ -61,7 +61,7 @@ * it would break the synchronisation and could cause DB problems, so * ckdb aborting and needing a complete restart resolves it * The users table, required for the authorise messages, is always updated - * immediately and is not affected by ckpool messages until we + * immediately */ /* Reload data needed @@ -92,17 +92,17 @@ * already exist * DB+RAM blocks: resolved by workinfo - any unsaved blocks (if any) * will be after the last DB workinfo - * DB+RAM accountbalance (TODO): resolved by shares/workinfo/blocks * RAM workerstatus: all except last_idle are set at the end of the * CCL reload * Code currently doesn't use last_idle + * RAM accountbalance: TODO: created as data is loaded * * idcontrol: only userid reuse is critical and the user is added * immeditately to the DB before replying to the add message * * Tables that are/will be written straight to the DB, so are OK: * users, useraccounts, paymentaddresses, payments, - * accountadjustment, optioncontrol, miningpayouts, + * accountadjustment, optioncontrol, miningpayouts, payouts, * eventlog, workmarkers, markersummary * * The code deals with the issue of 'now' when reloading by: @@ -329,6 +329,7 @@ K_STORE *workers_store; // PAYMENTADDRESSES K_TREE *paymentaddresses_root; +K_TREE *paymentaddresses_create_root; K_LIST *paymentaddresses_free; K_STORE *paymentaddresses_store; @@ -337,7 +338,6 @@ K_TREE *payments_root; K_LIST *payments_free; K_STORE *payments_store; -/* unused yet // ACCOUNTBALANCE K_TREE *accountbalance_root; K_LIST *accountbalance_free; @@ -347,7 +347,6 @@ K_STORE *accountbalance_store; K_TREE *accountadjustment_root; K_LIST *accountadjustment_free; K_STORE *accountadjustment_store; -*/ // IDCONTROL // These are only used for db access - not stored in memory @@ -405,6 +404,13 @@ K_TREE *miningpayouts_root; K_LIST *miningpayouts_free; K_STORE *miningpayouts_store; +// PAYOUTS +K_TREE *payouts_root; +K_TREE *payouts_id_root; +K_LIST *payouts_free; +K_STORE *payouts_store; +cklock_t process_pplns_lock; + /* // EVENTLOG K_TREE *eventlog_root; @@ -742,6 +748,10 @@ static bool getdata3() goto sukamudai; if (!(ok = payments_fill(conn)) || everyone_die) goto sukamudai; + if (!(ok = miningpayouts_fill(conn)) || everyone_die) + goto sukamudai; + if (!(ok = payouts_fill(conn)) || everyone_die) + goto sukamudai; } if (!(ok = workinfo_fill(conn)) || everyone_die) goto sukamudai; @@ -953,6 +963,7 @@ static void alloc_storage() LIMIT_PAYMENTADDRESSES, true); paymentaddresses_store = k_new_store(paymentaddresses_free); paymentaddresses_root = new_ktree(); + paymentaddresses_create_root = new_ktree(); paymentaddresses_free->dsp_func = dsp_paymentaddresses; payments_free = k_new_list("Payments", sizeof(PAYMENTS), @@ -960,6 +971,11 @@ static void alloc_storage() payments_store = k_new_store(payments_free); payments_root = new_ktree(); + accountbalance_free = k_new_list("AccountBalance", sizeof(ACCOUNTBALANCE), + ALLOC_ACCOUNTBALANCE, LIMIT_ACCOUNTBALANCE, true); + accountbalance_store = k_new_store(accountbalance_free); + accountbalance_root = new_ktree(); + idcontrol_free = k_new_list("IDControl", sizeof(IDCONTROL), ALLOC_IDCONTROL, LIMIT_IDCONTROL, true); idcontrol_store = k_new_store(idcontrol_free); @@ -999,6 +1015,12 @@ static void alloc_storage() miningpayouts_store = k_new_store(miningpayouts_free); miningpayouts_root = new_ktree(); + payouts_free = k_new_list("Payouts", sizeof(PAYOUTS), + ALLOC_PAYOUTS, LIMIT_PAYOUTS, true); + payouts_store = k_new_store(payouts_free); + payouts_root = new_ktree(); + payouts_id_root = new_ktree(); + auths_free = k_new_list("Auths", sizeof(AUTHS), ALLOC_AUTHS, LIMIT_AUTHS, true); auths_store = k_new_store(auths_free); @@ -1110,6 +1132,10 @@ static void dealloc_storage() FREE_ALL(poolstats); FREE_ALL(auths); + + FREE_TREE(payouts_id); + FREE_ALL(payouts); + FREE_ALL(miningpayouts); FREE_ALL(blocks); @@ -1131,7 +1157,10 @@ static void dealloc_storage() FREE_LIST_DATA(workinfo); FREE_LISTS(idcontrol); + FREE_ALL(accountbalance); FREE_ALL(payments); + + FREE_TREE(paymentaddresses_create); FREE_ALL(paymentaddresses); FREE_ALL(workers); @@ -1534,6 +1563,20 @@ static void check_blocks() btc_blockstatus(blocks); } +static void pplns_block(BLOCKS *blocks) +{ + if (sharesummary_marks_limit) { + LOGEMERG("%s() sharesummary marks limit, block %"PRId32" payout skipped", + __func__, blocks->height); + return; + } + + // Give it a sec after the block summarisation + sleep(1); + + process_pplns(blocks->height, blocks->blockhash, NULL); +} + static void summarise_blocks() { K_ITEM *b_item, *b_prev, *wi_item, ss_look, *ss_item; @@ -1733,17 +1776,15 @@ static void summarise_blocks() "%0.f/%.0f/%.0f/%.0f/%"PRId64, __func__, blocks->height, diffacc, diffinv, shareacc, shareinv, elapsed); + + // Now the summarisation is confirmed, generate the payout data + pplns_block(blocks); } else { LOGERR("%s() block %d, failed to confirm stats", __func__, blocks->height); } } -static void summarise_poolstats() -{ -// TODO -} - static void *summariser(__maybe_unused void *arg) { int i; @@ -1752,7 +1793,7 @@ static void *summariser(__maybe_unused void *arg) rename_proc("db_summariser"); - while (!everyone_die && !db_load_complete) + while (!everyone_die && !startup_complete) cksleep_ms(42); summariser_using_data = true; @@ -1764,12 +1805,8 @@ static void *summariser(__maybe_unused void *arg) } if (everyone_die) break; - else { - if (startup_complete) - check_blocks(); - if (!everyone_die) - summarise_blocks(); - } + else + check_blocks(); for (i = 0; i < 4; i++) { if (!everyone_die) @@ -1778,7 +1815,7 @@ static void *summariser(__maybe_unused void *arg) if (everyone_die) break; else - summarise_poolstats(); + summarise_blocks(); for (i = 0; i < 4; i++) { if (!everyone_die) @@ -2624,6 +2661,7 @@ static void *socketer(__maybe_unused void *arg) case CMD_WORKERS: case CMD_PAYMENTS: case CMD_PPLNS: + case CMD_PPLNS2: case CMD_DSP: case CMD_BLOCKSTATUS: if (!startup_complete) { @@ -2841,6 +2879,7 @@ static bool reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) case CMD_DSP: case CMD_STATS: case CMD_PPLNS: + case CMD_PPLNS2: case CMD_USERSTATUS: case CMD_MARKS: LOGERR("%s() Message line %"PRIu64" '%s' - invalid - ignored", @@ -4062,6 +4101,7 @@ int main(int argc, char **argv) ckp.main.processname = strdup("main"); cklock_init(&last_lock); + cklock_init(&process_pplns_lock); if (confirm_sharesummary) { // TODO: add a system lock to stop running 2 at once? diff --git a/src/ckdb.h b/src/ckdb.h index e8ecd940..9a48d42a 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -51,8 +51,8 @@ */ #define DB_VLOCK "1" -#define DB_VERSION "0.9.6" -#define CKDB_VERSION DB_VERSION"-0.920" +#define DB_VERSION "1.0.0" +#define CKDB_VERSION DB_VERSION"-1.000" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ @@ -94,6 +94,9 @@ extern const char *addrpatt; // BTC address size #define ADDR_MIN_LEN 26 #define ADDR_MAX_LEN 34 +/* All characters in a payaddress are less than this + * thus setting the 1st char to this will be greater than any payaddress */ +#define MAX_PAYADDR '~' typedef struct loadstatus { tv_t oldest_sharesummary_firstshare_n; @@ -336,6 +339,8 @@ enum cmd_values { CMD_DSP, CMD_STATS, CMD_PPLNS, + CMD_PPLNS2, + CMD_PAYOUT, CMD_USERSTATUS, CMD_MARKS, CMD_END @@ -816,6 +821,7 @@ typedef struct paymentaddresses { #define DATA_PAYMENTADDRESSES_NULL(_var, _item) DATA_GENERIC(_var, _item, paymentaddresses, false) extern K_TREE *paymentaddresses_root; +extern K_TREE *paymentaddresses_create_root; extern K_LIST *paymentaddresses_free; extern K_STORE *paymentaddresses_store; @@ -824,14 +830,18 @@ extern K_STORE *paymentaddresses_store; // PAYMENTS typedef struct payments { int64_t paymentid; + int64_t payoutid; int64_t userid; + char subname[TXT_BIG+1]; tv_t paydate; char payaddress[TXT_BIG+1]; char originaltxn[TXT_BIG+1]; int64_t amount; + double diffacc; char committxn[TXT_BIG+1]; char commitblockhash[TXT_BIG+1]; HISTORYDATECONTROLFIELDS; + K_ITEM *old_item; // Non-db field } PAYMENTS; #define ALLOC_PAYMENTS 1024 @@ -844,7 +854,6 @@ extern K_TREE *payments_root; extern K_LIST *payments_free; extern K_STORE *payments_store; -/* unused yet // ACCOUNTBALANCE typedef struct accountbalance { int64_t userid; @@ -852,7 +861,7 @@ typedef struct accountbalance { int64_t confirmedunpaid; int64_t pendingconfirm; int32_t heightupdate; - HISTORYDATECONTROLFIELDS; + MODIFYDATECONTROLFIELDS; } ACCOUNTBALANCE; #define ALLOC_ACCOUNTBALANCE 1024 @@ -881,7 +890,6 @@ typedef struct accountadjustment { extern K_TREE *accountadjustment_root; extern K_LIST *accountadjustment_free; extern K_STORE *accountadjustment_store; -*/ // IDCONTROL typedef struct idcontrol { @@ -1126,23 +1134,62 @@ extern K_STORE *blocks_store; // MININGPAYOUTS typedef struct miningpayouts { - int64_t miningpayoutid; + int64_t payoutid; int64_t userid; - int32_t height; - char blockhash[TXT_BIG+1]; + double diffacc; int64_t amount; HISTORYDATECONTROLFIELDS; + K_ITEM *old_item; // Non-db field } MININGPAYOUTS; #define ALLOC_MININGPAYOUTS 1000 #define LIMIT_MININGPAYOUTS 0 #define INIT_MININGPAYOUTS(_item) INIT_GENERIC(_item, miningpayouts) #define DATA_MININGPAYOUTS(_var, _item) DATA_GENERIC(_var, _item, miningpayouts, true) +#define DATA_MININGPAYOUTS_NULL(_var, _item) DATA_GENERIC(_var, _item, miningpayouts, false) extern K_TREE *miningpayouts_root; extern K_LIST *miningpayouts_free; extern K_STORE *miningpayouts_store; +// PAYOUTS +typedef struct payouts { + int64_t payoutid; + int32_t height; + char blockhash[TXT_BIG+1]; + int64_t minerreward; + int64_t workinfoidstart; + int64_t workinfoidend; + int64_t elapsed; + char status[TXT_FLAG+1]; + double diffwanted; + double diffused; + double shareacc; + tv_t lastshareacc; + char *stats; + HISTORYDATECONTROLFIELDS; +} PAYOUTS; + +#define ALLOC_PAYOUTS 1000 +#define LIMIT_PAYOUTS 0 +#define INIT_PAYOUTS(_item) INIT_GENERIC(_item, payouts) +#define DATA_PAYOUTS(_var, _item) DATA_GENERIC(_var, _item, payouts, true) +#define DATA_PAYOUTS_NULL(_var, _item) DATA_GENERIC(_var, _item, payouts, false) + +extern K_TREE *payouts_root; +extern K_TREE *payouts_id_root; +extern K_LIST *payouts_free; +extern K_STORE *payouts_store; +extern cklock_t process_pplns_lock; + +#define PAYOUTS_GENERATED 'G' +#define PAYOUTS_GENERATED_STR "G" +#define PAYGENERATED(_status) ((_status)[0] == PAYOUTS_GENERATED) +// A processing payout must be ignored +#define PAYOUTS_PROCESSING 'P' +#define PAYOUTS_PROCESSING_STR "P" +#define PAYPROCESSING(_status) ((_status)[0] == PAYOUTS_PROCESSING) + /* // EVENTLOG typedef struct eventlog { @@ -1527,6 +1574,20 @@ extern PGconn *dbconnect(); // *** ckdb_data.c *** // *** +/* Blocks after 334106 were set to 5xN + * however, they cannot count back to include the workinfoid of 333809 + * due to the markersummaries that were created. + * Code checks that if the block is after FIVExSTT then it must stop + * counting back shares at - and not include - FIVExWID */ +#define FIVExSTT 334106 +#define FIVExLIM 333809 +// 333809 workinfoid +#define FIVExWID 6085620100361140756 + +// optioncontrol names for PPLNS N diff calculation +#define PPLNSDIFFTIMES "pplns_diff_times" +#define PPLNSDIFFADD "pplns_diff_add" + // Data free functions (first) extern void free_workinfo_data(K_ITEM *item); extern void free_sharesummary_data(K_ITEM *item); @@ -1635,12 +1696,19 @@ extern K_ITEM *new_default_worker(PGconn *conn, bool update, int64_t userid, cha char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root); extern void dsp_paymentaddresses(K_ITEM *item, FILE *stream); extern cmp_t cmp_paymentaddresses(K_ITEM *a, K_ITEM *b); +extern cmp_t cmp_payaddr_create(K_ITEM *a, K_ITEM *b); extern K_ITEM *find_paymentaddresses(int64_t userid, K_TREE_CTX *ctx); +extern K_ITEM *find_paymentaddresses_create(int64_t userid, K_TREE_CTX *ctx); extern K_ITEM *find_one_payaddress(int64_t userid, char *payaddress, K_TREE_CTX *ctx); extern K_ITEM *find_any_payaddress(char *payaddress); extern cmp_t cmp_payments(K_ITEM *a, K_ITEM *b); +extern K_ITEM *find_payments(int64_t payoutid, int64_t userid, char *subname); +extern K_ITEM *find_first_payments(int64_t userid, K_TREE_CTX *ctx); +extern K_ITEM *find_first_paypayid(int64_t userid, int64_t payoutid, K_TREE_CTX *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); +extern K_ITEM *find_optioncontrol(char *optionname, tv_t *now, int32_t height); extern cmp_t cmp_workinfo(K_ITEM *a, K_ITEM *b); #define coinbase1height(_cb1) _coinbase1height(_cb1, WHERE_FFL_HERE) extern int32_t _coinbase1height(char *coinbase1, WHERE_FFL_ARGS); @@ -1670,12 +1738,23 @@ void _dbhash2btchash(char *hash, char *buf, size_t siz, WHERE_FFL_ARGS); extern void _dsp_hash(char *hash, char *buf, size_t siz, 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); +extern K_ITEM *find_blocks(int32_t height, char *blockhash, K_TREE_CTX *ctx); extern K_ITEM *find_prev_blocks(int32_t height); extern const char *blocks_confirmed(char *confirmed); extern void zero_on_new_block(); extern void set_block_share_counters(); extern cmp_t cmp_miningpayouts(K_ITEM *a, K_ITEM *b); +extern K_ITEM *find_miningpayouts(int64_t payoutid, int64_t userid); +extern K_ITEM *first_miningpayouts(int64_t payoutid, K_TREE_CTX *ctx); +extern cmp_t cmp_mu(K_ITEM *a, K_ITEM *b); +extern K_TREE *upd_add_mu(K_TREE *mu_root, K_STORE *mu_store, int64_t userid, + double diffacc); +extern cmp_t cmp_payouts(K_ITEM *a, K_ITEM *b); +extern cmp_t cmp_payouts_id(K_ITEM *a, K_ITEM *b); +extern K_ITEM *find_payouts(int32_t height, char *blockhash); +extern K_ITEM *find_last_payouts(); +extern K_ITEM *find_payoutid(int64_t payoutid); +extern void process_pplns(int32_t height, char *blockhash, tv_t *now); extern cmp_t cmp_auths(K_ITEM *a, K_ITEM *b); extern cmp_t cmp_poolstats(K_ITEM *a, K_ITEM *b); extern void dsp_userstats(K_ITEM *item, FILE *stream); @@ -1751,6 +1830,12 @@ extern PGresult *_CKPQexecParams(PGconn *conn, const char *qry, #define PGLOGEMERG(_str, _rescode, _conn) PGLOG(LOGEMERG, _str, _rescode, _conn) extern char *pqerrmsg(PGconn *conn); +extern bool CKPQConn(PGconn **conn); +extern void CKPQDisco(PGconn **conn, bool conned); +extern bool _CKPQBegin(PGconn *conn, WHERE_FFL_ARGS); +#define CKPQBegin(_conn) _CKPQBegin(conn, WHERE_FFL_HERE) +extern void _CKPQEnd(PGconn *conn, bool commit, WHERE_FFL_ARGS); +#define CKPQEnd(_conn, _commit) _CKPQEnd(_conn, _commit, WHERE_FFL_HERE) extern int64_t nextid(PGconn *conn, char *idname, int64_t increment, tv_t *cd, char *by, char *code, char *inet); @@ -1783,6 +1868,11 @@ extern bool paymentaddresses_set(PGconn *conn, int64_t userid, K_LIST *pa_store, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root); extern bool paymentaddresses_fill(PGconn *conn); +extern void payments_add_ram(bool ok, K_ITEM *mp_item, K_ITEM *old_mp_item, + tv_t *cd); +extern bool payments_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 bool payments_fill(PGconn *conn); extern bool idcontrol_add(PGconn *conn, char *idname, char *idvalue, char *by, char *code, char *inet, tv_t *cd, K_TREE *trf_root); @@ -1827,10 +1917,19 @@ extern bool blocks_add(PGconn *conn, char *height, char *blockhash, char *by, char *code, char *inet, tv_t *cd, bool igndup, char *id, K_TREE *trf_root); extern bool blocks_fill(PGconn *conn); -extern bool miningpayouts_add(PGconn *conn, char *username, char *height, - char *blockhash, char *amount, char *by, - char *code, char *inet, tv_t *cd, K_TREE *trf_root); +extern void miningpayouts_add_ram(bool ok, K_ITEM *mp_item, K_ITEM *old_mp_item, + tv_t *cd); +extern bool miningpayouts_add(PGconn *conn, bool add, K_ITEM *mp_item, + K_ITEM **old_mp_item, char *by, char *code, + char *inet, tv_t *cd, K_TREE *trf_root, + bool already); extern bool miningpayouts_fill(PGconn *conn); +extern void payouts_add_ram(bool ok, K_ITEM *p_item, K_ITEM *old_p_item, + tv_t *cd); +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 bool payouts_fill(PGconn *conn); extern bool auths_add(PGconn *conn, char *poolinstance, char *username, char *workername, char *clientid, char *enonce1, char *useragent, char *preauth, char *by, char *code, diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index cdfb2e0b..5d1bcba0 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -971,7 +971,7 @@ static char *cmd_blockstatus(__maybe_unused PGconn *conn, char *cmd, char *id, action = transfer_data(i_action); K_RLOCK(blocks_free); - b_item = find_blocks(height, transfer_data(i_blockhash)); + b_item = find_blocks(height, transfer_data(i_blockhash), NULL); K_RUNLOCK(blocks_free); if (!b_item) { @@ -1092,9 +1092,9 @@ static char *cmd_payments(__maybe_unused PGconn *conn, char *cmd, char *id, __maybe_unused tv_t *notcd, __maybe_unused K_TREE *trf_root) { - K_ITEM *i_username, look, *u_item, *p_item; + K_ITEM *i_username, *u_item, *p_item; K_TREE_CTX ctx[1]; - PAYMENTS lookpayments, *payments; + PAYMENTS *payments, curr; USERS *users; char reply[1024] = ""; char tmp[1024]; @@ -1116,33 +1116,62 @@ static char *cmd_payments(__maybe_unused PGconn *conn, char *cmd, char *id, return strdup("bad"); DATA_USERS(users, u_item); - lookpayments.userid = users->userid; - lookpayments.paydate.tv_sec = 0; - lookpayments.paydate.tv_usec = 0; - INIT_PAYMENTS(&look); - look.data = (void *)(&lookpayments); - p_item = find_after_in_ktree(payments_root, &look, cmp_payments, ctx); - DATA_PAYMENTS_NULL(payments, p_item); + bzero(&curr, sizeof(curr)); APPEND_REALLOC_INIT(buf, off, len); APPEND_REALLOC(buf, off, len, "ok."); rows = 0; + + K_RLOCK(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) { - tv_to_buf(&(payments->paydate), reply, sizeof(reply)); + if (CURRENT(&(payments->expirydate))) { + if (curr.payoutid && curr.payoutid != payments->payoutid) { + tv_to_buf(&(curr.paydate), reply, sizeof(reply)); + snprintf(tmp, sizeof(tmp), "paydate:%d=%s%c", rows, reply, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + + str_to_buf(curr.payaddress, reply, sizeof(reply)); + snprintf(tmp, sizeof(tmp), "payaddress:%d=%s%c", rows, reply, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + + bigint_to_buf(curr.amount, reply, sizeof(reply)); + snprintf(tmp, sizeof(tmp), "amount:%d=%s%c", rows, reply, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); + + rows++; + bzero(&curr, sizeof(curr)); + } + if (!curr.payoutid) { + curr.payoutid = payments->payoutid; + copy_tv(&(curr.paydate), &(payments->paydate)); + STRNCPY(curr.payaddress, payments->payaddress); + } else + STRNCPY(curr.payaddress, "*Multiple"); + curr.amount += payments->amount; + } + p_item = next_in_ktree(ctx); + DATA_PAYMENTS_NULL(payments, p_item); + } + K_RUNLOCK(payments_free); + if (curr.payoutid) { + tv_to_buf(&(curr.paydate), reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "paydate:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); - str_to_buf(payments->payaddress, reply, sizeof(reply)); + str_to_buf(curr.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)); + bigint_to_buf(curr.amount, reply, sizeof(reply)); snprintf(tmp, sizeof(tmp), "amount:%d=%s%c", rows, reply, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); rows++; - p_item = next_in_ktree(ctx); - DATA_PAYMENTS_NULL(payments, p_item); } + snprintf(tmp, sizeof(tmp), "rows=%d%cflds=%s%c", rows, FLDSEP, "paydate,payaddress,amount", FLDSEP); @@ -2220,7 +2249,7 @@ static char *cmd_auth_do(PGconn *conn, char *cmd, char *id, char *by, i_preauth = &auth_preauth; K_RLOCK(optioncontrol_free); - oc_item = find_optioncontrol(OPTIONCONTROL_AUTOADDUSER, cd); + oc_item = find_optioncontrol(OPTIONCONTROL_AUTOADDUSER, cd, pool.height); K_RUNLOCK(optioncontrol_free); if (oc_item) { K_RLOCK(users_free); @@ -3112,7 +3141,7 @@ static char *cmd_getopts(__maybe_unused PGconn *conn, char *cmd, char *id, if (comma) *(comma++) = '\0'; K_RLOCK(optioncontrol_free); - oc_item = find_optioncontrol(ptr, now); + oc_item = find_optioncontrol(ptr, now, pool.height); K_RUNLOCK(optioncontrol_free); /* web code must check the existance of the optionname * in the reply since it will be missing if it doesn't @@ -3305,81 +3334,12 @@ rollback: return strdup(reply); } -// order by userid asc -static cmp_t cmp_mu(K_ITEM *a, K_ITEM *b) -{ - MININGPAYOUTS *ma, *mb; - DATA_MININGPAYOUTS(ma, a); - DATA_MININGPAYOUTS(mb, b); - return CMP_BIGINT(ma->userid, mb->userid); -} - -static K_TREE *upd_add_mu(K_TREE *mu_root, K_STORE *mu_store, int64_t userid, int64_t diffacc) -{ - MININGPAYOUTS lookminingpayouts, *miningpayouts; - K_ITEM look, *mu_item; - K_TREE_CTX ctx[1]; - - lookminingpayouts.userid = userid; - INIT_MININGPAYOUTS(&look); - look.data = (void *)(&lookminingpayouts); - mu_item = find_in_ktree(mu_root, &look, cmp_mu, ctx); - if (mu_item) { - DATA_MININGPAYOUTS(miningpayouts, mu_item); - miningpayouts->amount += diffacc; - } else { - K_WLOCK(mu_store); - mu_item = k_unlink_head(miningpayouts_free); - DATA_MININGPAYOUTS(miningpayouts, mu_item); - miningpayouts->userid = userid; - miningpayouts->amount = diffacc; - mu_root = add_to_ktree(mu_root, mu_item, cmp_mu); - k_add_head(mu_store, mu_item); - K_WUNLOCK(mu_store); - } - - return mu_root; -} - -/* Find the block_workinfoid of the block requested - then add all it's diffacc shares - then keep stepping back shares until diffacc_total matches or exceeds - the number required (diff_want) - this is begin_workinfoid - (also summarising diffacc per user) - then keep stepping back until we complete the current begin_workinfoid - (also summarising diffacc per user) - While we are still below diff_want - find each workmarker and add on the full set of worksummary - diffacc shares (also summarising diffacc per user) - This will give us the total number of diff1 shares (diffacc_total) - to use for the payment calculations - The value of diff_want defaults to the block's network difficulty - (block_ndiff) but can be changed with diff_times and diff_add to: - block_ndiff * diff_times + diff_add - N.B. diff_times and diff_add can be zero, positive or negative - The pplns_elapsed time of the shares is from the createdate of the - begin_workinfoid that has shares accounted to the total, - up to the createdate of the last share - The user average hashrate would be: - diffacc_user * 2^32 / pplns_elapsed - PPLNS fraction of the payout would be: - diffacc_user / diffacc_total - - N.B. 'begin' means the oldest back in time and 'end' means the newest - 'end' should usually be the info of the found block with the pplns - data going back in time to 'begin' -*/ - -/* Blocks after 334106 were set to 5xN - * however, they cannot count back to include the workinfoid of 333809 - * due to the markersummaries that were created. - * Code checks that if the block is after FIVExSTT then it must stop - * counting back shares at - and not include - FIVExWID */ -#define FIVExSTT 334106 -#define FIVExLIM 333809 -// 333809 workinfoid -#define FIVExWID 6085620100361140756 - +/* 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, @@ -3857,6 +3817,219 @@ shazbot: 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; + MININGPAYOUTS *miningpayouts; + PAYMENTS *payments; + PAYOUTS *payouts; + BLOCKS lookblocks, *blocks; + USERS *users; + int32_t height; + K_TREE_CTX b_ctx[1], mp_ctx[1], pay_ctx[1]; + 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, cmp_blocks, b_ctx); + K_RUNLOCK(blocks_free); + DATA_BLOCKS_NULL(blocks, b_item); + if (!b_item || blocks->height != height) { + snprintf(reply, siz, "ERR.no block height %"PRId32, 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: + block_extra = "Can't be paid out"; + break; + default: + block_extra = EMPTY; + break; + } + + 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); + + 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); + + snprintf(tmp, sizeof(tmp), "%s%c", payouts->stats, 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_dsp(__maybe_unused PGconn *conn, __maybe_unused char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by, __maybe_unused char *code, @@ -3884,6 +4057,9 @@ static char *cmd_dsp(__maybe_unused PGconn *conn, __maybe_unused char *cmd, 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); @@ -3944,8 +4120,9 @@ static char *cmd_stats(__maybe_unused PGconn *conn, char *cmd, char *id, USEINFO(users, 1, 2); USEINFO(useratts, 1, 1); USEINFO(workers, 1, 1); - USEINFO(paymentaddresses, 1, 1); + USEINFO(paymentaddresses, 1, 2); USEINFO(payments, 1, 1); + USEINFO(accountbalance, 1, 1); USEINFO(idcontrol, 1, 0); USEINFO(optioncontrol, 1, 1); USEINFO(workinfo, 1, 1); @@ -3957,6 +4134,7 @@ static char *cmd_stats(__maybe_unused PGconn *conn, char *cmd, char *id, USEINFO(marks, 1, 1); USEINFO(blocks, 1, 1); USEINFO(miningpayouts, 1, 1); + USEINFO(payouts, 1, 2); USEINFO(auths, 1, 1); USEINFO(poolstats, 1, 1); USEINFO(userstats, 2, 1); @@ -4511,6 +4689,8 @@ struct CMDS ckdb_cmds[] = { { CMD_DSP, "dsp", false, false, cmd_dsp, ACCESS_SYSTEM }, { CMD_STATS, "stats", true, false, cmd_stats, ACCESS_SYSTEM ACCESS_WEB }, { CMD_PPLNS, "pplns", false, false, cmd_pplns, ACCESS_SYSTEM ACCESS_WEB }, + { CMD_PPLNS2, "pplns2", false, false, cmd_pplns2, ACCESS_SYSTEM ACCESS_WEB }, +// { CMD_PAYOUT, "payout", false, false, cmd_payout, ACCESS_SYSTEM }, { CMD_USERSTATUS,"userstatus", false, false, cmd_userstatus, ACCESS_SYSTEM ACCESS_WEB }, { CMD_MARKS, "marks", false, false, cmd_marks, ACCESS_SYSTEM }, { CMD_END, NULL, false, false, NULL, NULL } diff --git a/src/ckdb_data.c b/src/ckdb_data.c index d8ef8b38..3d227af9 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -8,6 +8,7 @@ */ #include "ckdb.h" +#include // Data free functions (added here as needed) void free_workinfo_data(K_ITEM *item) @@ -1208,6 +1209,21 @@ cmp_t cmp_paymentaddresses(K_ITEM *a, K_ITEM *b) return c; } +// order by userid asc,createdate asc,payaddress asc +cmp_t cmp_payaddr_create(K_ITEM *a, K_ITEM *b) +{ + PAYMENTADDRESSES *pa, *pb; + DATA_PAYMENTADDRESSES(pa, a); + DATA_PAYMENTADDRESSES(pb, b); + cmp_t c = CMP_BIGINT(pa->userid, pb->userid); + if (c == 0) { + c = CMP_TV(pa->createdate, pb->createdate); + if (c == 0) + c = CMP_STR(pa->payaddress, pb->payaddress); + } + return c; +} + /* Find the last CURRENT paymentaddresses for the given userid * N.B. there can be more than one * any more will be prev_in_ktree(ctx): CURRENT and userid matches */ @@ -1234,6 +1250,32 @@ K_ITEM *find_paymentaddresses(int64_t userid, K_TREE_CTX *ctx) return NULL; } +/* Find the first paymentaddresses for the given userid + * sorted by userid+createdate+... */ +K_ITEM *find_paymentaddresses_create(int64_t userid, K_TREE_CTX *ctx) +{ + PAYMENTADDRESSES paymentaddresses, *pa; + K_ITEM look, *item; + + paymentaddresses.userid = userid; + paymentaddresses.createdate.tv_sec = 0; + paymentaddresses.createdate.tv_usec = 0; + paymentaddresses.payaddress[0] = '\0'; + + INIT_PAYMENTADDRESSES(&look); + look.data = (void *)(&paymentaddresses); + item = find_after_in_ktree(paymentaddresses_create_root, &look, + cmp_payaddr_create, ctx); + if (item) { + DATA_PAYMENTADDRESSES(pa, item); + if (pa->userid == userid) + return item; + else + return NULL; + } else + return NULL; +} + K_ITEM *find_one_payaddress(int64_t userid, char *payaddress, K_TREE_CTX *ctx) { PAYMENTADDRESSES paymentaddresses; @@ -1254,7 +1296,10 @@ K_ITEM *find_one_payaddress(int64_t userid, char *payaddress, K_TREE_CTX *ctx) * that has EVER been seen before * However, also, cmd_userset() that uses it, effectively ensures * that 2 standard users, that mine to a username rather than - * a bitcoin address, cannot ever use the same bitcoin address */ + * a bitcoin address, cannot ever use the same bitcoin address + * N.B. this is faster than a bitcoind check, but still slow + * It needs a tree based on payaddress to speed it up + * N.B.2 paymentadresses_root doesn't contain addrauth usernames */ K_ITEM *find_any_payaddress(char *payaddress) { PAYMENTADDRESSES *pa; @@ -1271,7 +1316,7 @@ K_ITEM *find_any_payaddress(char *payaddress) return NULL; } -// order by userid asc,paydate asc,payaddress asc,expirydate desc +// order by userid asc,payoutid asc,subname asc,expirydate desc cmp_t cmp_payments(K_ITEM *a, K_ITEM *b) { PAYMENTS *pa, *pb; @@ -1279,9 +1324,9 @@ cmp_t cmp_payments(K_ITEM *a, K_ITEM *b) DATA_PAYMENTS(pb, b); cmp_t c = CMP_BIGINT(pa->userid, pb->userid); if (c == 0) { - c = CMP_TV(pa->paydate, pb->paydate); + c = CMP_BIGINT(pa->payoutid, pb->payoutid); if (c == 0) { - c = CMP_STR(pa->payaddress, pb->payaddress); + c = CMP_STR(pa->subname, pb->subname); if (c == 0) c = CMP_TV(pb->expirydate, pa->expirydate); } @@ -1289,6 +1334,87 @@ cmp_t cmp_payments(K_ITEM *a, K_ITEM *b) return c; } +K_ITEM *find_payments(int64_t payoutid, int64_t userid, char *subname) +{ + PAYMENTS payments; + K_TREE_CTX ctx[1]; + K_ITEM look; + + payments.payoutid = payoutid; + payments.userid = userid; + STRNCPY(payments.subname, subname); + payments.expirydate.tv_sec = default_expiry.tv_sec; + payments.expirydate.tv_usec = default_expiry.tv_usec; + + INIT_PAYMENTS(&look); + look.data = (void *)(&payments); + return find_in_ktree(payments_root, &look, cmp_payments, ctx); +} + +K_ITEM *find_first_payments(int64_t userid, K_TREE_CTX *ctx) +{ + PAYMENTS payments; + K_TREE_CTX ctx0[1]; + K_ITEM look, *item; + + if (ctx == NULL) + ctx = ctx0; + + bzero(&payments, sizeof(payments)); + payments.userid = userid; + + INIT_PAYMENTS(&look); + look.data = (void *)(&payments); + // userid needs to be checked if item returned != NULL + item = find_after_in_ktree(payments_root, &look, cmp_payments, ctx); + return item; +} + +K_ITEM *find_first_paypayid(int64_t userid, int64_t payoutid, K_TREE_CTX *ctx) +{ + PAYMENTS payments; + K_TREE_CTX ctx0[1]; + K_ITEM look, *item; + + if (ctx == NULL) + ctx = ctx0; + + payments.userid = userid; + payments.payoutid = payoutid; + payments.subname[0] = '\0'; + + INIT_PAYMENTS(&look); + look.data = (void *)(&payments); + // userid+payoutid needs to be checked if item returned != NULL + item = find_after_in_ktree(payments_root, &look, cmp_payments, ctx); + return item; +} + +// order by userid asc +cmp_t cmp_accountbalance(K_ITEM *a, K_ITEM *b) +{ + PAYMENTS *aba, *abb; + DATA_PAYMENTS(aba, a); + DATA_PAYMENTS(abb, b); + return CMP_BIGINT(aba->userid, abb->userid); +} + +K_ITEM *find_accountbalance(int64_t userid) +{ + ACCOUNTBALANCE accountbalance; + K_TREE_CTX ctx[1]; + K_ITEM look, *item; + + accountbalance.userid = userid; + + INIT_ACCOUNTBALANCE(&look); + look.data = (void *)(&accountbalance); + K_RLOCK(accountbalance_free); + item = find_in_ktree(accountbalance_root, &look, cmp_accountbalance, ctx); + K_RUNLOCK(accountbalance_free); + return item; +} + // order by optionname asc,activationdate asc,activationheight asc,expirydate desc cmp_t cmp_optioncontrol(K_ITEM *a, K_ITEM *b) { @@ -1308,16 +1434,16 @@ cmp_t cmp_optioncontrol(K_ITEM *a, K_ITEM *b) } // Must be R or W locked before call -K_ITEM *find_optioncontrol(char *optionname, tv_t *now) +K_ITEM *find_optioncontrol(char *optionname, tv_t *now, int32_t height) { OPTIONCONTROL optioncontrol, *oc, *ocbest; K_TREE_CTX ctx[1]; K_ITEM look, *item, *best; - /* Step through all records having optionaname and check: + /* Step through all records having optionname and check: * 1) activationdate is <= now * and - * 2) height <= current + * 2) height <= specified height (pool.height = current) * 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 @@ -1350,7 +1476,7 @@ K_ITEM *find_optioncontrol(char *optionname, tv_t *now) // Is oc active? if (CURRENT(&(oc->expirydate)) && - oc->activationheight <= pool.height && + oc->activationheight <= height && tv_newer_eq(&(oc->activationdate), now)) { // Is oc newer than ocbest? if (!ocbest || @@ -1972,12 +2098,15 @@ cmp_t cmp_blocks(K_ITEM *a, K_ITEM *b) * or add new ones as required here */ // Must be R or W locked before call - gets current status (default_expiry) -K_ITEM *find_blocks(int32_t height, char *blockhash) +K_ITEM *find_blocks(int32_t height, char *blockhash, K_TREE_CTX *ctx) { BLOCKS blocks; - K_TREE_CTX ctx[1]; + K_TREE_CTX ctx0[1]; K_ITEM look; + if (ctx == NULL) + ctx = ctx0; + blocks.height = height; STRNCPY(blocks.blockhash, blockhash); blocks.expirydate.tv_sec = default_expiry.tv_sec; @@ -2221,14 +2350,14 @@ void set_block_share_counters() LOGWARNING("%s(): Update block counters complete", __func__); } -/* order by height asc,userid asc,expirydate asc +/* order by payoutid asc,userid asc,expirydate asc * i.e. only one payout amount per block per user */ cmp_t cmp_miningpayouts(K_ITEM *a, K_ITEM *b) { MININGPAYOUTS *ma, *mb; DATA_MININGPAYOUTS(ma, a); DATA_MININGPAYOUTS(mb, b); - cmp_t c = CMP_INT(ma->height, mb->height); + cmp_t c = CMP_BIGINT(ma->payoutid, mb->payoutid); if (c == 0) { c = CMP_BIGINT(ma->userid, mb->userid); if (c == 0) @@ -2237,6 +2366,893 @@ cmp_t cmp_miningpayouts(K_ITEM *a, K_ITEM *b) return c; } +K_ITEM *find_miningpayouts(int64_t payoutid, int64_t userid) +{ + MININGPAYOUTS miningpayouts; + K_TREE_CTX ctx[1]; + K_ITEM look; + + miningpayouts.payoutid = payoutid; + miningpayouts.userid = userid; + miningpayouts.expirydate.tv_sec = default_expiry.tv_sec; + miningpayouts.expirydate.tv_usec = default_expiry.tv_usec; + + INIT_MININGPAYOUTS(&look); + look.data = (void *)(&miningpayouts); + return find_in_ktree(miningpayouts_root, &look, cmp_miningpayouts, ctx); +} + +K_ITEM *first_miningpayouts(int64_t payoutid, K_TREE_CTX *ctx) +{ + MININGPAYOUTS miningpayouts; + K_TREE_CTX ctx0[1]; + K_ITEM look; + + if (ctx == NULL) + ctx = ctx0; + + miningpayouts.payoutid = payoutid; + miningpayouts.userid = 0; + miningpayouts.expirydate.tv_sec = 0; + miningpayouts.expirydate.tv_usec = 0; + + INIT_MININGPAYOUTS(&look); + look.data = (void *)(&miningpayouts); + return find_after_in_ktree(miningpayouts_root, &look, cmp_miningpayouts, ctx); +} + +/* Processing payouts uses it's own tree of miningpayouts keyed only on userid + * that is stored in the miningpayouts tree/db when the calculations are done + * cmp_mu() and upd_add_mu() are used for that */ + +// order by userid asc +cmp_t cmp_mu(K_ITEM *a, K_ITEM *b) +{ + MININGPAYOUTS *ma, *mb; + DATA_MININGPAYOUTS(ma, a); + DATA_MININGPAYOUTS(mb, b); + return CMP_BIGINT(ma->userid, mb->userid); +} + +// update the userid record or add a new one if the userid isn't already present +K_TREE *upd_add_mu(K_TREE *mu_root, K_STORE *mu_store, int64_t userid, + double diffacc) +{ + MININGPAYOUTS lookminingpayouts, *miningpayouts; + K_ITEM look, *mu_item; + K_TREE_CTX ctx[1]; + + lookminingpayouts.userid = userid; + INIT_MININGPAYOUTS(&look); + look.data = (void *)(&lookminingpayouts); + // No locking required since it's not a shared tree or store + mu_item = find_in_ktree(mu_root, &look, cmp_mu, ctx); + if (mu_item) { + DATA_MININGPAYOUTS(miningpayouts, mu_item); + miningpayouts->diffacc += diffacc; + } else { + K_WLOCK(mu_store); + mu_item = k_unlink_head(miningpayouts_free); + DATA_MININGPAYOUTS(miningpayouts, mu_item); + miningpayouts->userid = userid; + miningpayouts->diffacc = diffacc; + mu_root = add_to_ktree(mu_root, mu_item, cmp_mu); + k_add_head(mu_store, mu_item); + K_WUNLOCK(mu_store); + } + + return mu_root; +} + +// order by height asc,blockhash asc,expirydate asc +cmp_t cmp_payouts(K_ITEM *a, K_ITEM *b) +{ + PAYOUTS *pa, *pb; + DATA_PAYOUTS(pa, a); + DATA_PAYOUTS(pb, b); + cmp_t c = CMP_INT(pa->height, pb->height); + if (c == 0) { + c = CMP_STR(pa->blockhash, pb->blockhash); + if (c == 0) + c = CMP_TV(pa->expirydate, pb->expirydate); + } + return c; +} + +// order by payoutid asc,expirydate asc +cmp_t cmp_payouts_id(K_ITEM *a, K_ITEM *b) +{ + PAYOUTS *pa, *pb; + DATA_PAYOUTS(pa, a); + DATA_PAYOUTS(pb, b); + cmp_t c = CMP_BIGINT(pa->payoutid, pb->payoutid); + if (c == 0) + c = CMP_TV(pa->expirydate, pb->expirydate); + return c; +} + +K_ITEM *find_payouts(int32_t height, char *blockhash) +{ + PAYOUTS payouts; + K_TREE_CTX ctx[1]; + K_ITEM look; + + payouts.height = height; + STRNCPY(payouts.blockhash, blockhash); + payouts.expirydate.tv_sec = default_expiry.tv_sec; + payouts.expirydate.tv_usec = default_expiry.tv_usec; + + INIT_PAYOUTS(&look); + look.data = (void *)(&payouts); + return find_in_ktree(payouts_root, &look, cmp_payouts, ctx); +} + +// Last block payout calculated +K_ITEM *find_last_payouts() +{ + K_TREE_CTX ctx[1]; + PAYOUTS *payouts; + K_ITEM *p_item; + + p_item = last_in_ktree(payouts_root, ctx); + while (p_item) { + DATA_PAYOUTS(payouts, p_item); + if (CURRENT(&(payouts->expirydate))) + return p_item; + p_item = prev_in_ktree(ctx); + } + return p_item; +} + +K_ITEM *find_payoutid(int64_t payoutid) +{ + PAYOUTS payouts; + K_TREE_CTX ctx[1]; + K_ITEM look; + + payouts.payoutid = payoutid; + payouts.expirydate.tv_sec = default_expiry.tv_sec; + payouts.expirydate.tv_usec = default_expiry.tv_usec; + + INIT_PAYOUTS(&look); + look.data = (void *)(&payouts); + return find_in_ktree(payouts_id_root, &look, cmp_payouts_id, ctx); +} + +/* Find the block_workinfoid of the block requested + then add all it's diffacc shares + then keep stepping back shares until diffacc_total matches or exceeds + the number required (diff_want) - this is begin_workinfoid + (also summarising diffacc per user) + then keep stepping back until we complete the current begin_workinfoid + (also summarising diffacc per user) + While we are still below diff_want + find each workmarker and add on the full set of worksummary + diffacc shares (also summarising diffacc per user) + This will give us the total number of diff1 shares (diffacc_total) + to use for the payment calculations + The value of diff_want defaults to the block's network difficulty + (block_ndiff) but can be changed with diff_times and diff_add to: + block_ndiff * diff_times + diff_add + N.B. diff_times and diff_add can be zero, positive or negative + The pplns_elapsed time of the shares is from the createdate of the + begin_workinfoid that has shares accounted to the total, + up to the createdate of the last share + The user average hashrate would be: + diffacc_user * 2^32 / pplns_elapsed + PPLNS fraction of the payout would be: + diffacc_user / diffacc_total + + N.B. 'begin' means the oldest back in time and 'end' means the newest + 'end' should usually be the info of the found block with the pplns + data going back in time to 'begin' + + The data processing procedure is to create a separate tree/store of + miningpayouts, create a seperate store of payments, + then actually create the payout record and transfer the miningpayouts + and payments to the ram table and the db + + TODO: recheck the payout if it already exists + + N.B. process_pplns() is only automatically triggered once after the block + summarisation is verified, so it can always report all errors +*/ +void process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) +{ + K_TREE_CTX b_ctx[1], ss_ctx[1], wm_ctx[1], ms_ctx[1], pay_ctx[1], mu_ctx[1]; + bool allow_aged = true, conned = false, begun = false; + bool countbacklimit, ok; + PGconn *conn = NULL; + MININGPAYOUTS *miningpayouts; + OPTIONCONTROL *optioncontrol; + PAYMENTS *payments; + WORKINFO *workinfo; + PAYOUTS *payouts, *payouts2; + BLOCKS *blocks, *blocks2; + USERS *users; + K_ITEM *p_item, *old_p_item, *b_item, *b2_item, *w_item, *wb_item; + K_ITEM *u_item, *mu_item, *oc_item, *pay_item, *p2_item, *old_p2_item; + SHARESUMMARY looksharesummary, *sharesummary; + WORKMARKERS lookworkmarkers, *workmarkers; + MARKERSUMMARY lookmarkersummary, *markersummary; + K_ITEM ss_look, *ss_item, wm_look, *wm_item, ms_look, *ms_item; + int64_t amount, used, d64, g64, begin_workinfoid, end_workinfoid; + int64_t total_share_count, acc_share_count; + int64_t ss_count, wm_count, ms_count; + K_STORE *mu_store = NULL, *pay_store = NULL, *addr_store = NULL; + K_TREE *mu_root = NULL; + int usercount; + double ndiff, total_diff, diff_want, elapsed; + char ndiffbin[TXT_SML+1]; + double diff_times, diff_add; + char cd_buf[CDATE_BUFSIZ]; + tv_t begin_tv, end_tv, now; + char buf[1024]; + + /* + * Only allow one process_pplns() at a time + * This ensures that a payout can't be processed twice at the same time + * and simply avoids the problems that would cause without much more + * strict locking than is used already + */ + ck_wlock(&process_pplns_lock); + + setnow(&now); + + K_RLOCK(payouts_free); + p_item = find_payouts(height, blockhash); + K_RUNLOCK(payouts_free); + // TODO: regenerate miningpayouts and payments if required or missing? + if (p_item) { + DATA_PAYOUTS(payouts, p_item); + tv_to_buf(&(payouts->createdate), cd_buf, sizeof(cd_buf)); + LOGERR("%s(): payout for block %"PRId32"/%s already exists" + "%"PRId64"/%"PRId64"/%"PRId64"/%s", + __func__, height, blockhash, payouts->payoutid, + payouts->workinfoidstart, payouts->workinfoidend, + cd_buf); + goto oku; + } + + // Check the block status + K_RLOCK(blocks_free); + b_item = find_blocks(height, blockhash, b_ctx); + if (!b_item) { + K_RUNLOCK(blocks_free); + LOGERR("%s(): no block %"PRId32"/%s for payout", + __func__, height, blockhash); + goto oku; + } + DATA_BLOCKS(blocks, b_item); + // If addr_cd is null, use the block NEW createdate + if (!addr_cd) { + b2_item = b_item; + DATA_BLOCKS(blocks2, b2_item); + while (b2_item && blocks2->height == height && + strcmp(blocks2->blockhash, blockhash) == 0) { + if (blocks2->confirmed[0] == BLOCKS_NEW) { + addr_cd = &(blocks2->createdate); + break; + } + b2_item = next_in_ktree(b_ctx); + DATA_BLOCKS_NULL(blocks2, b2_item); + } + if (!addr_cd) { + K_RUNLOCK(blocks_free); + LOGEMERG("%s(): missing NEW record for block %"PRId32 + "/%"PRId64"/%s/%s/%"PRId64, + __func__, blocks->height, blocks->workinfoid, + blocks->workername, blocks->confirmed, + blocks->reward); + goto oku; + } + } + K_RUNLOCK(blocks_free); + + 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: + case BLOCKS_ORPHAN: + LOGERR("%s(): can't process block %"PRId32"/%" + PRId64"/%s/%"PRId64" status: %s/%s", + __func__, blocks->height, blocks->workinfoid, + blocks->workername, blocks->reward, + blocks->confirmed, + blocks_confirmed(blocks->confirmed)); + goto oku; + } + w_item = find_workinfo(blocks->workinfoid, NULL); + if (!w_item) { + LOGEMERG("%s(): missing block workinfoid %"PRId32"/%"PRId64 + "/%s/%s/%"PRId64, + __func__, blocks->height, blocks->workinfoid, + blocks->workername, blocks->confirmed, + blocks->reward); + goto oku; + } + DATA_WORKINFO(workinfo, w_item); + + // Get the PPLNS N values + K_RLOCK(optioncontrol_free); + oc_item = find_optioncontrol(PPLNSDIFFTIMES, &(blocks->createdate), + height); + K_RUNLOCK(optioncontrol_free); + if (!oc_item) { + tv_to_buf(&(blocks->createdate), cd_buf, sizeof(cd_buf)); + LOGEMERG("%s(): missing optioncontrol %s (%s/%"PRId32")", + __func__, PPLNSDIFFTIMES, cd_buf, blocks->height); + goto oku; + } + DATA_OPTIONCONTROL(optioncontrol, oc_item); + diff_times = atof(optioncontrol->optionvalue); + + K_RLOCK(optioncontrol_free); + oc_item = find_optioncontrol(PPLNSDIFFADD, &(blocks->createdate), + height); + K_RUNLOCK(optioncontrol_free); + if (!oc_item) { + tv_to_buf(&(blocks->createdate), cd_buf, sizeof(cd_buf)); + LOGEMERG("%s(): missing optioncontrol %s (%s/%"PRId32")", + __func__, PPLNSDIFFADD, cd_buf, blocks->height); + goto oku; + } + DATA_OPTIONCONTROL(optioncontrol, oc_item); + diff_add = atof(optioncontrol->optionvalue); + + hex2bin(ndiffbin, workinfo->bits, 4); + ndiff = diff_from_nbits(ndiffbin); + diff_want = ndiff * diff_times + diff_add; + if (diff_want < 1.0) { + LOGERR("%s(): invalid diff_want %.1f, block %"PRId32"/%" + PRId64"/%s/%s/%"PRId64, + __func__, diff_want, blocks->height, blocks->workinfoid, + blocks->workername, blocks->confirmed, blocks->reward); + goto oku; + } + + // Check for the hard coded limit + if (blocks->height > FIVExSTT) + countbacklimit = true; + else + countbacklimit = false; + LOGDEBUG("%s(): ndiff %.1f limit %c", + __func__, ndiff, countbacklimit ? 'Y' : 'N'); + + // add up all the shares ... + begin_workinfoid = end_workinfoid = 0; + total_share_count = acc_share_count = 0; + total_diff = 0; + ss_count = wm_count = ms_count = 0; + + mu_store = k_new_store(miningpayouts_free); + mu_root = new_ktree(); + + looksharesummary.workinfoid = blocks->workinfoid; + looksharesummary.userid = MAXID; + looksharesummary.workername = EMPTY; + INIT_SHARESUMMARY(&ss_look); + ss_look.data = (void *)(&looksharesummary); + K_RLOCK(sharesummary_free); + K_RLOCK(workmarkers_free); + K_RLOCK(markersummary_free); + ss_item = find_before_in_ktree(sharesummary_workinfoid_root, &ss_look, + cmp_sharesummary_workinfoid, ss_ctx); + DATA_SHARESUMMARY_NULL(sharesummary, ss_item); + if (ss_item) + end_workinfoid = sharesummary->workinfoid; + /* Add up all sharesummaries until >= diff_want + * also record the latest lastshare - that will be the end pplns time + * which will be >= blocks->createdate */ + while (total_diff < diff_want && ss_item) { + switch (sharesummary->complete[0]) { + case SUMMARY_CONFIRM: + break; + case SUMMARY_COMPLETE: + if (allow_aged) + break; + default: + // Release ASAP + K_RUNLOCK(markersummary_free); + K_RUNLOCK(workmarkers_free); + K_RUNLOCK(sharesummary_free); + LOGERR("%s(): sharesummary not ready %" + PRId64"/%s/%"PRId64"/%s. allow_aged=%s", + __func__, sharesummary->userid, + sharesummary->workername, + sharesummary->workinfoid, + sharesummary->complete, + allow_aged ? "true" : "false"); + goto shazbot; + } + + // 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 += sharesummary->diffacc; + begin_workinfoid = sharesummary->workinfoid; + // TODO: add lastshareacc to sharesummary and markersummary + if (sharesummary->shareacc > 0 && + tv_newer(&end_tv, &(sharesummary->lastshare))) + copy_tv(&end_tv, &(sharesummary->lastshare)); + mu_root = upd_add_mu(mu_root, mu_store, + sharesummary->userid, + sharesummary->diffacc); + ss_item = prev_in_ktree(ss_ctx); + DATA_SHARESUMMARY_NULL(sharesummary, ss_item); + } + + // Include the rest of the sharesummaries matching 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: + // Release ASAP + K_RUNLOCK(markersummary_free); + K_RUNLOCK(workmarkers_free); + K_RUNLOCK(sharesummary_free); + LOGERR("%s(): sharesummary2 not ready %" + PRId64"/%s/%"PRId64"/%s. allow_aged=%s", + __func__, sharesummary->userid, + sharesummary->workername, + sharesummary->workinfoid, + sharesummary->complete, + allow_aged ? "true" : "false"); + goto shazbot; + } + ss_count++; + total_share_count += sharesummary->sharecount; + acc_share_count += sharesummary->shareacc; + total_diff += sharesummary->diffacc; + mu_root = upd_add_mu(mu_root, mu_store, + sharesummary->userid, + sharesummary->diffacc); + ss_item = prev_in_ktree(ss_ctx); + DATA_SHARESUMMARY_NULL(sharesummary, ss_item); + } + LOGDEBUG("%s(): ss %"PRId64" total %.1f want %.1f", + __func__, ss_count, total_diff, diff_want); + + /* If we haven't met or exceeded the required N, + * move on to the markersummaries ... this is now mandatory */ + 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 = blocks->workinfoid + 1; + INIT_WORKMARKERS(&wm_look); + wm_look.data = (void *)(&lookworkmarkers); + wm_item = find_before_in_ktree(workmarkers_workinfoid_root, &wm_look, + cmp_workmarkers_workinfoid, wm_ctx); + DATA_WORKMARKERS_NULL(workmarkers, wm_item); + 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, + cmp_markersummary, ms_ctx); + DATA_MARKERSUMMARY_NULL(markersummary, ms_item); + // add the whole markerid + while (ms_item && markersummary->markerid == workmarkers->markerid) { + if (end_workinfoid == 0) + end_workinfoid = workmarkers->workinfoidend; + ms_count++; + total_share_count += markersummary->sharecount; + acc_share_count += markersummary->shareacc; + total_diff += markersummary->diffacc; + begin_workinfoid = workmarkers->workinfoidstart; + if (markersummary->shareacc > 0 && + tv_newer(&end_tv, &(markersummary->lastshare))) + copy_tv(&end_tv, &(markersummary->lastshare)); + mu_root = upd_add_mu(mu_root, mu_store, + markersummary->userid, + 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 %.1f want %.1f", + __func__, wm_count, ms_count, total_diff, diff_want); + } + K_RUNLOCK(markersummary_free); + K_RUNLOCK(workmarkers_free); + K_RUNLOCK(sharesummary_free); + + usercount = mu_store->count; + + if (wm_count < 1) { + /* Problem means either workmarkers are not being processed + * or if they are, then when the shifts are later created, + * they almost certainly won't match the begin_workinfo + * calculated + * i.e. the payout N is too small, it's less than the time + * needed to create and process any workmarkers for this + * block - so abort + * The fix is to create the marks and summaries needed via + * cmd_marks() then manually trigger the payout generation + * TODO: via cmd_payouts() ... which isn't available yet */ + LOGEMERG("%s(): payout had < 1 (%"PRId64") workmarkers for " + "block %"PRId32"/%"PRId64"/%s/%s/%"PRId64 + " beginwi=%"PRId64" ss=%"PRId64" diff=%.1f", + __func__, wm_count, blocks->height, blocks->workinfoid, + blocks->workername, blocks->confirmed, blocks->reward, + begin_workinfoid, ss_count, total_diff); + goto shazbot; + } + + LOGDEBUG("%s(): total %.1f want %.1f", __func__, total_diff, diff_want); + if (total_diff == 0.0) { + LOGERR("%s(): total share diff zero before block %"PRId32 + "/%"PRId64"/%s/%s/%"PRId64, + __func__, blocks->height, blocks->workinfoid, + blocks->workername, blocks->confirmed, + blocks->reward); + goto shazbot; + } + + wb_item = find_workinfo(begin_workinfoid, NULL); + if (!wb_item) { + LOGEMERG("%s(): missing begin workinfo record %"PRId64 + " payout of block %"PRId32"/%"PRId64"/%s/%s/%"PRId64, + __func__, begin_workinfoid, blocks->height, + blocks->workinfoid, blocks->workername, + blocks->confirmed, blocks->reward); + 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 accepted - + * which can be after the block, but must have the same workinfoid as + * the block, if it is after the block + * Any 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 - but will be candidates for subsequent blocks */ + elapsed = tvdiff(&end_tv, &begin_tv); + + // Create the payout + K_WLOCK(payouts_free); + p_item = k_unlink_head(payouts_free); + K_WUNLOCK(payouts_free); + DATA_PAYOUTS(payouts, p_item); + + bzero(payouts, sizeof(*payouts)); + payouts->height = height; + STRNCPY(payouts->blockhash, blockhash); + d64 = blocks->reward * 9 / 1000; + g64 = blocks->reward - d64; + payouts->minerreward = g64; + payouts->workinfoidstart = begin_workinfoid; + payouts->workinfoidend = end_workinfoid; + payouts->elapsed = elapsed; + STRNCPY(payouts->status, PAYOUTS_PROCESSING_STR); + payouts->diffwanted = diff_want; + payouts->diffused = total_diff; + payouts->shareacc = acc_share_count; + copy_tv(&(payouts->lastshareacc), &end_tv); + + ctv_to_buf(addr_cd, cd_buf, sizeof(cd_buf)); + snprintf(buf, sizeof(buf), + "diff_times=%f%cdiff_add=%f%ctotal_share_count=%"PRId64 + "%css_count=%"PRId64"%cwm_count=%"PRId64"%cms_count=%"PRId64 + "%caddr_cd=%s", + diff_times, FLDSEP, diff_add, FLDSEP, total_share_count, + FLDSEP, ss_count, FLDSEP, wm_count, FLDSEP, ms_count, + FLDSEP, cd_buf); + payouts->stats = buf; + + conned = CKPQConn(&conn); + begun = CKPQBegin(conn); + if (!begun) + goto shazbot; + + // begun is true + ok = payouts_add(conn, true, p_item, &old_p_item, (char *)by_default, + (char *)__func__, (char *)inet_default, &now, NULL, + begun); + if (!ok) + goto shazbot; + + // Update and store the miningpayouts and payments + pay_store = k_new_store(payments_free); + mu_item = first_in_ktree(mu_root, mu_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) { + LOGEMERG("%s(): unknown userid %"PRId64"/%.1f in " + "payout for block %"PRId32, + __func__, miningpayouts->userid, + miningpayouts->diffacc, blocks->height); + goto shazbot; + } + DATA_USERS(users, u_item); + + K_ITEM *pa_item, *pa_item2; + PAYMENTADDRESSES *pa, *pa2; + int64_t paytotal = 0; + int count = 0; + + used = 0; + amount = floor((double)(payouts->minerreward) * + miningpayouts->diffacc / payouts->diffused); + + /* Get the paymentaddresses active as at *addr_cd + * which defaults to when the block was found */ + addr_store = k_new_store(paymentaddresses_free); + K_WLOCK(paymentaddresses_free); + pa_item = find_paymentaddresses_create(miningpayouts->userid, + pay_ctx); + if (pa_item) { + DATA_PAYMENTADDRESSES(pa, pa_item); + while (pa_item && pa->userid == miningpayouts->userid && + tv_newer(&(pa->createdate), addr_cd)) { + if (tv_newer_eq(addr_cd, &(pa->expirydate))) { + paytotal += pa->payratio; + + /* Duplicate it to a new store - + * thus changes to paymentaddresses + * can't affect the code below + * and we don't need to keep + * paymentaddresses locked until we + * have completed the db + * additions/updates */ + pa_item2 = k_unlink_head(paymentaddresses_free); + DATA_PAYMENTADDRESSES(pa2, pa_item2); + pa2->userid = pa->userid; + STRNCPY(pa2->payaddress, pa->payaddress); + pa2->payratio = pa->payratio; + k_add_tail(addr_store, pa_item2); + + pa_item = next_in_ktree(pay_ctx); + DATA_PAYMENTADDRESSES_NULL(pa, pa_item); + } + } + } + K_WUNLOCK(paymentaddresses_free); + + pa_item = addr_store->head; + if (pa_item) { + // Normal user with at least 1 paymentaddress + while (pa_item) { + DATA_PAYMENTADDRESSES(pa, pa_item); + K_WLOCK(payments_free); + pay_item = k_unlink_head(payments_free); + K_WUNLOCK(payments_free); + DATA_PAYMENTS(payments, pay_item); + bzero(payments, sizeof(*payments)); + payments->payoutid = payouts->payoutid; + payments->userid = miningpayouts->userid; + snprintf(payments->subname, + sizeof(payments->subname), + "%s.%d", users->username, ++count); + STRNCPY(payments->payaddress, pa->payaddress); + d64 = floor((double)amount * + (double)(pa->payratio) / + (double)paytotal); + payments->amount = d64; + payments->diffacc = miningpayouts->diffacc * + (double)(pa->payratio) / + (double)paytotal; + used += d64; + k_add_tail(pay_store, pay_item); + ok = payments_add(conn, true, pay_item, + &(payments->old_item), + (char *)by_default, + (char *)__func__, + (char *)inet_default, &now, + NULL, begun); + if (!ok) + goto shazbot; + + pa_item = pa_item->next; + } + } else { + /* Address user or normal user without a paymentaddress + * TODO: user table needs a flag to say which it is ... + * for now use a simple test */ + bool gotaddr = false; + size_t len; + + switch (users->username[0]) { + case '1': + case '3': + len = strlen(users->username); + if (len >= ADDR_MIN_LEN && len <= ADDR_MAX_LEN) + gotaddr = true; + } + if (gotaddr) { + K_WLOCK(payments_free); + pay_item = k_unlink_head(payments_free); + K_WUNLOCK(payments_free); + DATA_PAYMENTS(payments, pay_item); + bzero(payments, sizeof(*payments)); + payments->payoutid = payouts->payoutid; + payments->userid = miningpayouts->userid; + snprintf(payments->subname, + sizeof(payments->subname), + "%s.0", users->username); + STRNCPY(payments->payaddress, users->username); + payments->amount = amount; + used = amount; + k_add_head(pay_store, pay_item); + ok = payments_add(conn, true, pay_item, + &(payments->old_item), + (char *)by_default, + (char *)__func__, + (char *)inet_default, &now, + NULL, begun); + if (!ok) + goto shazbot; + } // else they go to their dust balance + } + + /* N.B. there will, of course, be a miningpayouts record without + * any payments record if the paymentaddress was missing */ + miningpayouts->payoutid = payouts->payoutid; + if (used == 0) + miningpayouts->amount = amount; + else + miningpayouts->amount = used; + + ok = miningpayouts_add(conn, true, mu_item, + &(miningpayouts->old_item), + (char *)by_default, (char *)__func__, + (char *)inet_default, &now, NULL, begun); + if (!ok) + goto shazbot; + + if (addr_store->count) { + K_WLOCK(paymentaddresses_free); + k_list_transfer_to_head(addr_store, paymentaddresses_free); + K_WUNLOCK(addr_store); + } + addr_store = k_free_store(addr_store); + + mu_item = next_in_ktree(mu_ctx); + } + + // begun is true + CKPQEnd(conn, begun); + + payouts_add_ram(true, p_item, old_p_item, &now); + + mu_root = free_ktree(mu_root, NULL); + mu_item = k_unlink_head(mu_store); + while (mu_item) { + DATA_MININGPAYOUTS(miningpayouts, mu_item); + miningpayouts_add_ram(true, mu_item, miningpayouts->old_item, &now); + mu_item = k_unlink_head(mu_store); + } + mu_store = k_free_store(mu_store); + + pay_item = k_unlink_head(pay_store); + while (pay_item) { + DATA_PAYMENTS(payments, pay_item); + payments_add_ram(true, pay_item, payments->old_item, &now); + pay_item = k_unlink_head(pay_store); + } + pay_store = k_free_store(pay_store); + + ctv_to_buf(addr_cd, cd_buf, sizeof(cd_buf)); + LOGWARNING("%s(): payout %"PRId64" setup for block %"PRId32"/%"PRId64 + "/%s/%"PRId64" ss=%"PRId64" wm=%"PRId64" ms=%"PRId64 + " users=%d times=%.1f add=%.1f addr_cd=%s", + __func__, payouts->payoutid, blocks->height, + blocks->workinfoid, blocks->confirmed, blocks->reward, + ss_count, wm_count, ms_count, usercount, diff_times, + diff_add, cd_buf); + + // convert the stack memory to heap memeory + payouts->stats = strdup(payouts->stats); + + K_WLOCK(payouts_free); + p2_item = k_unlink_head(payouts_free); + K_WUNLOCK(payouts_free); + 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)); + payouts2->stats = strdup(payouts->stats); + + setnow(&now); + /* N.B. the PROCESSING payouts could have expirydate = createdate + * if the code above executes faster than the pgsql time resolution */ + ok = payouts_add(conn, true, p2_item, &old_p2_item, (char *)by_default, + (char *)__func__, (char *)inet_default, &now, NULL, + false); + if (!ok) { + LOGEMERG("%s(): payout %"PRId64" for block %"PRId32"/%s " + "NOT set generated - it needs to be set manually", + __func__, payouts->payoutid, blocks->height, + blocks->blockhash); + } + + CKPQDisco(&conn, conned); + + goto oku; + +shazbot: + if (begun) + CKPQEnd(conn, false); + CKPQDisco(&conn, conned); + + if (p_item) { + K_WLOCK(payouts_free); + k_add_head(payouts_free, p_item); + K_WUNLOCK(payouts_free); + } + +oku: + ; + ck_wunlock(&process_pplns_lock); + if (mu_root) + mu_root = free_ktree(mu_root, NULL); + if (mu_store) { + if (mu_store->count) { + K_WLOCK(mu_store); + k_list_transfer_to_head(mu_store, miningpayouts_free); + K_WUNLOCK(mu_store); + } + mu_store = k_free_store(mu_store); + } + if (pay_store) { + if (pay_store->count) { + K_WLOCK(pay_store); + k_list_transfer_to_head(pay_store, payments_free); + K_WUNLOCK(pay_store); + } + pay_store = k_free_store(pay_store); + } + if (addr_store) { + if (addr_store->count) { + K_WLOCK(addr_store); + k_list_transfer_to_head(addr_store, paymentaddresses_free); + K_WUNLOCK(addr_store); + } + addr_store = k_free_store(addr_store); + } + return; +} + // order by userid asc,createdate asc,authid asc,expirydate desc cmp_t cmp_auths(K_ITEM *a, K_ITEM *b) { diff --git a/src/ckdb_dbio.c b/src/ckdb_dbio.c index 4f29c233..73573095 100644 --- a/src/ckdb_dbio.c +++ b/src/ckdb_dbio.c @@ -167,6 +167,8 @@ char *pqerrmsg(PGconn *conn) #define PQPARAM14 PQPARAM8 ",$9,$10,$11,$12,$13,$14" #define PQPARAM15 PQPARAM8 ",$9,$10,$11,$12,$13,$14,$15" #define PQPARAM16 PQPARAM8 ",$9,$10,$11,$12,$13,$14,$15,$16" +#define PQPARAM17 PQPARAM16 ",$17" +#define PQPARAM18 PQPARAM16 ",$17,$18" #define PQPARAM22 PQPARAM16 ",$17,$18,$19,$20,$21,$22" #define PQPARAM26 PQPARAM22 ",$23,$24,$25,$26" #define PQPARAM27 PQPARAM26 ",$27" @@ -222,6 +224,70 @@ PGresult *_CKPQexecParams(PGconn *conn, const char *qry, #define PQexec CKPQexec #define PQexecParams CKPQexecParams +// TODO: switch all to use this +bool CKPQConn(PGconn **conn) +{ + if (*conn == NULL) { + LOGDEBUG("%s(): connecting", __func__); + *conn = dbconnect(); + return true; + } + return false; +} + +// TODO: switch all to use this +void CKPQDisco(PGconn **conn, bool conned) +{ + if (conned) { + LOGDEBUG("%s(): disco", __func__); + PQfinish(*conn); + } +} + +// TODO: switch all to use this +bool _CKPQBegin(PGconn *conn, WHERE_FFL_ARGS) +{ + ExecStatusType rescode; + PGresult *res; + + res = PQexec(conn, "Begin", CKPQ_WRITE); + rescode = PQresultStatus(res); + PQclear(res); + if (!PGOK(rescode)) { + char *buf = pqerrmsg(conn); + LOGEMERG("%s(): Begin failed (%d) '%s'" WHERE_FFL, + __func__, (int)rescode, buf, WHERE_FFL_PASS); + free(buf); + return false; + } + LOGDEBUG("%s(): begin", __func__); + return true; +} + +// TODO: switch all to use this +void _CKPQEnd(PGconn *conn, bool commit, WHERE_FFL_ARGS) +{ + ExecStatusType rescode; + PGresult *res; + + if (commit) { + LOGDEBUG("%s(): commit", __func__); + res = PQexec(conn, "Commit", CKPQ_WRITE); + } else { + LOGDEBUG("%s(): rollback", __func__); + res = PQexec(conn, "Rollback", CKPQ_WRITE); + } + rescode = PQresultStatus(res); + PQclear(res); + if (!PGOK(rescode)) { + char *buf = pqerrmsg(conn); + LOGEMERG("%s(): %s failed (%d) '%s'" WHERE_FFL, + __func__, commit ? "commit" : "rollback", + (int)rescode, buf, WHERE_FFL_PASS); + free(buf); + } +} + int64_t nextid(PGconn *conn, char *idname, int64_t increment, tv_t *cd, char *by, char *code, char *inet) { @@ -1642,9 +1708,13 @@ unparam: n++; paymentaddresses_root = remove_from_ktree(paymentaddresses_root, item, cmp_paymentaddresses); + paymentaddresses_create_root = remove_from_ktree(paymentaddresses_create_root, + item, cmp_payaddr_create); copy_tv(&(row->expirydate), cd); paymentaddresses_root = add_to_ktree(paymentaddresses_root, item, cmp_paymentaddresses); + paymentaddresses_create_root = add_to_ktree(paymentaddresses_create_root, + item, cmp_payaddr_create); } item = prev; DATA_PAYMENTADDRESSES_NULL(row, item); @@ -1658,6 +1728,8 @@ unparam: if (!pa->match) { paymentaddresses_root = add_to_ktree(paymentaddresses_root, match, cmp_paymentaddresses); + paymentaddresses_create_root = add_to_ktree(paymentaddresses_create_root, + match, cmp_payaddr_create); k_unlink_item(pa_store, match); k_add_head(paymentaddresses_store, match); count++; @@ -1745,7 +1817,10 @@ bool paymentaddresses_fill(PGconn *conn) if (!ok) break; - paymentaddresses_root = add_to_ktree(paymentaddresses_root, item, cmp_paymentaddresses); + paymentaddresses_root = add_to_ktree(paymentaddresses_root, item, + cmp_paymentaddresses); + paymentaddresses_create_root = add_to_ktree(paymentaddresses_create_root, + item, cmp_payaddr_create); k_add_head(paymentaddresses_store, item); } if (!ok) @@ -1762,30 +1837,169 @@ bool paymentaddresses_fill(PGconn *conn) return ok; } +// The timing of the memory table updates depends on 'already' +void payments_add_ram(bool ok, K_ITEM *p_item, K_ITEM *old_p_item, tv_t *cd) +{ + PAYMENTS *oldp; + + LOGDEBUG("%s(): ok %c", __func__, ok ? 'Y' : 'N'); + + K_WLOCK(payments_free); + if (!ok) { + // Cleanup for the calling function + k_add_head(payments_free, p_item); + } else { + if (old_p_item) { + DATA_PAYMENTS(oldp, old_p_item); + payments_root = remove_from_ktree(payments_root, old_p_item, cmp_payments); + copy_tv(&(oldp->expirydate), cd); + payments_root = add_to_ktree(payments_root, old_p_item, cmp_payments); + } + payments_root = add_to_ktree(payments_root, p_item, cmp_payments); + k_add_head(payments_store, p_item); + } + K_WUNLOCK(payments_free); +} + +/* Add means create a new one and expire the old one if it exists, + * otherwise we only expire the old one if it exists + * It's the calling functions job to determine if a new one is required + * - i.e. if there is a difference between the old and new + * already = already begun a transaction - and don't update the ram table */ +bool payments_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) +{ + ExecStatusType rescode; + bool conned = false; + PGresult *res; + bool ok = false, begun = false; + PAYMENTS *row, *oldp = NULL; + char *upd, *ins; + char *params[11 + HISTORYDATECOUNT]; + int n, par = 0; + + LOGDEBUG("%s(): add %c already %c", __func__, + add ? 'Y' : 'N', already ? 'Y' : 'N'); + + DATA_PAYMENTS(row, p_item); + K_RLOCK(payments_free); + *old_p_item = find_payments(row->payoutid, row->userid, row->subname); + K_RUNLOCK(payments_free); + + conned = CKPQConn(&conn); + if (!already) { + begun = CKPQBegin(conn); + if (!begun) + goto unparam; + } + + if (*old_p_item) { + LOGDEBUG("%s(): updating old", __func__); + + DATA_PAYMENTS(oldp, *old_p_item); + + upd = "update payments set expirydate=$1 where paymentid=$2" + " and expirydate=$3"; + par = 0; + params[par++] = tv_to_buf(cd, NULL, 0); + params[par++] = bigint_to_buf(oldp->paymentid, 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); + PQclear(res); + if (!PGOK(rescode)) { + PGLOGERR("Update", rescode, conn); + goto rollback; + } + + for (n = 0; n < par; n++) + free(params[n]); + par = 0; + + // Expiring an old record + row->paymentid = oldp->paymentid; + } else { + if (add) { + // Creating a new record + row->paymentid = nextid(conn, "paymentid", (int64_t)1, cd, by, code, inet); + if (row->paymentid == 0) + goto rollback; + } + } + + if (add) { + LOGDEBUG("%s(): adding new", __func__); + + HISTORYDATEINIT(row, cd, by, code, inet); + HISTORYDATETRANSFER(trf_root, row); + + par = 0; + params[par++] = bigint_to_buf(row->paymentid, NULL, 0); + params[par++] = bigint_to_buf(row->payoutid, NULL, 0); + params[par++] = bigint_to_buf(row->userid, NULL, 0); + params[par++] = str_to_buf(row->subname, NULL, 0); + params[par++] = tv_to_buf(&(row->paydate), NULL, 0); + params[par++] = str_to_buf(row->payaddress, NULL, 0); + params[par++] = str_to_buf(row->originaltxn, NULL, 0); + params[par++] = bigint_to_buf(row->amount, NULL, 0); + params[par++] = double_to_buf(row->diffacc, NULL, 0); + params[par++] = str_to_buf(row->committxn, NULL, 0); + params[par++] = str_to_buf(row->commitblockhash, NULL, 0); + HISTORYDATEPARAMS(params, par, row); + PARCHK(par, params); + + ins = "insert into payments " + "(paymentid,payoutid,userid,subname,paydate,payaddress," + "originaltxn,amount,diffacc,committxn,commitblockhash" + HISTORYDATECONTROL ") values (" PQPARAM16 ")"; + + res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + if (!PGOK(rescode)) { + PGLOGERR("Insert", rescode, conn); + goto unparam; + } + } + + ok = true; +rollback: + if (begun) + CKPQEnd(conn, ok); +unparam: + for (n = 0; n < par; n++) + free(params[n]); + + CKPQDisco(&conn, conned); + + if (!already) + payments_add_ram(ok, p_item, *old_p_item, cd); + + return ok; +} + bool payments_fill(PGconn *conn) { ExecStatusType rescode; PGresult *res; K_ITEM *item; PAYMENTS *row; - char *params[1]; - int n, i, par = 0; + int n, i; char *field; char *sel; - int fields = 8; + int fields = 11; bool ok; LOGDEBUG("%s(): select", __func__); - // TODO: handle selecting a subset, eg 20 per web page (in blocklist also) sel = "select " - "userid,paydate,payaddress,originaltxn,amount,committxn,commitblockhash" + "paymentid,payoutid,userid,subname,paydate,payaddress," + "originaltxn,amount,diffacc,committxn,commitblockhash" HISTORYDATECONTROL - ",paymentid from payments where expirydate=$1"; - par = 0; - params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); - PARCHK(par, params); - res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_READ); + " from payments"; + res = PQexec(conn, sel, CKPQ_READ); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Select", rescode, conn); @@ -1814,11 +2028,26 @@ bool payments_fill(PGconn *conn) break; } + PQ_GET_FLD(res, i, "paymentid", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("paymentid", field, row->paymentid); + + PQ_GET_FLD(res, i, "payoutid", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("payoutid", field, row->payoutid); + PQ_GET_FLD(res, i, "userid", field, ok); if (!ok) break; TXT_TO_BIGINT("userid", field, row->userid); + PQ_GET_FLD(res, i, "subname", field, ok); + if (!ok) + break; + TXT_TO_STR("subname", field, row->subname); + PQ_GET_FLD(res, i, "paydate", field, ok); if (!ok) break; @@ -1839,6 +2068,11 @@ bool payments_fill(PGconn *conn) break; TXT_TO_BIGINT("amount", field, row->amount); + PQ_GET_FLD(res, i, "diffacc", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("diffacc", field, row->diffacc); + PQ_GET_FLD(res, i, "committxn", field, ok); if (!ok) break; @@ -1853,11 +2087,6 @@ bool payments_fill(PGconn *conn) if (!ok) break; - PQ_GET_FLD(res, i, "paymentid", field, ok); - if (!ok) - break; - TXT_TO_BIGINT("paymentid", field, row->paymentid); - payments_root = add_to_ktree(payments_root, item, cmp_payments); k_add_head(payments_store, item); } @@ -3653,7 +3882,7 @@ bool blocks_stats(PGconn *conn, int32_t height, char *blockhash, dsp_hash(blockhash, hash_dsp, sizeof(hash_dsp)); K_RLOCK(blocks_free); - old_b_item = find_blocks(height, blockhash); + old_b_item = find_blocks(height, blockhash, NULL); K_RUNLOCK(blocks_free); if (!old_b_item) { @@ -3811,7 +4040,7 @@ bool blocks_add(PGconn *conn, char *height, char *blockhash, dsp_hash(blockhash, hash_dsp, sizeof(hash_dsp)); K_RLOCK(blocks_free); - old_b_item = find_blocks(row->height, blockhash); + old_b_item = find_blocks(row->height, blockhash, NULL); K_RUNLOCK(blocks_free); DATA_BLOCKS_NULL(oldblocks, old_b_item); @@ -4297,96 +4526,128 @@ bool blocks_fill(PGconn *conn) return ok; } -bool miningpayouts_add(PGconn *conn, char *username, char *height, - char *blockhash, char *amount, char *by, - char *code, char *inet, tv_t *cd, K_TREE *trf_root) +// The timing of the memory table updates depends on 'already' +void miningpayouts_add_ram(bool ok, K_ITEM *mp_item, K_ITEM *old_mp_item, tv_t *cd) +{ + MININGPAYOUTS *oldmp; + + LOGDEBUG("%s(): ok %c", __func__, ok ? 'Y' : 'N'); + + K_WLOCK(miningpayouts_free); + if (!ok) { + // Cleanup for the calling function + k_add_head(miningpayouts_free, mp_item); + } else { + if (old_mp_item) { + DATA_MININGPAYOUTS(oldmp, old_mp_item); + miningpayouts_root = remove_from_ktree(miningpayouts_root, old_mp_item, cmp_miningpayouts); + copy_tv(&(oldmp->expirydate), cd); + miningpayouts_root = add_to_ktree(miningpayouts_root, old_mp_item, cmp_miningpayouts); + } + miningpayouts_root = add_to_ktree(miningpayouts_root, mp_item, cmp_miningpayouts); + k_add_head(miningpayouts_store, mp_item); + } + K_WUNLOCK(miningpayouts_free); +} + +/* Add means create a new one and expire the old one if it exists, + * otherwise we only expire the old one if it exists + * It's the calling functions job to determine if a new one is required + * - i.e. if there is a difference between the old and new + * already = already begun a transaction - and don't update the ram table */ +bool miningpayouts_add(PGconn *conn, bool add, K_ITEM *mp_item, + K_ITEM **old_mp_item, char *by, char *code, char *inet, + tv_t *cd, K_TREE *trf_root, bool already) { ExecStatusType rescode; bool conned = false; PGresult *res; - K_ITEM *m_item, *u_item; - bool ok = false; - MININGPAYOUTS *row; - USERS *users; - char *ins; - char *params[5 + HISTORYDATECOUNT]; + bool ok = false, begun = false; + MININGPAYOUTS *row, *oldmp = NULL; + char *upd, *ins; + char *params[4 + HISTORYDATECOUNT]; int n, par = 0; - LOGDEBUG("%s(): add", __func__); + LOGDEBUG("%s(): add %c already %c", __func__, + add ? 'Y' : 'N', already ? 'Y' : 'N'); - K_WLOCK(miningpayouts_free); - m_item = k_unlink_head(miningpayouts_free); - K_WUNLOCK(miningpayouts_free); - - DATA_MININGPAYOUTS(row, m_item); + DATA_MININGPAYOUTS(row, mp_item); + K_RLOCK(miningpayouts_free); + *old_mp_item = find_miningpayouts(row->payoutid, row->userid); + K_RUNLOCK(miningpayouts_free); - if (conn == NULL) { - conn = dbconnect(); - conned = true; + conned = CKPQConn(&conn); + if (!already) { + begun = CKPQBegin(conn); + if (!begun) + goto unparam; } + + if (*old_mp_item) { + LOGDEBUG("%s(): updating old", __func__); - row->miningpayoutid = nextid(conn, "miningpayoutid", (int64_t)1, cd, by, code, inet); - if (row->miningpayoutid == 0) - goto unitem; + DATA_MININGPAYOUTS(oldmp, *old_mp_item); - K_RLOCK(users_free); - u_item = find_users(username); - K_RUNLOCK(users_free); - if (!u_item) { - char *txt; - LOGERR("%s(): unknown user '%s'", - __func__, - txt = safe_text(username)); - free(txt); - goto unitem; + upd = "update miningpayouts set expirydate=$1 where payoutid=$2" + " and userid=$3 and expirydate=$4"; + par = 0; + params[par++] = tv_to_buf(cd, NULL, 0); + params[par++] = bigint_to_buf(oldmp->payoutid, NULL, 0); + params[par++] = bigint_to_buf(oldmp->userid, NULL, 0); + params[par++] = tv_to_buf((tv_t *)&default_expiry, NULL, 0); + PARCHKVAL(par, 4, params); + + res = PQexecParams(conn, upd, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + PQclear(res); + if (!PGOK(rescode)) { + PGLOGERR("Update", rescode, conn); + goto rollback; + } + + for (n = 0; n < par; n++) + free(params[n]); + par = 0; } - DATA_USERS(users, u_item); - row->userid = users->userid; - TXT_TO_INT("height", height, row->height); - STRNCPY(row->blockhash, blockhash); - TXT_TO_BIGINT("amount", amount, row->amount); + if (add) { + LOGDEBUG("%s(): adding new", __func__); - HISTORYDATEINIT(row, cd, by, code, inet); - HISTORYDATETRANSFER(trf_root, row); + HISTORYDATEINIT(row, cd, by, code, inet); + HISTORYDATETRANSFER(trf_root, row); - par = 0; - params[par++] = bigint_to_buf(row->miningpayoutid, NULL, 0); - params[par++] = bigint_to_buf(row->userid, NULL, 0); - params[par++] = int_to_buf(row->height, NULL, 0); - params[par++] = str_to_buf(row->blockhash, NULL, 0); - params[par++] = bigint_to_buf(row->amount, NULL, 0); - HISTORYDATEPARAMS(params, par, row); - PARCHK(par, params); + par = 0; + params[par++] = bigint_to_buf(row->payoutid, NULL, 0); + params[par++] = bigint_to_buf(row->userid, NULL, 0); + params[par++] = double_to_buf(row->diffacc, NULL, 0); + params[par++] = bigint_to_buf(row->amount, NULL, 0); + HISTORYDATEPARAMS(params, par, row); + PARCHK(par, params); - ins = "insert into miningpayouts " - "(miningpayoutid,userid,height,blockhash,amount" - HISTORYDATECONTROL ") values (" PQPARAM10 ")"; + ins = "insert into miningpayouts " + "(payoutid,userid,diffacc,amount" + HISTORYDATECONTROL ") values (" PQPARAM9 ")"; - res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); - rescode = PQresultStatus(res); - if (!PGOK(rescode)) { - PGLOGERR("Insert", rescode, conn); - goto unparam; + res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + if (!PGOK(rescode)) { + PGLOGERR("Insert", rescode, conn); + goto unparam; + } } ok = true; - +rollback: + if (begun) + CKPQEnd(conn, ok); unparam: - PQclear(res); for (n = 0; n < par; n++) free(params[n]); -unitem: - if (conned) - PQfinish(conn); - K_WLOCK(miningpayouts_free); - if (!ok) - k_add_head(miningpayouts_free, m_item); - else { - miningpayouts_root = add_to_ktree(miningpayouts_root, m_item, cmp_miningpayouts); - k_add_head(miningpayouts_store, m_item); - } - K_WUNLOCK(miningpayouts_free); + + CKPQDisco(&conn, conned); + + if (!already) + miningpayouts_add_ram(ok, mp_item, *old_mp_item, cd); return ok; } @@ -4397,23 +4658,19 @@ bool miningpayouts_fill(PGconn *conn) PGresult *res; K_ITEM *item; MININGPAYOUTS *row; - char *params[1]; - int n, i, par = 0; + int n, i; char *field; char *sel; - int fields = 5; + int fields = 4; bool ok; LOGDEBUG("%s(): select", __func__); sel = "select " - "miningpayoutid,userid,height,blockhash,amount" + "payoutid,userid,diffacc,amount" HISTORYDATECONTROL - " from miningpayouts where expirydate=$1"; - par = 0; - params[par++] = tv_to_buf((tv_t *)(&default_expiry), NULL, 0); - PARCHK(par, params); - res = PQexecParams(conn, sel, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_READ); + " from miningpayouts"; + res = PQexec(conn, sel, CKPQ_READ); rescode = PQresultStatus(res); if (!PGOK(rescode)) { PGLOGERR("Select", rescode, conn); @@ -4442,25 +4699,20 @@ bool miningpayouts_fill(PGconn *conn) break; } - PQ_GET_FLD(res, i, "miningpayoutid", field, ok); + PQ_GET_FLD(res, i, "payoutid", field, ok); if (!ok) break; - TXT_TO_BIGINT("miningpayoutid", field, row->miningpayoutid); + TXT_TO_BIGINT("payoutid", field, row->payoutid); PQ_GET_FLD(res, i, "userid", field, ok); if (!ok) break; TXT_TO_BIGINT("userid", field, row->userid); - PQ_GET_FLD(res, i, "height", field, ok); - if (!ok) - break; - TXT_TO_INT("height", field, row->height); - - PQ_GET_FLD(res, i, "blockhash", field, ok); + PQ_GET_FLD(res, i, "diffacc", field, ok); if (!ok) break; - TXT_TO_STR("blockhash", field, row->blockhash); + TXT_TO_DOUBLE("diffacc", field, row->diffacc); PQ_GET_FLD(res, i, "amount", field, ok); if (!ok) @@ -4490,6 +4742,292 @@ bool miningpayouts_fill(PGconn *conn) return ok; } +// The timing of the memory table updates depends on 'already' +void payouts_add_ram(bool ok, K_ITEM *p_item, K_ITEM *old_p_item, tv_t *cd) +{ + PAYOUTS *oldp; + + LOGDEBUG("%s(): ok %c", __func__, ok ? 'Y' : 'N'); + + K_WLOCK(payouts_free); + if (!ok) { + // Cleanup for the calling function + k_add_head(payouts_free, p_item); + } else { + if (old_p_item) { + DATA_PAYOUTS(oldp, old_p_item); + payouts_root = remove_from_ktree(payouts_root, old_p_item, cmp_payouts); + payouts_id_root = remove_from_ktree(payouts_id_root, old_p_item, cmp_payouts_id); + copy_tv(&(oldp->expirydate), cd); + payouts_root = add_to_ktree(payouts_root, old_p_item, cmp_payouts); + payouts_id_root = add_to_ktree(payouts_id_root, old_p_item, cmp_payouts_id); + } + payouts_root = add_to_ktree(payouts_root, p_item, cmp_payouts); + payouts_id_root = add_to_ktree(payouts_id_root, p_item, cmp_payouts_id); + k_add_head(payouts_store, p_item); + } + K_WUNLOCK(payouts_free); +} + +/* Add means create a new one and expire the old one if it exists, + * otherwise we only expire the old one if it exists + * It's the calling functions job to determine if a new one is required + * - i.e. if there is a difference between the old and new + * already = already begun a transaction - and don't update the ram table */ +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) +{ + ExecStatusType rescode; + bool conned = false; + PGresult *res; + bool ok = false, begun = false; + PAYOUTS *row, *oldpayouts = NULL; + char *upd, *ins; + char *params[13 + HISTORYDATECOUNT]; + int n, par = 0; + + LOGDEBUG("%s(): add %c already %c", __func__, + add ? 'Y' : 'N', already ? 'Y' : 'N'); + + DATA_PAYOUTS(row, p_item); + K_RLOCK(payouts_free); + *old_p_item = find_payouts(row->height, row->blockhash); + K_RUNLOCK(payouts_free); + + conned = CKPQConn(&conn); + if (!already) { + begun = CKPQBegin(conn); + if (!begun) + goto unparam; + } + + if (*old_p_item) { + LOGDEBUG("%s(): updating old", __func__); + + DATA_PAYOUTS(oldpayouts, *old_p_item); + + upd = "update payouts set expirydate=$1 where payoutid=$2" + " and expirydate=$3"; + par = 0; + params[par++] = tv_to_buf(cd, NULL, 0); + params[par++] = bigint_to_buf(oldpayouts->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); + PQclear(res); + if (!PGOK(rescode)) { + PGLOGERR("Update", rescode, conn); + goto rollback; + } + + for (n = 0; n < par; n++) + free(params[n]); + par = 0; + + // Expiring an old record + row->payoutid = oldpayouts->payoutid; + } else { + if (add) { + // Creating a new record + row->payoutid = nextid(conn, "payoutid", (int64_t)1, cd, by, code, inet); + if (row->payoutid == 0) + goto rollback; + } + } + + if (add) { + LOGDEBUG("%s(): adding new", __func__); + + HISTORYDATEINIT(row, cd, by, code, inet); + HISTORYDATETRANSFER(trf_root, row); + + par = 0; + params[par++] = bigint_to_buf(row->payoutid, NULL, 0); + params[par++] = int_to_buf(row->height, NULL, 0); + params[par++] = str_to_buf(row->blockhash, NULL, 0); + params[par++] = bigint_to_buf(row->minerreward, NULL, 0); + params[par++] = bigint_to_buf(row->workinfoidstart, NULL, 0); + params[par++] = bigint_to_buf(row->workinfoidend, NULL, 0); + params[par++] = bigint_to_buf(row->elapsed, NULL, 0); + params[par++] = str_to_buf(row->status, NULL, 0); + params[par++] = double_to_buf(row->diffwanted, NULL, 0); + params[par++] = double_to_buf(row->diffused, NULL, 0); + params[par++] = double_to_buf(row->shareacc, NULL, 0); + params[par++] = tv_to_buf(&(row->lastshareacc), NULL, 0); + params[par++] = str_to_buf(row->stats, NULL, 0); + HISTORYDATEPARAMS(params, par, row); + PARCHK(par, params); + + ins = "insert into payouts " + "(payoutid,height,blockhash,minerreward,workinfoidstart," + "workinfoidend,elapsed,status,diffwanted,diffused,shareacc," + "lastshareacc,stats" + HISTORYDATECONTROL ") values (" PQPARAM18 ")"; + + res = PQexecParams(conn, ins, par, NULL, (const char **)params, NULL, NULL, 0, CKPQ_WRITE); + rescode = PQresultStatus(res); + if (!PGOK(rescode)) { + PGLOGERR("Insert", rescode, conn); + goto unparam; + } + } + + ok = true; +rollback: + if (begun) + CKPQEnd(conn, ok); +unparam: + for (n = 0; n < par; n++) + free(params[n]); + + CKPQDisco(&conn, conned); + + if (!already) + payouts_add_ram(ok, p_item, *old_p_item, cd); + + return ok; +} + +bool payouts_fill(PGconn *conn) +{ + ExecStatusType rescode; + PGresult *res; + K_ITEM *item; + PAYOUTS *row; + int n, i; + char *field; + char *sel; + int fields = 13; + bool ok; + + LOGDEBUG("%s(): select", __func__); + + sel = "select " + "payoutid,height,blockhash,minerreward,workinfoidstart,workinfoidend," + "elapsed,status,diffwanted,diffused,shareacc,lastshareacc,stats" + HISTORYDATECONTROL + " from payouts"; + res = PQexec(conn, sel, CKPQ_READ); + rescode = PQresultStatus(res); + if (!PGOK(rescode)) { + PGLOGERR("Select", rescode, conn); + PQclear(res); + return false; + } + + n = PQnfields(res); + if (n != (fields + HISTORYDATECOUNT)) { + LOGERR("%s(): Invalid field count - should be %d, but is %d", + __func__, fields + HISTORYDATECOUNT, n); + PQclear(res); + return false; + } + + n = PQntuples(res); + LOGDEBUG("%s(): tree build count %d", __func__, n); + ok = true; + K_WLOCK(payouts_free); + for (i = 0; i < n; i++) { + item = k_unlink_head(payouts_free); + DATA_PAYOUTS(row, item); + + if (everyone_die) { + ok = false; + break; + } + + PQ_GET_FLD(res, i, "payoutid", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("payoutid", field, row->payoutid); + + PQ_GET_FLD(res, i, "height", field, ok); + if (!ok) + break; + TXT_TO_INT("height", field, row->height); + + PQ_GET_FLD(res, i, "blockhash", field, ok); + if (!ok) + break; + TXT_TO_STR("blockhash", field, row->blockhash); + + PQ_GET_FLD(res, i, "minerreward", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("minerreward", field, row->minerreward); + + PQ_GET_FLD(res, i, "workinfoidstart", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("workinfoidstart", field, row->workinfoidstart); + + PQ_GET_FLD(res, i, "workinfoidend", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("workinfoidend", field, row->workinfoidend); + + PQ_GET_FLD(res, i, "elapsed", field, ok); + if (!ok) + break; + TXT_TO_BIGINT("elapsed", field, row->elapsed); + + PQ_GET_FLD(res, i, "status", field, ok); + if (!ok) + break; + TXT_TO_STR("status", field, row->status); + + PQ_GET_FLD(res, i, "diffwanted", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("diffwanted", field, row->diffwanted); + + PQ_GET_FLD(res, i, "diffused", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("diffused", field, row->diffused); + + PQ_GET_FLD(res, i, "shareacc", field, ok); + if (!ok) + break; + TXT_TO_DOUBLE("shareacc", field, row->shareacc); + + PQ_GET_FLD(res, i, "lastshareacc", field, ok); + if (!ok) + break; + TXT_TO_TV("lastshareacc", field, row->lastshareacc); + + PQ_GET_FLD(res, i, "stats", field, ok); + if (!ok) + break; + TXT_TO_BLOB("stats", field, row->stats); + + HISTORYDATEFLDS(res, i, row, ok); + if (!ok) + break; + + payouts_root = add_to_ktree(payouts_root, item, cmp_payouts); + payouts_id_root = add_to_ktree(payouts_id_root, item, cmp_payouts_id); + k_add_head(payouts_store, item); + + tick(); + } + if (!ok) + k_add_head(payouts_free, item); + + K_WUNLOCK(payouts_free); + PQclear(res); + + if (ok) { + LOGDEBUG("%s(): built", __func__); + LOGWARNING("%s(): loaded %d payout records", __func__, n); + } + + return ok; +} + // TODO: discard them from RAM bool auths_add(PGconn *conn, char *poolinstance, char *username, char *workername, char *clientid, char *enonce1, @@ -5312,7 +5850,8 @@ bool _workmarkers_process(PGconn *conn, bool already, bool add, bool ok = false, begun = false; int n, par = 0; - LOGDEBUG("%s(): add", __func__); + LOGDEBUG("%s(): add %c already %c", __func__, + add ? 'Y' : 'N', already ? 'Y' : 'N'); if (markerid == 0) { K_RLOCK(workmarkers_free); @@ -5324,6 +5863,8 @@ bool _workmarkers_process(PGconn *conn, bool already, bool add, K_RUNLOCK(workmarkers_free); } if (old_wm_item) { + LOGDEBUG("%s(): updating old", __func__); + DATA_WORKMARKERS(oldworkmarkers, old_wm_item); if (!conn) { conn = dbconnect(); @@ -5363,6 +5904,8 @@ bool _workmarkers_process(PGconn *conn, bool already, bool add, } if (add) { + LOGDEBUG("%s(): adding new", __func__); + if (poolinstance == NULL || description == NULL || status == NULL) { LOGEMERG("%s(): NULL field(s) passed:%s%s%s" @@ -5639,12 +6182,14 @@ bool _marks_process(PGconn *conn, bool add, char *poolinstance, bool ok = false, begun = false; int n, par = 0; - LOGDEBUG("%s(): add", __func__); + LOGDEBUG("%s(): add %c", __func__, add ? 'Y' : 'N'); K_RLOCK(marks_free); old_m_item = find_marks(workinfoid); K_RUNLOCK(marks_free); if (old_m_item) { + LOGDEBUG("%s(): updating old", __func__); + DATA_MARKS(oldmarks, old_m_item); if (!conn) { conn = dbconnect(); @@ -5682,6 +6227,8 @@ bool _marks_process(PGconn *conn, bool add, char *poolinstance, } if (add) { + LOGDEBUG("%s(): adding new", __func__); + if (poolinstance == NULL || description == NULL || extra == NULL || marktype == NULL || status == NULL) { LOGEMERG("%s(): NULL field(s) passed:%s%s%s%s%s" From 1380acddfb085d06b708ec3bb871e201f08ba3bf Mon Sep 17 00:00:00 2001 From: kanoi Date: Fri, 27 Feb 2015 00:06:46 +1100 Subject: [PATCH 30/39] ckdb - cmd_payouts for setting up old payouts --- src/ckdb.c | 2 + src/ckdb.h | 7 ++- src/ckdb_cmd.c | 161 +++++++++++++++++++++++++++++++++++++++++++++++- src/ckdb_data.c | 51 +++++++-------- 4 files changed, 193 insertions(+), 28 deletions(-) diff --git a/src/ckdb.c b/src/ckdb.c index 4dca76ad..222682db 100644 --- a/src/ckdb.c +++ b/src/ckdb.c @@ -2662,6 +2662,7 @@ static void *socketer(__maybe_unused void *arg) case CMD_PAYMENTS: case CMD_PPLNS: case CMD_PPLNS2: + case CMD_PAYOUTS: case CMD_DSP: case CMD_BLOCKSTATUS: if (!startup_complete) { @@ -2880,6 +2881,7 @@ static bool reload_line(PGconn *conn, char *filename, uint64_t count, char *buf) case CMD_STATS: case CMD_PPLNS: case CMD_PPLNS2: + case CMD_PAYOUTS: case CMD_USERSTATUS: case CMD_MARKS: LOGERR("%s() Message line %"PRIu64" '%s' - invalid - ignored", diff --git a/src/ckdb.h b/src/ckdb.h index 7f65e817..3a529a49 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -52,7 +52,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.000" +#define CKDB_VERSION DB_VERSION"-1.001" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ @@ -340,7 +340,7 @@ enum cmd_values { CMD_STATS, CMD_PPLNS, CMD_PPLNS2, - CMD_PAYOUT, + CMD_PAYOUTS, CMD_USERSTATUS, CMD_MARKS, CMD_END @@ -1182,6 +1182,7 @@ extern K_LIST *payouts_free; extern K_STORE *payouts_store; extern cklock_t process_pplns_lock; +// N.B. status should be checked under r/w lock #define PAYOUTS_GENERATED 'G' #define PAYOUTS_GENERATED_STR "G" #define PAYGENERATED(_status) ((_status)[0] == PAYOUTS_GENERATED) @@ -1754,7 +1755,7 @@ extern cmp_t cmp_payouts_id(K_ITEM *a, K_ITEM *b); extern K_ITEM *find_payouts(int32_t height, char *blockhash); extern K_ITEM *find_last_payouts(); extern K_ITEM *find_payoutid(int64_t payoutid); -extern void process_pplns(int32_t height, char *blockhash, tv_t *now); +extern bool process_pplns(int32_t height, char *blockhash, tv_t *now); extern cmp_t cmp_auths(K_ITEM *a, K_ITEM *b); extern cmp_t cmp_poolstats(K_ITEM *a, K_ITEM *b); extern void dsp_userstats(K_ITEM *item, FILE *stream); diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index 5d1bcba0..c7f89e79 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -4030,6 +4030,165 @@ 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)); + payouts2->stats = strdup(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); + } + 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, payouts2->status, old_payouts2->status, + payouts2->height, payouts2->blockhash); +/* + } else if (strcasecmp(action, "expire") == 0) { + / TODO: Expire the payout - effectively deletes it + * Require payoutid + * 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); + if (!p_item) { + K_WUNLOCK(payouts_free); + snprintf(reply, siz, + "no payout with id %"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; + + ... +*/ + } 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 for 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); + + if (addrdate.tv_sec == 0) + ok = process_pplns(height, blockhash, NULL); + else + ok = process_pplns(height, blockhash, &addrdate); + + } 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_dsp(__maybe_unused PGconn *conn, __maybe_unused char *cmd, char *id, __maybe_unused tv_t *now, __maybe_unused char *by, __maybe_unused char *code, @@ -4690,7 +4849,7 @@ struct CMDS ckdb_cmds[] = { { CMD_STATS, "stats", true, false, cmd_stats, ACCESS_SYSTEM ACCESS_WEB }, { CMD_PPLNS, "pplns", false, false, cmd_pplns, ACCESS_SYSTEM ACCESS_WEB }, { CMD_PPLNS2, "pplns2", false, false, cmd_pplns2, ACCESS_SYSTEM ACCESS_WEB }, -// { CMD_PAYOUT, "payout", false, false, cmd_payout, ACCESS_SYSTEM }, + { CMD_PAYOUTS, "payouts", false, false, cmd_payouts, ACCESS_SYSTEM }, { CMD_USERSTATUS,"userstatus", false, false, cmd_userstatus, ACCESS_SYSTEM ACCESS_WEB }, { CMD_MARKS, "marks", false, false, cmd_marks, ACCESS_SYSTEM }, { CMD_END, NULL, false, false, NULL, NULL } diff --git a/src/ckdb_data.c b/src/ckdb_data.c index 3d227af9..ce38a11a 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -2557,11 +2557,11 @@ K_ITEM *find_payoutid(int64_t payoutid) N.B. process_pplns() is only automatically triggered once after the block summarisation is verified, so it can always report all errors */ -void process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) +bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) { K_TREE_CTX b_ctx[1], ss_ctx[1], wm_ctx[1], ms_ctx[1], pay_ctx[1], mu_ctx[1]; bool allow_aged = true, conned = false, begun = false; - bool countbacklimit, ok; + bool countbacklimit, ok = false; PGconn *conn = NULL; MININGPAYOUTS *miningpayouts; OPTIONCONTROL *optioncontrol; @@ -2586,7 +2586,8 @@ void process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) char ndiffbin[TXT_SML+1]; double diff_times, diff_add; char cd_buf[CDATE_BUFSIZ]; - tv_t begin_tv, end_tv, now; + tv_t end_tv = { 0L, 0L }; + tv_t begin_tv, now; char buf[1024]; /* @@ -2624,30 +2625,30 @@ void process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) goto oku; } DATA_BLOCKS(blocks, b_item); - // If addr_cd is null, use the block NEW createdate - if (!addr_cd) { - b2_item = b_item; - DATA_BLOCKS(blocks2, b2_item); - while (b2_item && blocks2->height == height && - strcmp(blocks2->blockhash, blockhash) == 0) { - if (blocks2->confirmed[0] == BLOCKS_NEW) { + b2_item = b_item; + DATA_BLOCKS(blocks2, b2_item); + while (b2_item && blocks2->height == height && + strcmp(blocks2->blockhash, blockhash) == 0) { + if (blocks2->confirmed[0] == BLOCKS_NEW) { + copy_tv(&end_tv, &(blocks->createdate)); + if (!addr_cd) addr_cd = &(blocks2->createdate); - break; - } - b2_item = next_in_ktree(b_ctx); - DATA_BLOCKS_NULL(blocks2, b2_item); - } - if (!addr_cd) { - K_RUNLOCK(blocks_free); - LOGEMERG("%s(): missing NEW record for block %"PRId32 - "/%"PRId64"/%s/%s/%"PRId64, - __func__, blocks->height, blocks->workinfoid, - blocks->workername, blocks->confirmed, - blocks->reward); - goto oku; + break; } + b2_item = next_in_ktree(b_ctx); + DATA_BLOCKS_NULL(blocks2, b2_item); } K_RUNLOCK(blocks_free); + // If addr_cd was null it should've been set to the block NEW createdate + if (!addr_cd || end_tv.tv_sec == 0) { + LOGEMERG("%s(): missing %s record for block %"PRId32 + "/%"PRId64"/%s/%s/%"PRId64, + __func__, blocks_confirmed(BLOCKS_NEW_STR), + blocks->height, blocks->workinfoid, + blocks->workername, blocks->confirmed, + blocks->reward); + goto oku; + } LOGDEBUG("%s(): block %"PRId32"/%"PRId64"/%s/%s/%"PRId64, __func__, blocks->height, blocks->workinfoid, @@ -3211,6 +3212,8 @@ void process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) goto oku; shazbot: + ok = false; + if (begun) CKPQEnd(conn, false); CKPQDisco(&conn, conned); @@ -3250,7 +3253,7 @@ oku: } addr_store = k_free_store(addr_store); } - return; + return ok; } // order by userid asc,createdate asc,authid asc,expirydate desc From c451119f258c06c9d076ddb07c6bdab509b9a3e9 Mon Sep 17 00:00:00 2001 From: kanoi Date: Fri, 27 Feb 2015 00:27:35 +1100 Subject: [PATCH 31/39] ckdb - correct status order in cmd_payouts message --- src/ckdb.h | 2 +- src/ckdb_cmd.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ckdb.h b/src/ckdb.h index 3a529a49..53a09f2d 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -52,7 +52,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.001" +#define CKDB_VERSION DB_VERSION"-1.002" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index c7f89e79..2bc67399 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -4115,7 +4115,7 @@ static char *cmd_payouts(PGconn *conn, char *cmd, char *id, tv_t *now, snprintf(msg, sizeof(msg), "payout %"PRId64" changed from '%s' to '%s' for" "%"PRId32"/%s", - payoutid, payouts2->status, old_payouts2->status, + payoutid, old_payouts2->status, payouts2->status, payouts2->height, payouts2->blockhash); /* } else if (strcasecmp(action, "expire") == 0) { From cda0724c6d993022ab2c9db37c1d679fc830dec6 Mon Sep 17 00:00:00 2001 From: kanoi Date: Fri, 27 Feb 2015 11:56:32 +1100 Subject: [PATCH 32/39] =?UTF-8?q?ckdb=20-=20=E2=88=9E=20loops=20are=20bad?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ckdb.h | 2 +- src/ckdb_data.c | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ckdb.h b/src/ckdb.h index 53a09f2d..3481ba62 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -52,7 +52,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.002" +#define CKDB_VERSION DB_VERSION"-1.003" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ diff --git a/src/ckdb_data.c b/src/ckdb_data.c index ce38a11a..10f5625e 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -3032,10 +3032,9 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) STRNCPY(pa2->payaddress, pa->payaddress); pa2->payratio = pa->payratio; k_add_tail(addr_store, pa_item2); - - pa_item = next_in_ktree(pay_ctx); - DATA_PAYMENTADDRESSES_NULL(pa, pa_item); } + pa_item = next_in_ktree(pay_ctx); + DATA_PAYMENTADDRESSES_NULL(pa, pa_item); } } K_WUNLOCK(paymentaddresses_free); From 5afedffc5accb53580e938628c733c0af7050c94 Mon Sep 17 00:00:00 2001 From: kanoi Date: Fri, 27 Feb 2015 13:10:07 +1100 Subject: [PATCH 33/39] ckdb - store missing diffacc in addressuser payments --- src/ckdb.h | 2 +- src/ckdb_cmd.c | 2 +- src/ckdb_data.c | 31 ++++++++++++++++++++++++------- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/ckdb.h b/src/ckdb.h index 3481ba62..d817ef9b 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -52,7 +52,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.003" +#define CKDB_VERSION DB_VERSION"-1.004" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index 2bc67399..14606a80 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -4150,7 +4150,7 @@ static char *cmd_payouts(PGconn *conn, char *cmd, char *id, tv_t *now, * 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 for payoutaddresses + * 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); diff --git a/src/ckdb_data.c b/src/ckdb_data.c index 10f5625e..b835798a 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -2527,13 +2527,15 @@ K_ITEM *find_payoutid(int64_t payoutid) then keep stepping back until we complete the current begin_workinfoid (also summarising diffacc per user) While we are still below diff_want - find each workmarker and add on the full set of worksummary + find each next workmarker and add on the full set of worksummary diffacc shares (also summarising diffacc per user) This will give us the total number of diff1 shares (diffacc_total) to use for the payment calculations The value of diff_want defaults to the block's network difficulty (block_ndiff) but can be changed with diff_times and diff_add to: block_ndiff * diff_times + diff_add + they are stored in the optioncontrol table and thus can use the + block number to change their values over time N.B. diff_times and diff_add can be zero, positive or negative The pplns_elapsed time of the shares is from the createdate of the begin_workinfoid that has shares accounted to the total, @@ -2547,12 +2549,19 @@ K_ITEM *find_payoutid(int64_t payoutid) 'end' should usually be the info of the found block with the pplns data going back in time to 'begin' - The data processing procedure is to create a separate tree/store of - miningpayouts, create a seperate store of payments, - then actually create the payout record and transfer the miningpayouts - and payments to the ram table and the db + The data processing procedure is to: + create a separate tree/store of miningpayouts during the diff_used + calculation, + store the payout in the db with a 'processing' status, + create a seperate store of payments per miningpayout that are stored + in the db, + store each mininging payout in the db after storing the payments for the + given miningpayout, + commit that all and if it succeeds then update the ram tables for all + of the above + then update the payout status, in the db and ram, to 'generated' - TODO: recheck the payout if it already exists + TODO: recheck the payout if it already exists? N.B. process_pplns() is only automatically triggered once after the block summarisation is verified, so it can always report all errors @@ -3014,6 +3023,13 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) pay_ctx); if (pa_item) { DATA_PAYMENTADDRESSES(pa, pa_item); + /* The tv_newer and tv_newer_eq are critical since: + * when a record is replaced, the expirydate is set + * to 'now' and the new record will have the same + * createdate of 'now', so to avoid possibly selecting + * both records, we get the one that was created + * before addr_cd and expires on or after addr_cd + */ while (pa_item && pa->userid == miningpayouts->userid && tv_newer(&(pa->createdate), addr_cd)) { if (tv_newer_eq(addr_cd, &(pa->expirydate))) { @@ -3102,8 +3118,9 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) "%s.0", users->username); STRNCPY(payments->payaddress, users->username); payments->amount = amount; + payments->diffacc = miningpayouts->diffacc; used = amount; - k_add_head(pay_store, pay_item); + k_add_tail(pay_store, pay_item); ok = payments_add(conn, true, pay_item, &(payments->old_item), (char *)by_default, From e30cc81cad143b168e4ff1088446247172aa882a Mon Sep 17 00:00:00 2001 From: kanoi Date: Fri, 27 Feb 2015 14:10:23 +1100 Subject: [PATCH 34/39] ckdb/php - add extra pplns2 epoch information like pplns --- pool/page_pplns2.php | 12 ++++---- src/ckdb.h | 2 +- src/ckdb_cmd.c | 71 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 75 insertions(+), 10 deletions(-) diff --git a/pool/page_pplns2.php b/pool/page_pplns2.php index 01c4c4ca..781fab51 100644 --- a/pool/page_pplns2.php +++ b/pool/page_pplns2.php @@ -188,14 +188,14 @@ Block: 'Elapsed Seconds' => ',pplns_elapsed', 'Users' => 'rows', 'Oldest Workinfoid' => 'begin_workinfoid', -# 'Oldest Time' => 'begin_stamp', -# 'Oldest Epoch' => 'begin_epoch', + 'Oldest Time' => 'begin_stamp', + 'Oldest Epoch' => 'begin_epoch', 'Block Workinfoid' => 'block_workinfoid', -# 'Block Time' => 'block_stamp', -# 'Block Epoch' => 'block_epoch', + 'Block Time' => 'block_stamp', + 'Block Epoch' => 'block_epoch', 'Newest Workinfoid' => 'end_workinfoid', -# 'Newest Share Time' => 'end_stamp', -# 'Newest Share Epoch' => 'end_epoch', + 'Newest Share Time' => 'end_stamp', + 'Newest Share Epoch' => 'end_epoch', 'Network Difficulty' => 'block_ndiff', 'PPLNS Factor' => 'diff_times', 'PPLNS Added' => 'diff_add', diff --git a/src/ckdb.h b/src/ckdb.h index d817ef9b..b11bb0a9 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -52,7 +52,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.004" +#define CKDB_VERSION DB_VERSION"-1.005" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ diff --git a/src/ckdb_cmd.c b/src/ckdb_cmd.c index 14606a80..a9c32c77 100644 --- a/src/ckdb_cmd.c +++ b/src/ckdb_cmd.c @@ -3828,13 +3828,19 @@ static char *cmd_pplns2(__maybe_unused PGconn *conn, char *cmd, char *id, 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; + tv_t block_tv = { 0L, 0L }; + WORKINFO *bworkinfo, *workinfo; + char ndiffbin[TXT_SML+1]; + double ndiff; 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; @@ -3851,18 +3857,38 @@ static char *cmd_pplns2(__maybe_unused PGconn *conn, char *cmd, char *id, LOGDEBUG("%s(): height %"PRId32, __func__, height); - lookblocks.height = height; + lookblocks.height = height + 1; 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, cmp_blocks, b_ctx); + b_item = find_before_in_ktree(blocks_root, &b_look, cmp_blocks, b_ctx); + if (!b_item) { + K_RUNLOCK(blocks_free); + snprintf(reply, siz, "ERR.no block height %"PRId32, height); + return strdup(reply); + } + DATA_BLOCKS(blocks, b_item); + while (b_item && blocks->height == height) { + if (blocks->confirmed[0] == BLOCKS_NEW) + copy_tv(&block_tv, &(blocks->createdate)); + // Allow any state, but report it + if (CURRENT(&(blocks->expirydate))) + break; + b_item = prev_in_ktree(b_ctx); + DATA_BLOCKS_NULL(blocks, b_item); + } K_RUNLOCK(blocks_free); - DATA_BLOCKS_NULL(blocks, b_item); if (!b_item || blocks->height != height) { snprintf(reply, siz, "ERR.no block height %"PRId32, height); return strdup(reply); } + if (block_tv.tv_sec == 0) { + snprintf(reply, siz, "ERR.block %"PRId32" missing '%s' record", + height, + blocks_confirmed(BLOCKS_NEW_STR)); + return strdup(reply); + } if (!CURRENT(&(blocks->expirydate))) { snprintf(reply, siz, "ERR.no CURRENT block %d"PRId32, height); return strdup(reply); @@ -3882,6 +3908,15 @@ static char *cmd_pplns2(__maybe_unused PGconn *conn, char *cmd, char *id, 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); @@ -3905,6 +3940,15 @@ static char *cmd_pplns2(__maybe_unused PGconn *conn, char *cmd, char *id, 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); @@ -4012,8 +4056,29 @@ static char *cmd_pplns2(__maybe_unused PGconn *conn, char *cmd, char *id, "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(&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(&(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); + hex2bin(ndiffbin, bworkinfo->bits, 4); + ndiff = diff_from_nbits(ndiffbin); + snprintf(tmp, sizeof(tmp), "block_ndiff=%f%c", ndiff, FLDSEP); + APPEND_REALLOC(buf, off, len, tmp); snprintf(tmp, sizeof(tmp), "diff_want=%.1f%c", payouts->diffwanted, FLDSEP); APPEND_REALLOC(buf, off, len, tmp); From 59e7d2b7bc0de8d748630a77246fb5c3dc8d089e Mon Sep 17 00:00:00 2001 From: kanoi Date: Fri, 27 Feb 2015 16:33:29 +1100 Subject: [PATCH 35/39] ckdb - pplns2 include the trailing shares in the tv_end check --- src/ckdb.h | 2 +- src/ckdb_data.c | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ckdb.h b/src/ckdb.h index b11bb0a9..9e84b3da 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -52,7 +52,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.005" +#define CKDB_VERSION DB_VERSION"-1.006" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ diff --git a/src/ckdb_data.c b/src/ckdb_data.c index b835798a..f7cc3653 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -2824,6 +2824,10 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) total_share_count += sharesummary->sharecount; acc_share_count += sharesummary->shareacc; total_diff += sharesummary->diffacc; + // TODO: add lastshareacc to sharesummary and markersummary + if (sharesummary->shareacc > 0 && + tv_newer(&end_tv, &(sharesummary->lastshare))) + copy_tv(&end_tv, &(sharesummary->lastshare)); mu_root = upd_add_mu(mu_root, mu_store, sharesummary->userid, sharesummary->diffacc); From 122d6f38bac92363457dab30e0082e8e2a1449e3 Mon Sep 17 00:00:00 2001 From: kanoi Date: Fri, 27 Feb 2015 16:34:45 +1100 Subject: [PATCH 36/39] ckdb - correct tv_end for pplns2 block --- src/ckdb.h | 2 +- src/ckdb_data.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ckdb.h b/src/ckdb.h index 9e84b3da..34c91f7c 100644 --- a/src/ckdb.h +++ b/src/ckdb.h @@ -52,7 +52,7 @@ #define DB_VLOCK "1" #define DB_VERSION "1.0.0" -#define CKDB_VERSION DB_VERSION"-1.006" +#define CKDB_VERSION DB_VERSION"-1.007" #define WHERE_FFL " - from %s %s() line %d" #define WHERE_FFL_HERE __FILE__, __func__, __LINE__ diff --git a/src/ckdb_data.c b/src/ckdb_data.c index f7cc3653..96649aa8 100644 --- a/src/ckdb_data.c +++ b/src/ckdb_data.c @@ -2639,7 +2639,7 @@ bool process_pplns(int32_t height, char *blockhash, tv_t *addr_cd) while (b2_item && blocks2->height == height && strcmp(blocks2->blockhash, blockhash) == 0) { if (blocks2->confirmed[0] == BLOCKS_NEW) { - copy_tv(&end_tv, &(blocks->createdate)); + copy_tv(&end_tv, &(blocks2->createdate)); if (!addr_cd) addr_cd = &(blocks2->createdate); break; From 3047ee5c8fe36e25449595066e547a5fe80d84a2 Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Fri, 27 Feb 2015 20:18:42 +1100 Subject: [PATCH 37/39] Use the more generic ckmsg_t for the json_lists --- src/stratifier.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/stratifier.c b/src/stratifier.c index 859edc6e..7c0ab397 100644 --- a/src/stratifier.c +++ b/src/stratifier.c @@ -366,14 +366,6 @@ struct stratifier_data { typedef struct stratifier_data sdata_t; -typedef struct json_entry json_entry_t; - -struct json_entry { - json_entry_t *next; - json_entry_t *prev; - json_t *val; -}; - /* Priority levels for generator messages */ #define GEN_LAX 0 #define GEN_NORMAL 1 @@ -3860,7 +3852,7 @@ out: * avoid floods of stat data coming at once. */ static void update_workerstats(ckpool_t *ckp, sdata_t *sdata) { - json_entry_t *json_list = NULL, *entry, *tmpentry; + ckmsg_t *json_list = NULL, *entry, *tmpentry; user_instance_t *user, *tmp; char cdfield[64]; time_t now_t; @@ -3921,8 +3913,8 @@ static void update_workerstats(ckpool_t *ckp, sdata_t *sdata) "createcode", __func__, "createinet", ckp->serverurl[0]); worker->notified_idle = worker->idle; - entry = ckalloc(sizeof(json_entry_t)); - entry->val = val; + entry = ckalloc(sizeof(ckmsg_t)); + entry->data = val; DL_APPEND(json_list, entry); } } @@ -3930,7 +3922,7 @@ static void update_workerstats(ckpool_t *ckp, sdata_t *sdata) /* Add all entries outside of the instance lock */ DL_FOREACH_SAFE(json_list, entry, tmpentry) { - ckdbq_add(ckp, ID_WORKERSTATS, entry->val); + ckdbq_add(ckp, ID_WORKERSTATS, (json_t *)entry->data); DL_DELETE(json_list, entry); free(entry); } From ca5c7da81b01d6cc77494756034c9db42a70b16c Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Fri, 27 Feb 2015 20:39:00 +1100 Subject: [PATCH 38/39] Provide helper functions for storing log text and their associated filenames to be written to in unlocked code --- src/ckpool.h | 9 +++++++++ src/stratifier.c | 46 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/ckpool.h b/src/ckpool.h index 771ac692..86b8ff3f 100644 --- a/src/ckpool.h +++ b/src/ckpool.h @@ -73,6 +73,15 @@ struct char_entry { char *buf; }; +typedef struct log_entry log_entry_t; + +struct log_entry { + log_entry_t *next; + log_entry_t *prev; + char *fname; + char *buf; +}; + struct server_instance { /* Hash table data */ UT_hash_handle hh; diff --git a/src/stratifier.c b/src/stratifier.c index 7c0ab397..be512f82 100644 --- a/src/stratifier.c +++ b/src/stratifier.c @@ -366,6 +366,14 @@ struct stratifier_data { typedef struct stratifier_data sdata_t; +typedef struct json_entry json_entry_t; + +struct json_entry { + json_entry_t *next; + json_entry_t *prev; + json_t *val; +}; + /* Priority levels for generator messages */ #define GEN_LAX 0 #define GEN_NORMAL 1 @@ -3852,7 +3860,7 @@ out: * avoid floods of stat data coming at once. */ static void update_workerstats(ckpool_t *ckp, sdata_t *sdata) { - ckmsg_t *json_list = NULL, *entry, *tmpentry; + json_entry_t *json_list = NULL, *entry, *tmpentry; user_instance_t *user, *tmp; char cdfield[64]; time_t now_t; @@ -3913,8 +3921,8 @@ static void update_workerstats(ckpool_t *ckp, sdata_t *sdata) "createcode", __func__, "createinet", ckp->serverurl[0]); worker->notified_idle = worker->idle; - entry = ckalloc(sizeof(ckmsg_t)); - entry->data = val; + entry = ckalloc(sizeof(json_entry_t)); + entry->val = val; DL_APPEND(json_list, entry); } } @@ -3922,12 +3930,42 @@ static void update_workerstats(ckpool_t *ckp, sdata_t *sdata) /* Add all entries outside of the instance lock */ DL_FOREACH_SAFE(json_list, entry, tmpentry) { - ckdbq_add(ckp, ID_WORKERSTATS, (json_t *)entry->data); + ckdbq_add(ckp, ID_WORKERSTATS, entry->val); DL_DELETE(json_list, entry); free(entry); } } +static void add_log_entry(log_entry_t *entries, char **fname, char **buf) +{ + log_entry_t *entry = ckalloc(sizeof(log_entry_t)); + + entry->fname = *fname; + *fname = NULL; + entry->buf = *buf; + *buf = NULL; + DL_APPEND(entries, entry); +} + +static void dump_log_entries(log_entry_t *entries) +{ + log_entry_t *entry, *tmpentry; + FILE *fp; + + DL_FOREACH_SAFE(entries, entry, tmpentry) { + DL_DELETE(entries, entry); + fp = fopen(entry->fname, "we"); + if (likely(!fp)) { + fprintf(fp, "%s", entry->buf); + fclose(fp); + } else + LOGERR("Failed to fopen %s in dump_log_entries", entry->fname); + free(entry->fname); + free(entry->buf); + free(entry); + } +} + static void *statsupdate(void *arg) { ckpool_t *ckp = (ckpool_t *)arg; From 1b077f2a051bb071dd944d0a92a91230cb41b74c Mon Sep 17 00:00:00 2001 From: Con Kolivas Date: Fri, 27 Feb 2015 21:09:31 +1100 Subject: [PATCH 39/39] Log to files outside of instance lock --- src/stratifier.c | 47 +++++++++++++++++++---------------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/src/stratifier.c b/src/stratifier.c index be512f82..97fb1083 100644 --- a/src/stratifier.c +++ b/src/stratifier.c @@ -3936,7 +3936,7 @@ static void update_workerstats(ckpool_t *ckp, sdata_t *sdata) } } -static void add_log_entry(log_entry_t *entries, char **fname, char **buf) +static void add_log_entry(log_entry_t **entries, char **fname, char **buf) { log_entry_t *entry = ckalloc(sizeof(log_entry_t)); @@ -3944,18 +3944,18 @@ static void add_log_entry(log_entry_t *entries, char **fname, char **buf) *fname = NULL; entry->buf = *buf; *buf = NULL; - DL_APPEND(entries, entry); + DL_APPEND(*entries, entry); } -static void dump_log_entries(log_entry_t *entries) +static void dump_log_entries(log_entry_t **entries) { log_entry_t *entry, *tmpentry; FILE *fp; - DL_FOREACH_SAFE(entries, entry, tmpentry) { - DL_DELETE(entries, entry); + DL_FOREACH_SAFE(*entries, entry, tmpentry) { + DL_DELETE(*entries, entry); fp = fopen(entry->fname, "we"); - if (likely(!fp)) { + if (likely(fp)) { fprintf(fp, "%s", entry->buf); fclose(fp); } else @@ -3985,14 +3985,14 @@ static void *statsupdate(void *arg) char suffix360[16], suffix1440[16], suffix10080[16]; char_entry_t *char_list = NULL, *char_t, *chartmp_t; stratum_instance_t *client, *tmp; + log_entry_t *log_entries = NULL; user_instance_t *user, *tmpuser; int idle_workers = 0; - char fname[512] = {}; + char *fname, *s; tv_t now, diff; ts_t ts_now; json_t *val; FILE *fp; - char *s; int i; tv_time(&now); @@ -4058,17 +4058,10 @@ static void *statsupdate(void *arg) "lastupdate", now.tv_sec, "bestshare", worker->best_diff); - snprintf(fname, 511, "%s/workers/%s", ckp->logdir, worker->workername); - fp = fopen(fname, "we"); - if (unlikely(!fp)) { - LOGERR("Failed to fopen %s", fname); - continue; - } + ASPRINTF(&fname, "%s/workers/%s", ckp->logdir, worker->workername); s = json_dumps(val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER | JSON_EOL); - fprintf(fp, "%s", s); - dealloc(s); + add_log_entry(&log_entries, &fname, &s); json_decref(val); - fclose(fp); } /* Decay times per user */ @@ -4108,25 +4101,22 @@ static void *statsupdate(void *arg) "workers", user->workers, "bestshare", user->best_diff); - snprintf(fname, 511, "%s/users/%s", ckp->logdir, user->username); - fp = fopen(fname, "we"); - if (unlikely(!fp)) { - LOGERR("Failed to fopen %s", fname); - continue; - } - s = json_dumps(val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER); - fprintf(fp, "%s\n", s); + ASPRINTF(&fname, "%s/users/%s", ckp->logdir, user->username); + s = json_dumps(val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER | JSON_EOL); + add_log_entry(&log_entries, &fname, &s); if (!idle) { char_t = ckalloc(sizeof(char_entry_t)); + s = json_dumps(val, JSON_NO_UTF8 | JSON_PRESERVE_ORDER); ASPRINTF(&char_t->buf, "User %s:%s", user->username, s); DL_APPEND(char_list, char_t); } - dealloc(s); json_decref(val); - fclose(fp); } ck_runlock(&sdata->instance_lock); + /* Dump log entries out of instance_lock */ + dump_log_entries(&log_entries); + DL_FOREACH_SAFE(char_list, char_t, chartmp_t) { LOGNOTICE("%s", char_t->buf); DL_DELETE(char_list, char_t); @@ -4155,10 +4145,11 @@ static void *statsupdate(void *arg) ghs10080 = stats->dsps10080 * nonces; suffix_string(ghs10080, suffix10080, 16, 0); - snprintf(fname, 511, "%s/pool/pool.status", ckp->logdir); + ASPRINTF(&fname, "%s/pool/pool.status", ckp->logdir); fp = fopen(fname, "we"); if (unlikely(!fp)) LOGERR("Failed to fopen %s", fname); + dealloc(fname); JSON_CPACK(val, "{si,si,si,si,si,si}", "runtime", diff.tv_sec,