|
|
|
@ -69,9 +69,11 @@ developers.
|
|
|
|
|
#include "log.h"
|
|
|
|
|
#include "webui.h"
|
|
|
|
|
#include "forkoff.h"
|
|
|
|
|
#include "growbag.h"
|
|
|
|
|
#include "uthash.h"
|
|
|
|
|
|
|
|
|
|
#define MAX_LINE 8192
|
|
|
|
|
#define POOL_CLIENTS_GROW 0x4000
|
|
|
|
|
#define CLIENTS_INIT 0x4000
|
|
|
|
|
#define RPC_BODY_MAX 8192
|
|
|
|
|
#define JOB_BODY_MAX 8192
|
|
|
|
|
#define ERROR_BODY_MAX 512
|
|
|
|
@ -133,6 +135,17 @@ enum msgbin_type { BIN_PING, BIN_CONNECT, BIN_DISCONNECT, BIN_SHARE,
|
|
|
|
|
BIN_BLOCK, BIN_STATS, BIN_BALANCE };
|
|
|
|
|
const unsigned char msgbin[] = {0x4D,0x4E,0x52,0x4F,0x50,0x4F,0x4F,0x4C};
|
|
|
|
|
|
|
|
|
|
/* 2m, 10m, 30m, 1h, 1d, 1w */
|
|
|
|
|
const unsigned hr_intervals[] = {120,600,1800,3600,86400,604800};
|
|
|
|
|
|
|
|
|
|
typedef struct hr_stats_t
|
|
|
|
|
{
|
|
|
|
|
time_t last_calc;
|
|
|
|
|
uint64_t diff_since;
|
|
|
|
|
/* 2m, 10m, 30m, 1h, 1d, 1w */
|
|
|
|
|
double avg[6];
|
|
|
|
|
} hr_stats_t;
|
|
|
|
|
|
|
|
|
|
typedef struct config_t
|
|
|
|
|
{
|
|
|
|
|
char rpc_host[MAX_HOST];
|
|
|
|
@ -206,19 +219,25 @@ typedef struct client_t
|
|
|
|
|
char agent[256];
|
|
|
|
|
bstack_t *active_jobs;
|
|
|
|
|
uint64_t hashes;
|
|
|
|
|
hr_stats_t hr_stats;
|
|
|
|
|
time_t connected_since;
|
|
|
|
|
bool is_xnp;
|
|
|
|
|
uint32_t mode;
|
|
|
|
|
uint8_t bad_shares;
|
|
|
|
|
bool downstream;
|
|
|
|
|
uint32_t downstream_miners;
|
|
|
|
|
uint32_t downstream_accounts;
|
|
|
|
|
UT_hash_handle hh;
|
|
|
|
|
} client_t;
|
|
|
|
|
|
|
|
|
|
typedef struct pool_clients_t
|
|
|
|
|
typedef struct account_t
|
|
|
|
|
{
|
|
|
|
|
client_t *clients;
|
|
|
|
|
size_t count;
|
|
|
|
|
} pool_clients_t;
|
|
|
|
|
char address[ADDRESS_MAX];
|
|
|
|
|
size_t worker_count;
|
|
|
|
|
time_t connected_since;
|
|
|
|
|
uint64_t hashes;
|
|
|
|
|
hr_stats_t hr_stats;
|
|
|
|
|
UT_hash_handle hh;
|
|
|
|
|
} account_t;
|
|
|
|
|
|
|
|
|
|
typedef struct share_t
|
|
|
|
|
{
|
|
|
|
@ -248,14 +267,15 @@ typedef struct payment_t
|
|
|
|
|
|
|
|
|
|
typedef struct rpc_callback_t rpc_callback_t;
|
|
|
|
|
typedef void (*rpc_callback_fun)(const char*, rpc_callback_t*);
|
|
|
|
|
typedef void (*rpc_datafree_fun)(void*);
|
|
|
|
|
struct rpc_callback_t
|
|
|
|
|
{
|
|
|
|
|
rpc_callback_fun f;
|
|
|
|
|
rpc_callback_fun cf;
|
|
|
|
|
void *data;
|
|
|
|
|
rpc_datafree_fun df;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static config_t config;
|
|
|
|
|
static pool_clients_t pool_clients;
|
|
|
|
|
static bstack_t *bst;
|
|
|
|
|
static bstack_t *bsh;
|
|
|
|
|
static struct event_base *pool_base;
|
|
|
|
@ -281,6 +301,8 @@ static pthread_cond_t cond_clients = PTHREAD_COND_INITIALIZER;
|
|
|
|
|
static pthread_mutex_t mutex_clients = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
|
static pthread_mutex_t mutex_log = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
|
static pthread_rwlock_t rwlock_tx = PTHREAD_RWLOCK_INITIALIZER;
|
|
|
|
|
static pthread_rwlock_t rwlock_acc = PTHREAD_RWLOCK_INITIALIZER;
|
|
|
|
|
static pthread_rwlock_t rwlock_cfd = PTHREAD_RWLOCK_INITIALIZER;
|
|
|
|
|
static FILE *fd_log;
|
|
|
|
|
static unsigned char sec_view[32];
|
|
|
|
|
static unsigned char pub_spend[32];
|
|
|
|
@ -293,6 +315,10 @@ static struct event *timer_10s;
|
|
|
|
|
static time_t upstream_last_time;
|
|
|
|
|
static uint64_t upstream_last_height;
|
|
|
|
|
static uint32_t miner_count;
|
|
|
|
|
static client_t *clients_by_fd = NULL;
|
|
|
|
|
static account_t *accounts = NULL;
|
|
|
|
|
static gbag_t *bag_accounts;
|
|
|
|
|
static gbag_t *bag_clients;
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_RX
|
|
|
|
|
extern void rx_stop_mining();
|
|
|
|
@ -323,12 +349,44 @@ void rx_slow_hash_free_state(){}
|
|
|
|
|
} \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
hr_update(hr_stats_t *stats)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
Update some time decayed EMA hashrates.
|
|
|
|
|
*/
|
|
|
|
|
time_t now = time(NULL);
|
|
|
|
|
double t = difftime(now, stats->last_calc);
|
|
|
|
|
if (t <= 0)
|
|
|
|
|
return;
|
|
|
|
|
double h = stats->diff_since;
|
|
|
|
|
double d, p, z;
|
|
|
|
|
unsigned i = sizeof(hr_intervals)/sizeof(hr_intervals[0]);
|
|
|
|
|
while (i--)
|
|
|
|
|
{
|
|
|
|
|
unsigned inter = hr_intervals[i];
|
|
|
|
|
double *f = &stats->avg[i];
|
|
|
|
|
d = t/inter;
|
|
|
|
|
if (d > 32)
|
|
|
|
|
d = 32;
|
|
|
|
|
p = 1 - 1.0 / exp(d);
|
|
|
|
|
z = 1 + p;
|
|
|
|
|
*f += (h / t * p);
|
|
|
|
|
*f /= z;
|
|
|
|
|
if (*f < 2e-16)
|
|
|
|
|
*f = 0;
|
|
|
|
|
}
|
|
|
|
|
stats->diff_since = 0;
|
|
|
|
|
stats->last_calc = now;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline rpc_callback_t *
|
|
|
|
|
rpc_callback_new(rpc_callback_fun f, void *data)
|
|
|
|
|
rpc_callback_new(rpc_callback_fun cf, void *data, rpc_datafree_fun df)
|
|
|
|
|
{
|
|
|
|
|
rpc_callback_t *c = calloc(1, sizeof(rpc_callback_t));
|
|
|
|
|
c->f = f;
|
|
|
|
|
c->cf = cf;
|
|
|
|
|
c->data = data;
|
|
|
|
|
c->df = df;
|
|
|
|
|
return c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -338,10 +396,21 @@ rpc_callback_free(rpc_callback_t *callback)
|
|
|
|
|
if (!callback)
|
|
|
|
|
return;
|
|
|
|
|
if (callback->data)
|
|
|
|
|
free(callback->data);
|
|
|
|
|
{
|
|
|
|
|
if (callback->df)
|
|
|
|
|
callback->df(callback->data);
|
|
|
|
|
else
|
|
|
|
|
free(callback->data);
|
|
|
|
|
}
|
|
|
|
|
free(callback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void
|
|
|
|
|
rpc_bag_free(void* data)
|
|
|
|
|
{
|
|
|
|
|
gbag_free((gbag_t*)data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
compare_uint64(const MDB_val *a, const MDB_val *b)
|
|
|
|
|
{
|
|
|
|
@ -603,35 +672,21 @@ store_block(uint64_t height, block_t *block)
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint64_t
|
|
|
|
|
miner_hr(const char *address)
|
|
|
|
|
void
|
|
|
|
|
account_hr(double *avg, const char *address)
|
|
|
|
|
{
|
|
|
|
|
pthread_mutex_lock(&mutex_clients);
|
|
|
|
|
clients_reading++;
|
|
|
|
|
pthread_mutex_unlock(&mutex_clients);
|
|
|
|
|
client_t *c = pool_clients.clients;
|
|
|
|
|
uint64_t hr = 0;
|
|
|
|
|
for (size_t i = 0; i < pool_clients.count; i++, c++)
|
|
|
|
|
{
|
|
|
|
|
if (c->connected_since
|
|
|
|
|
&& !strncmp(c->address, address, ADDRESS_MAX))
|
|
|
|
|
{
|
|
|
|
|
double d = difftime(time(NULL), c->connected_since);
|
|
|
|
|
if (d == 0.0)
|
|
|
|
|
continue;
|
|
|
|
|
hr += c->hashes / d;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pthread_mutex_lock(&mutex_clients);
|
|
|
|
|
clients_reading--;
|
|
|
|
|
pthread_cond_signal(&cond_clients);
|
|
|
|
|
pthread_mutex_unlock(&mutex_clients);
|
|
|
|
|
return hr;
|
|
|
|
|
account_t *account = NULL;
|
|
|
|
|
pthread_rwlock_rdlock(&rwlock_acc);
|
|
|
|
|
HASH_FIND_STR(accounts, address, account);
|
|
|
|
|
if (!account)
|
|
|
|
|
goto bail;
|
|
|
|
|
memcpy(avg, account->hr_stats.avg, sizeof(account->hr_stats.avg));
|
|
|
|
|
bail:
|
|
|
|
|
pthread_rwlock_unlock(&rwlock_acc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint64_t
|
|
|
|
|
miner_balance(const char *address)
|
|
|
|
|
account_balance(const char *address)
|
|
|
|
|
{
|
|
|
|
|
int rc;
|
|
|
|
|
char *err;
|
|
|
|
@ -937,17 +992,9 @@ static void
|
|
|
|
|
update_pool_hr(void)
|
|
|
|
|
{
|
|
|
|
|
uint64_t hr = 0;
|
|
|
|
|
client_t *c = pool_clients.clients;
|
|
|
|
|
for (size_t i = 0; i < pool_clients.count; i++, c++)
|
|
|
|
|
{
|
|
|
|
|
if (c->connected_since)
|
|
|
|
|
{
|
|
|
|
|
double d = difftime(time(NULL), c->connected_since);
|
|
|
|
|
if (d == 0.0)
|
|
|
|
|
continue;
|
|
|
|
|
hr += c->hashes / d;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
client_t *c = (client_t*)gbag_first(bag_clients);
|
|
|
|
|
while ((c = gbag_next(bag_clients, 0)))
|
|
|
|
|
hr += (uint64_t) c->hr_stats.avg[0];
|
|
|
|
|
log_debug("Pool hashrate: %"PRIu64, hr);
|
|
|
|
|
if (upstream_event)
|
|
|
|
|
return;
|
|
|
|
@ -1344,10 +1391,34 @@ miner_send_job(client_t *client, bool response)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
pool_clients_send_job(void)
|
|
|
|
|
accounts_moved(const void *items, size_t count)
|
|
|
|
|
{
|
|
|
|
|
client_t *c = pool_clients.clients;
|
|
|
|
|
for (size_t i = 0; i < pool_clients.count; i++, c++)
|
|
|
|
|
account_t *s, *e, *r;
|
|
|
|
|
s = (account_t*) items;
|
|
|
|
|
e = s + count;
|
|
|
|
|
pthread_rwlock_wrlock(&rwlock_acc);
|
|
|
|
|
while (s<e)
|
|
|
|
|
HASH_REPLACE_STR(accounts, address, s, r);
|
|
|
|
|
pthread_rwlock_unlock(&rwlock_acc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
clients_moved(const void *items, size_t count)
|
|
|
|
|
{
|
|
|
|
|
client_t *s, *e, *r;
|
|
|
|
|
s = (client_t*) items;
|
|
|
|
|
e = s + count;
|
|
|
|
|
pthread_rwlock_wrlock(&rwlock_cfd);
|
|
|
|
|
while (s<e)
|
|
|
|
|
HASH_REPLACE_INT(clients_by_fd, fd, s, r);
|
|
|
|
|
pthread_rwlock_unlock(&rwlock_cfd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
clients_send_job(void)
|
|
|
|
|
{
|
|
|
|
|
client_t *c = (client_t*) gbag_first(bag_clients);
|
|
|
|
|
while ((c = gbag_next(bag_clients, 0)))
|
|
|
|
|
{
|
|
|
|
|
if (c->fd == 0 || c->address[0] == 0 || c->downstream)
|
|
|
|
|
continue;
|
|
|
|
@ -1356,27 +1427,36 @@ pool_clients_send_job(void)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
pool_clients_init(void)
|
|
|
|
|
clients_init(void)
|
|
|
|
|
{
|
|
|
|
|
assert(pool_clients.count == 0);
|
|
|
|
|
pool_clients.count = POOL_CLIENTS_GROW;
|
|
|
|
|
pool_clients.clients = (client_t*) calloc(pool_clients.count,
|
|
|
|
|
sizeof(client_t));
|
|
|
|
|
gbag_new(&bag_accounts, CLIENTS_INIT, sizeof(account_t), 0,
|
|
|
|
|
accounts_moved);
|
|
|
|
|
gbag_new(&bag_clients, CLIENTS_INIT, sizeof(client_t), 0,
|
|
|
|
|
clients_moved);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
pool_clients_free(void)
|
|
|
|
|
clients_free(void)
|
|
|
|
|
{
|
|
|
|
|
if (!pool_clients.clients)
|
|
|
|
|
if (!(bag_accounts && bag_clients))
|
|
|
|
|
return;
|
|
|
|
|
client_t *c = pool_clients.clients;
|
|
|
|
|
for (size_t i = 0; i < pool_clients.count; i++, c++)
|
|
|
|
|
|
|
|
|
|
client_t *c = (client_t*) gbag_first(bag_clients);
|
|
|
|
|
while ((c = gbag_next(bag_clients, 0)))
|
|
|
|
|
{
|
|
|
|
|
if (!c->active_jobs)
|
|
|
|
|
continue;
|
|
|
|
|
client_clear_jobs(c);
|
|
|
|
|
}
|
|
|
|
|
free(pool_clients.clients);
|
|
|
|
|
pthread_rwlock_wrlock(&rwlock_cfd);
|
|
|
|
|
HASH_CLEAR(hh, clients_by_fd);
|
|
|
|
|
gbag_free(bag_clients);
|
|
|
|
|
pthread_rwlock_unlock(&rwlock_cfd);
|
|
|
|
|
|
|
|
|
|
pthread_rwlock_wrlock(&rwlock_acc);
|
|
|
|
|
HASH_CLEAR(hh, accounts);
|
|
|
|
|
gbag_free(bag_accounts);
|
|
|
|
|
pthread_rwlock_unlock(&rwlock_acc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
@ -1464,7 +1544,7 @@ rpc_on_response(struct evhttp_request *req, void *arg)
|
|
|
|
|
char body[len+1];
|
|
|
|
|
evbuffer_remove(input, body, len);
|
|
|
|
|
body[len] = '\0';
|
|
|
|
|
callback->f(body, callback);
|
|
|
|
|
callback->cf(body, callback);
|
|
|
|
|
rpc_callback_free(callback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -1659,7 +1739,7 @@ rpc_on_block_template(const char* data, rpc_callback_t *callback)
|
|
|
|
|
pool_stats.last_template_fetched = time(NULL);
|
|
|
|
|
block_template_t *top = (block_template_t*) bstack_push(bst, NULL);
|
|
|
|
|
response_to_block_template(result, top);
|
|
|
|
|
pool_clients_send_job();
|
|
|
|
|
clients_send_job();
|
|
|
|
|
json_object_put(root);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -1770,7 +1850,7 @@ startup_payout(uint64_t height)
|
|
|
|
|
rpc_get_request_body(body, "get_block_header_by_height", "sd",
|
|
|
|
|
"height", block->height);
|
|
|
|
|
rpc_callback_t *cb = rpc_callback_new(
|
|
|
|
|
rpc_on_block_header_by_height, NULL);
|
|
|
|
|
rpc_on_block_header_by_height, 0, 0);
|
|
|
|
|
rpc_request(pool_base, body, cb);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -1862,7 +1942,7 @@ rpc_on_last_block_header(const char* data, rpc_callback_t *callback)
|
|
|
|
|
uint64_t reserve = 17;
|
|
|
|
|
rpc_get_request_body(body, "get_block_template", "sssd",
|
|
|
|
|
"wallet_address", config.pool_wallet, "reserve_size", reserve);
|
|
|
|
|
rpc_callback_t *cb1 = rpc_callback_new(rpc_on_block_template, NULL);
|
|
|
|
|
rpc_callback_t *cb1 = rpc_callback_new(rpc_on_block_template, 0, 0);
|
|
|
|
|
rpc_request(pool_base, body, cb1);
|
|
|
|
|
|
|
|
|
|
uint64_t end = top->height - 60;
|
|
|
|
@ -1870,7 +1950,7 @@ rpc_on_last_block_header(const char* data, rpc_callback_t *callback)
|
|
|
|
|
rpc_get_request_body(body, "get_block_headers_range", "sdsd",
|
|
|
|
|
"start_height", start, "end_height", end);
|
|
|
|
|
rpc_callback_t *cb2 = rpc_callback_new(
|
|
|
|
|
rpc_on_block_headers_range, NULL);
|
|
|
|
|
rpc_on_block_headers_range, 0, 0);
|
|
|
|
|
rpc_request(pool_base, body, cb2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -1955,11 +2035,12 @@ rpc_on_wallet_transferred(const char* data, rpc_callback_t *callback)
|
|
|
|
|
mdb_txn_abort(txn);
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
payment_t *payment = (payment_t*) callback->data;
|
|
|
|
|
for (; payment->amount; payment++)
|
|
|
|
|
gbag_t *bag_pay = (gbag_t*) callback->data;
|
|
|
|
|
payment_t *p = (payment_t*) gbag_first(bag_pay);
|
|
|
|
|
while((p = gbag_next(bag_pay, 0)))
|
|
|
|
|
{
|
|
|
|
|
MDB_cursor_op op = MDB_SET;
|
|
|
|
|
MDB_val key = {ADDRESS_MAX, (void*)payment->address};
|
|
|
|
|
MDB_val key = {ADDRESS_MAX, (void*)p->address};
|
|
|
|
|
MDB_val val;
|
|
|
|
|
rc = mdb_cursor_get(cursor, &key, &val, op);
|
|
|
|
|
if (rc == MDB_NOTFOUND)
|
|
|
|
@ -1975,21 +2056,21 @@ rpc_on_wallet_transferred(const char* data, rpc_callback_t *callback)
|
|
|
|
|
}
|
|
|
|
|
uint64_t current_amount = *(uint64_t*)val.mv_data;
|
|
|
|
|
|
|
|
|
|
if (current_amount >= payment->amount)
|
|
|
|
|
if (current_amount >= p->amount)
|
|
|
|
|
{
|
|
|
|
|
current_amount -= payment->amount;
|
|
|
|
|
current_amount -= p->amount;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
log_error("Payment was more than balance: %"PRIu64" > %"PRIu64,
|
|
|
|
|
payment->amount, current_amount);
|
|
|
|
|
p->amount, current_amount);
|
|
|
|
|
current_amount = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (error)
|
|
|
|
|
{
|
|
|
|
|
log_warn("Error seen on transfer for %s with amount %"PRIu64,
|
|
|
|
|
payment->address, payment->amount);
|
|
|
|
|
p->address, p->amount);
|
|
|
|
|
}
|
|
|
|
|
MDB_val new_val = {sizeof(current_amount), (void*)¤t_amount};
|
|
|
|
|
rc = mdb_cursor_put(cursor, &key, &new_val, MDB_CURRENT);
|
|
|
|
@ -2022,12 +2103,12 @@ rpc_on_wallet_transferred(const char* data, rpc_callback_t *callback)
|
|
|
|
|
goto cleanup;
|
|
|
|
|
}
|
|
|
|
|
time_t now = time(NULL);
|
|
|
|
|
payment = (payment_t*) callback->data;
|
|
|
|
|
for (; payment->amount; payment++)
|
|
|
|
|
p = (payment_t*) gbag_first(bag_pay);
|
|
|
|
|
while((p = gbag_next(bag_pay, 0)))
|
|
|
|
|
{
|
|
|
|
|
payment->timestamp = now;
|
|
|
|
|
MDB_val key = {ADDRESS_MAX, (void*)payment->address};
|
|
|
|
|
MDB_val val = {sizeof(payment_t), payment};
|
|
|
|
|
p->timestamp = now;
|
|
|
|
|
MDB_val key = {ADDRESS_MAX, (void*)p->address};
|
|
|
|
|
MDB_val val = {sizeof(payment_t), p};
|
|
|
|
|
if ((rc = mdb_cursor_put(cursor, &key, &val, MDB_APPENDDUP)) != 0)
|
|
|
|
|
{
|
|
|
|
|
err = mdb_strerror(rc);
|
|
|
|
@ -2071,12 +2152,8 @@ send_payments(void)
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t payments_count = 0;
|
|
|
|
|
size_t payments_max_count = 25;
|
|
|
|
|
size_t payments_size = payments_max_count * sizeof(payment_t);
|
|
|
|
|
payment_t *payments = (payment_t*) calloc(1, payments_size);
|
|
|
|
|
payment_t *payment = payments;
|
|
|
|
|
payment_t *end_payment = payment + payments_max_count;
|
|
|
|
|
gbag_t *bag_pay = NULL;
|
|
|
|
|
gbag_new(&bag_pay, 25, sizeof(payment_t), 0, 0);
|
|
|
|
|
|
|
|
|
|
MDB_cursor_op op = MDB_FIRST;
|
|
|
|
|
while (1)
|
|
|
|
@ -2096,52 +2173,43 @@ send_payments(void)
|
|
|
|
|
|
|
|
|
|
log_info("Sending payment: %"PRIu64", %.8s", amount, address);
|
|
|
|
|
|
|
|
|
|
strncpy(payment->address, address, ADDRESS_MAX-1);
|
|
|
|
|
payment->amount = amount;
|
|
|
|
|
payments_count++;
|
|
|
|
|
|
|
|
|
|
if (++payment == end_payment)
|
|
|
|
|
{
|
|
|
|
|
payments_size <<= 1;
|
|
|
|
|
payments = (payment_t*) realloc(payments, payments_size);
|
|
|
|
|
payment = payments + payments_max_count;
|
|
|
|
|
memset(payment, 0, sizeof(payment_t) * payments_max_count);
|
|
|
|
|
payments_max_count <<= 1;
|
|
|
|
|
end_payment = payments + payments_max_count;
|
|
|
|
|
}
|
|
|
|
|
payment_t *p = (payment_t*) gbag_get(bag_pay);
|
|
|
|
|
strncpy(p->address, address, ADDRESS_MAX-1);
|
|
|
|
|
p->amount = amount;
|
|
|
|
|
}
|
|
|
|
|
mdb_cursor_close(cursor);
|
|
|
|
|
mdb_txn_abort(txn);
|
|
|
|
|
|
|
|
|
|
if (payments_count)
|
|
|
|
|
size_t proc = gbag_used(bag_pay);
|
|
|
|
|
if (proc)
|
|
|
|
|
{
|
|
|
|
|
size_t body_size = 160 * payments_count + 128;
|
|
|
|
|
size_t body_size = 160 * proc + 128;
|
|
|
|
|
char body[body_size];
|
|
|
|
|
char *start = body;
|
|
|
|
|
char *end = body + body_size;
|
|
|
|
|
start = stecpy(start, "{\"id\":\"0\",\"jsonrpc\":\"2.0\",\"method\":"
|
|
|
|
|
"\"transfer_split\",\"params\":{"
|
|
|
|
|
"\"ring_size\":11,\"destinations\":[", end);
|
|
|
|
|
for (size_t i=0; i<payments_count; i++)
|
|
|
|
|
payment_t *p = (payment_t*) gbag_first(bag_pay);
|
|
|
|
|
while ((p = gbag_next(bag_pay, 0)))
|
|
|
|
|
{
|
|
|
|
|
payment_t *p = &payments[i];
|
|
|
|
|
start = stecpy(start, "{\"address\":\"", end);
|
|
|
|
|
start = stecpy(start, p->address, end);
|
|
|
|
|
start = stecpy(start, "\",\"amount\":", end);
|
|
|
|
|
sprintf(start, "%"PRIu64"}", p->amount);
|
|
|
|
|
start = body + strlen(body);
|
|
|
|
|
if (i != payments_count -1)
|
|
|
|
|
if (--proc)
|
|
|
|
|
start = stecpy(start, ",", end);
|
|
|
|
|
else
|
|
|
|
|
start = stecpy(start, "]}}", end);
|
|
|
|
|
}
|
|
|
|
|
log_trace(body);
|
|
|
|
|
rpc_callback_t *cb = rpc_callback_new(
|
|
|
|
|
rpc_on_wallet_transferred, payments);
|
|
|
|
|
rpc_on_wallet_transferred, bag_pay, rpc_bag_free);
|
|
|
|
|
rpc_wallet_request(pool_base, body, cb);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
free(payments);
|
|
|
|
|
gbag_free(bag_pay);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
@ -2159,7 +2227,7 @@ fetch_view_key(void)
|
|
|
|
|
return;
|
|
|
|
|
char body[RPC_BODY_MAX] = {0};
|
|
|
|
|
rpc_get_request_body(body, "query_key", "ss", "key_type", "view_key");
|
|
|
|
|
rpc_callback_t *cb = rpc_callback_new(rpc_on_view_key, NULL);
|
|
|
|
|
rpc_callback_t *cb = rpc_callback_new(rpc_on_view_key, 0, 0);
|
|
|
|
|
rpc_wallet_request(pool_base, body, cb);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -2169,7 +2237,7 @@ fetch_last_block_header(void)
|
|
|
|
|
log_info("Fetching last block header");
|
|
|
|
|
char body[RPC_BODY_MAX] = {0};
|
|
|
|
|
rpc_get_request_body(body, "get_last_block_header", NULL);
|
|
|
|
|
rpc_callback_t *cb = rpc_callback_new(rpc_on_last_block_header, NULL);
|
|
|
|
|
rpc_callback_t *cb = rpc_callback_new(rpc_on_last_block_header, 0, 0);
|
|
|
|
|
rpc_request(pool_base, body, cb);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -2239,7 +2307,7 @@ trusted_send_balance(client_t *client, const char *address)
|
|
|
|
|
int t = BIN_BALANCE;
|
|
|
|
|
memcpy(data, msgbin, 8);
|
|
|
|
|
memcpy(data+8, &t, 1);
|
|
|
|
|
uint64_t balance = miner_balance(address);
|
|
|
|
|
uint64_t balance = account_balance(address);
|
|
|
|
|
memcpy(data+9, &balance, sizeof(uint64_t));
|
|
|
|
|
memcpy(data+9+sizeof(uint64_t), address, ADDRESS_MAX);
|
|
|
|
|
evbuffer_add(output, data, z);
|
|
|
|
@ -2258,7 +2326,7 @@ upstream_send_ping()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
upstream_send_client_connect(uint32_t count)
|
|
|
|
|
upstream_send_account_connect(uint32_t count)
|
|
|
|
|
{
|
|
|
|
|
struct evbuffer *output = bufferevent_get_output(upstream_event);
|
|
|
|
|
size_t z = 9 + sizeof(uint32_t);
|
|
|
|
@ -2268,11 +2336,11 @@ upstream_send_client_connect(uint32_t count)
|
|
|
|
|
memcpy(data+8, &t, 1);
|
|
|
|
|
memcpy(data+9, &count, z-9);
|
|
|
|
|
evbuffer_add(output, data, z);
|
|
|
|
|
log_trace("Sending message connect upstream");
|
|
|
|
|
log_trace("Sending message account connect upstream");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
upstream_send_client_disconnect()
|
|
|
|
|
upstream_send_account_disconnect()
|
|
|
|
|
{
|
|
|
|
|
struct evbuffer *output = bufferevent_get_output(upstream_event);
|
|
|
|
|
char data[9];
|
|
|
|
@ -2412,37 +2480,37 @@ upstream_send_backlog()
|
|
|
|
|
mdb_cursor_close(curshr);
|
|
|
|
|
mdb_cursor_close(curblk);
|
|
|
|
|
mdb_txn_abort(txn);
|
|
|
|
|
upstream_send_client_connect(pool_stats.connected_miners);
|
|
|
|
|
upstream_send_account_connect(pool_stats.connected_accounts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
trusted_on_client_connect(client_t *client)
|
|
|
|
|
trusted_on_account_connect(client_t *client)
|
|
|
|
|
{
|
|
|
|
|
struct evbuffer *input = bufferevent_get_input(client->bev);
|
|
|
|
|
uint32_t count;
|
|
|
|
|
evbuffer_remove(input, &count, sizeof(uint32_t));
|
|
|
|
|
pool_stats.connected_miners += count;
|
|
|
|
|
client->downstream_miners += count;
|
|
|
|
|
log_trace("Downstream miner connected. "
|
|
|
|
|
"Miner count: %d, Pool hashrate: %"PRIu64,
|
|
|
|
|
pool_stats.connected_miners, pool_stats.pool_hashrate);
|
|
|
|
|
pool_stats.connected_accounts += count;
|
|
|
|
|
client->downstream_accounts += count;
|
|
|
|
|
log_trace("Downstream account connected. "
|
|
|
|
|
"Accounts: %d, Pool hashrate: %"PRIu64,
|
|
|
|
|
pool_stats.connected_accounts, pool_stats.pool_hashrate);
|
|
|
|
|
trusted_send_stats(client);
|
|
|
|
|
if (upstream_event)
|
|
|
|
|
upstream_send_client_connect(count);
|
|
|
|
|
upstream_send_account_connect(count);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
trusted_on_client_disconnect(client_t *client)
|
|
|
|
|
trusted_on_account_disconnect(client_t *client)
|
|
|
|
|
{
|
|
|
|
|
pool_stats.connected_miners--;
|
|
|
|
|
if (client->downstream_miners)
|
|
|
|
|
client->downstream_miners--;
|
|
|
|
|
log_trace("Downstream miner disconnected. "
|
|
|
|
|
pool_stats.connected_accounts--;
|
|
|
|
|
if (client->downstream_accounts)
|
|
|
|
|
client->downstream_accounts--;
|
|
|
|
|
log_trace("Downstream account disconnected. "
|
|
|
|
|
"Miner count: %d, Pool hashrate: %"PRIu64,
|
|
|
|
|
pool_stats.connected_miners, pool_stats.pool_hashrate);
|
|
|
|
|
pool_stats.connected_accounts, pool_stats.pool_hashrate);
|
|
|
|
|
trusted_send_stats(client);
|
|
|
|
|
if (upstream_event)
|
|
|
|
|
upstream_send_client_disconnect();
|
|
|
|
|
upstream_send_account_disconnect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
@ -2459,6 +2527,8 @@ trusted_on_client_share(client_t *client)
|
|
|
|
|
s.difficulty);
|
|
|
|
|
client->hashes += s.difficulty;
|
|
|
|
|
pool_stats.round_hashes += s.difficulty;
|
|
|
|
|
client->hr_stats.diff_since += s.difficulty;
|
|
|
|
|
hr_update(&client->hr_stats);
|
|
|
|
|
rc = store_share(s.height, &s);
|
|
|
|
|
if (rc != 0)
|
|
|
|
|
log_warn("Failed to store share: %s", mdb_strerror(rc));
|
|
|
|
@ -2494,7 +2564,7 @@ upstream_on_stats(struct bufferevent *bev)
|
|
|
|
|
evbuffer_remove(input, &pool_stats, sizeof(pool_stats_t));
|
|
|
|
|
log_trace("Stats from upstream: "
|
|
|
|
|
"%d, %"PRIu64", %"PRIu64", %d, %"PRIu64,
|
|
|
|
|
pool_stats.connected_miners,
|
|
|
|
|
pool_stats.connected_accounts,
|
|
|
|
|
pool_stats.pool_hashrate,
|
|
|
|
|
pool_stats.round_hashes,
|
|
|
|
|
pool_stats.pool_blocks_found,
|
|
|
|
@ -2598,9 +2668,9 @@ upstream_on_event(struct bufferevent *bev, short error, void *ctx)
|
|
|
|
|
log_debug("Upstream timeout");
|
|
|
|
|
}
|
|
|
|
|
/* Update stats due to upstream disconnect */
|
|
|
|
|
if (pool_stats.connected_miners != miner_count)
|
|
|
|
|
if (pool_stats.connected_accounts != miner_count)
|
|
|
|
|
{
|
|
|
|
|
pool_stats.connected_miners = miner_count;
|
|
|
|
|
pool_stats.connected_accounts = miner_count;
|
|
|
|
|
update_pool_hr();
|
|
|
|
|
}
|
|
|
|
|
/* Wait and try to reconnect */
|
|
|
|
@ -2686,42 +2756,28 @@ timer_on_10m(int fd, short kind, void *ctx)
|
|
|
|
|
static void
|
|
|
|
|
client_add(int fd, struct bufferevent *bev, bool downstream)
|
|
|
|
|
{
|
|
|
|
|
client_t *c = pool_clients.clients;
|
|
|
|
|
bool resize = true;
|
|
|
|
|
for (size_t i = 0; i < pool_clients.count; i++, c++)
|
|
|
|
|
{
|
|
|
|
|
if (c->connected_since == 0)
|
|
|
|
|
{
|
|
|
|
|
resize = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
client_t *c;
|
|
|
|
|
bool resize = gbag_used(bag_clients) == gbag_max(bag_clients);
|
|
|
|
|
if (resize)
|
|
|
|
|
{
|
|
|
|
|
pthread_mutex_lock(&mutex_clients);
|
|
|
|
|
while (clients_reading)
|
|
|
|
|
pthread_cond_wait(&cond_clients, &mutex_clients);
|
|
|
|
|
pool_clients.count += POOL_CLIENTS_GROW;
|
|
|
|
|
c = realloc(pool_clients.clients, sizeof(client_t) *
|
|
|
|
|
pool_clients.count);
|
|
|
|
|
pool_clients.clients = c;
|
|
|
|
|
c += pool_clients.count - POOL_CLIENTS_GROW;
|
|
|
|
|
memset(c, 0, sizeof(client_t) * POOL_CLIENTS_GROW);
|
|
|
|
|
c = gbag_get(bag_clients);
|
|
|
|
|
pthread_mutex_unlock(&mutex_clients);
|
|
|
|
|
log_debug("Client pool can now hold %zu clients", pool_clients.count);
|
|
|
|
|
log_debug("Client pool can now hold %zu clients",
|
|
|
|
|
gbag_max(bag_clients));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
memset(c, 0, sizeof(client_t));
|
|
|
|
|
c = gbag_get(bag_clients);
|
|
|
|
|
c->fd = fd;
|
|
|
|
|
c->bev = bev;
|
|
|
|
|
c->connected_since = time(NULL);
|
|
|
|
|
c->downstream = downstream;
|
|
|
|
|
bstack_new(&c->active_jobs, CLIENT_JOBS_MAX, sizeof(job_t), job_recycle);
|
|
|
|
|
miner_count++;
|
|
|
|
|
if (!downstream)
|
|
|
|
|
pool_stats.connected_miners++;
|
|
|
|
|
if (upstream_event)
|
|
|
|
|
upstream_send_client_connect(1);
|
|
|
|
|
pthread_rwlock_wrlock(&rwlock_cfd);
|
|
|
|
|
HASH_ADD_INT(clients_by_fd, fd, c);
|
|
|
|
|
pthread_rwlock_unlock(&rwlock_cfd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
@ -2733,16 +2789,9 @@ client_find(struct bufferevent *bev, client_t **client)
|
|
|
|
|
*client = NULL;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
client_t *c = pool_clients.clients;
|
|
|
|
|
for (size_t i = 0; i < pool_clients.count; i++, c++)
|
|
|
|
|
{
|
|
|
|
|
if (c->fd == fd)
|
|
|
|
|
{
|
|
|
|
|
*client = c;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*client = NULL;
|
|
|
|
|
pthread_rwlock_rdlock(&rwlock_cfd);
|
|
|
|
|
HASH_FIND_INT(clients_by_fd, &fd, *client);
|
|
|
|
|
pthread_rwlock_unlock(&rwlock_cfd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
@ -2753,15 +2802,31 @@ client_clear(struct bufferevent *bev)
|
|
|
|
|
if (!client)
|
|
|
|
|
return;
|
|
|
|
|
client_clear_jobs(client);
|
|
|
|
|
if (client->downstream)
|
|
|
|
|
pool_stats.connected_miners -= client->downstream_miners;
|
|
|
|
|
else
|
|
|
|
|
pool_stats.connected_miners--;
|
|
|
|
|
account_t *account = NULL;
|
|
|
|
|
pthread_rwlock_rdlock(&rwlock_acc);
|
|
|
|
|
HASH_FIND_STR(accounts, client->address, account);
|
|
|
|
|
pthread_rwlock_unlock(&rwlock_acc);
|
|
|
|
|
if (account && account->worker_count == 1)
|
|
|
|
|
{
|
|
|
|
|
if (client->downstream)
|
|
|
|
|
pool_stats.connected_accounts -= client->downstream_accounts;
|
|
|
|
|
else
|
|
|
|
|
pool_stats.connected_accounts--;
|
|
|
|
|
if (upstream_event)
|
|
|
|
|
upstream_send_account_disconnect();
|
|
|
|
|
miner_count--;
|
|
|
|
|
pthread_rwlock_wrlock(&rwlock_acc);
|
|
|
|
|
HASH_DEL(accounts, account);
|
|
|
|
|
pthread_rwlock_unlock(&rwlock_acc);
|
|
|
|
|
gbag_put(bag_accounts, account);
|
|
|
|
|
}
|
|
|
|
|
else if (account && account->worker_count > 1)
|
|
|
|
|
account->worker_count--;
|
|
|
|
|
pthread_rwlock_wrlock(&rwlock_cfd);
|
|
|
|
|
HASH_DEL(clients_by_fd, client);
|
|
|
|
|
pthread_rwlock_unlock(&rwlock_cfd);
|
|
|
|
|
memset(client, 0, sizeof(client_t));
|
|
|
|
|
bufferevent_free(bev);
|
|
|
|
|
if (upstream_event)
|
|
|
|
|
upstream_send_client_disconnect();
|
|
|
|
|
miner_count--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
@ -2831,6 +2896,28 @@ miner_on_login(json_object *message, client_t *client)
|
|
|
|
|
|
|
|
|
|
strncpy(client->address, address, sizeof(client->address)-1);
|
|
|
|
|
strncpy(client->worker_id, worker_id, sizeof(client->worker_id)-1);
|
|
|
|
|
account_t *account = NULL;
|
|
|
|
|
pthread_rwlock_rdlock(&rwlock_acc);
|
|
|
|
|
HASH_FIND_STR(accounts, client->address, account);
|
|
|
|
|
pthread_rwlock_unlock(&rwlock_acc);
|
|
|
|
|
if (!account)
|
|
|
|
|
{
|
|
|
|
|
miner_count++;
|
|
|
|
|
if (!client->downstream)
|
|
|
|
|
pool_stats.connected_accounts++;
|
|
|
|
|
if (upstream_event)
|
|
|
|
|
upstream_send_account_connect(1);
|
|
|
|
|
account = gbag_get(bag_accounts);
|
|
|
|
|
strncpy(account->address, address, sizeof(account->address)-1);
|
|
|
|
|
account->worker_count = 1;
|
|
|
|
|
account->connected_since = time(NULL);
|
|
|
|
|
account->hashes = 0;
|
|
|
|
|
pthread_rwlock_wrlock(&rwlock_acc);
|
|
|
|
|
HASH_ADD_STR(accounts, address, account);
|
|
|
|
|
pthread_rwlock_unlock(&rwlock_acc);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
account->worker_count++;
|
|
|
|
|
uuid_t cid;
|
|
|
|
|
uuid_generate(cid);
|
|
|
|
|
bin_to_hex((const unsigned char*)cid, sizeof(uuid_t),
|
|
|
|
@ -3137,7 +3224,17 @@ post_hash:
|
|
|
|
|
BN_free(rh);
|
|
|
|
|
|
|
|
|
|
/* Process share */
|
|
|
|
|
account_t *account = NULL;
|
|
|
|
|
pthread_rwlock_rdlock(&rwlock_acc);
|
|
|
|
|
HASH_FIND_STR(accounts, client->address, account);
|
|
|
|
|
client->hashes += job->target;
|
|
|
|
|
client->hr_stats.diff_since += job->target;
|
|
|
|
|
account->hashes += job->target;
|
|
|
|
|
account->hr_stats.diff_since += job->target;
|
|
|
|
|
hr_update(&client->hr_stats);
|
|
|
|
|
/* TODO: account hr should be called less freq */
|
|
|
|
|
hr_update(&account->hr_stats);
|
|
|
|
|
pthread_rwlock_unlock(&rwlock_acc);
|
|
|
|
|
time_t now = time(NULL);
|
|
|
|
|
bool can_store = true;
|
|
|
|
|
log_trace("Checking hash against block difficulty: "
|
|
|
|
@ -3156,7 +3253,7 @@ post_hash:
|
|
|
|
|
"\"submit_block\", \"params\":[\"%s\"]}",
|
|
|
|
|
block_hex);
|
|
|
|
|
|
|
|
|
|
rpc_callback_t *cb = rpc_callback_new(rpc_on_block_submitted, NULL);
|
|
|
|
|
rpc_callback_t *cb = rpc_callback_new(rpc_on_block_submitted, 0, 0);
|
|
|
|
|
cb->data = calloc(1, sizeof(block_t));
|
|
|
|
|
block_t* b = (block_t*) cb->data;
|
|
|
|
|
b->height = bt->height;
|
|
|
|
@ -3385,11 +3482,11 @@ trusted_on_read(struct bufferevent *bev, void *ctx)
|
|
|
|
|
if (len - 9 < sizeof(uint32_t))
|
|
|
|
|
goto unlock;
|
|
|
|
|
evbuffer_drain(input, 9);
|
|
|
|
|
trusted_on_client_connect(client);
|
|
|
|
|
trusted_on_account_connect(client);
|
|
|
|
|
break;
|
|
|
|
|
case BIN_DISCONNECT:
|
|
|
|
|
evbuffer_drain(input, 9);
|
|
|
|
|
trusted_on_client_disconnect(client);
|
|
|
|
|
trusted_on_account_disconnect(client);
|
|
|
|
|
break;
|
|
|
|
|
case BIN_SHARE:
|
|
|
|
|
if (len - 9 < sizeof(share_t))
|
|
|
|
@ -3492,7 +3589,7 @@ listener_on_accept(evutil_socket_t listener, short event, void *arg)
|
|
|
|
|
bufferevent_setwatermark(bev, EV_READ, 0, MAX_LINE);
|
|
|
|
|
client_add(fd, bev, base == trusted_base);
|
|
|
|
|
log_info("New %s connected. Miner count: %d, Pool hashrate: %"PRIu64,
|
|
|
|
|
type, pool_stats.connected_miners, pool_stats.pool_hashrate);
|
|
|
|
|
type, pool_stats.connected_accounts, pool_stats.pool_hashrate);
|
|
|
|
|
bufferevent_enable(bev, EV_READ|EV_WRITE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -4077,7 +4174,7 @@ cleanup(void)
|
|
|
|
|
event_base_loopbreak(trusted_base);
|
|
|
|
|
if (pool_base)
|
|
|
|
|
event_base_free(pool_base);
|
|
|
|
|
pool_clients_free();
|
|
|
|
|
clients_free();
|
|
|
|
|
if (bsh)
|
|
|
|
|
bstack_free(bsh);
|
|
|
|
|
if (bst)
|
|
|
|
@ -4090,6 +4187,8 @@ cleanup(void)
|
|
|
|
|
pthread_mutex_destroy(&mutex_clients);
|
|
|
|
|
pthread_mutex_destroy(&mutex_log);
|
|
|
|
|
pthread_rwlock_destroy(&rwlock_tx);
|
|
|
|
|
pthread_rwlock_destroy(&rwlock_acc);
|
|
|
|
|
pthread_rwlock_destroy(&rwlock_cfd);
|
|
|
|
|
pthread_cond_destroy(&cond_clients);
|
|
|
|
|
log_info("Pool shutdown successfully");
|
|
|
|
|
if (fd_log)
|
|
|
|
@ -4252,7 +4351,7 @@ int main(int argc, char **argv)
|
|
|
|
|
uuid_generate(iid);
|
|
|
|
|
memcpy(&instance_id, iid, 4);
|
|
|
|
|
|
|
|
|
|
pool_clients_init();
|
|
|
|
|
clients_init();
|
|
|
|
|
|
|
|
|
|
wui_context_t uic;
|
|
|
|
|
uic.port = config.webui_port;
|
|
|
|
|