mirror of
https://git.wownero.com/wownero/wownero-puddle.git
synced 2024-08-15 01:03:20 +00:00
2502 lines
75 KiB
C
2502 lines
75 KiB
C
/*
|
|
Copyright (c) 2014-2018, The Monero Project
|
|
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without modification, are
|
|
permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions of source code must retain the above copyright notice, this list of
|
|
conditions and the following disclaimer.
|
|
|
|
2. Redistributions in binary form must reproduce the above copyright notice, this list
|
|
of conditions and the following disclaimer in the documentation and/or other
|
|
materials provided with the distribution.
|
|
|
|
3. Neither the name of the copyright holder nor the names of its contributors may be
|
|
used to endorse or promote products derived from this software without specific
|
|
prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
Parts of the project are originally copyright (c) 2012-2013 The Cryptonote developers
|
|
*/
|
|
|
|
#include <netinet/in.h>
|
|
#include <sys/socket.h>
|
|
#include <fcntl.h>
|
|
|
|
#include <event2/event.h>
|
|
#include <event2/buffer.h>
|
|
#include <event2/bufferevent.h>
|
|
#include <event2/http.h>
|
|
|
|
#include <lmdb.h>
|
|
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <stdbool.h>
|
|
#include <math.h>
|
|
#include <uuid/uuid.h>
|
|
#include <getopt.h>
|
|
|
|
#define __STDC_FORMAT_MACROS
|
|
#include <inttypes.h>
|
|
|
|
#include <json-c/json.h>
|
|
#include <openssl/bn.h>
|
|
#include <pthread.h>
|
|
|
|
#include "util.h"
|
|
#include "xmr.h"
|
|
#include "log.h"
|
|
#include "webui.h"
|
|
|
|
#define MAX_LINE 4096
|
|
#define POOL_CLIENTS_GROW 1024
|
|
#define RPC_BODY_MAX 4096
|
|
#define JOB_BODY_MAX 2048
|
|
#define ERROR_BODY_MAX 512
|
|
#define STATUS_BODY_MAX 256
|
|
#define CLIENT_JOBS_MAX 4
|
|
#define BLOCK_HEADERS_MAX 4
|
|
#define BLOCK_TEMPLATES_MAX 4
|
|
#define MAINNET_ADDRESS_PREFIX 18
|
|
#define TESTNET_ADDRESS_PREFIX 53
|
|
#define BLOCK_HEADERS_RANGE 10
|
|
#define DB_SIZE 0x140000000 /* 5G */
|
|
#define DB_COUNT_MAX 10
|
|
#define MAX_PATH 1024
|
|
#define RPC_PATH "/json_rpc"
|
|
#define ADDRESS_MAX 128
|
|
#define BLOCK_TIME 120
|
|
#define HR_BLOCK_COUNT 5
|
|
|
|
#define uint128_t unsigned __int128
|
|
|
|
/*
|
|
A block is initially locked.
|
|
After height + 60 blocks, we check to see if its on the chain.
|
|
If not, it becomes orphaned, otherwise we unlock it for payouts.
|
|
*/
|
|
|
|
/*
|
|
Tables:
|
|
|
|
Shares
|
|
------
|
|
height <-> share_t
|
|
|
|
Blocks
|
|
------
|
|
height <-> block_t
|
|
|
|
Balance
|
|
-------
|
|
wallet addr <-> balance
|
|
|
|
Payments
|
|
--------
|
|
wallet addr <-> payment_t
|
|
*/
|
|
|
|
enum block_status { BLOCK_LOCKED=0, BLOCK_UNLOCKED=1, BLOCK_ORPHANED=2 };
|
|
|
|
typedef struct config_t
|
|
{
|
|
char rpc_host[256];
|
|
uint32_t rpc_port;
|
|
uint32_t rpc_timeout;
|
|
char wallet_rpc_host[256];
|
|
uint32_t wallet_rpc_port;
|
|
char pool_wallet[ADDRESS_MAX];
|
|
uint64_t pool_start_diff;
|
|
float share_mul;
|
|
float pool_fee;
|
|
float payment_threshold;
|
|
uint32_t pool_port;
|
|
uint32_t log_level;
|
|
uint32_t webui_port;
|
|
char log_file[MAX_PATH];
|
|
} config_t;
|
|
|
|
typedef struct block_template_t
|
|
{
|
|
char *blockhashing_blob;
|
|
char *blocktemplate_blob;
|
|
uint64_t difficulty;
|
|
uint64_t height;
|
|
char prev_hash[64];
|
|
uint32_t reserved_offset;
|
|
} block_template_t;
|
|
|
|
typedef struct job_t
|
|
{
|
|
uuid_t id;
|
|
char *blob;
|
|
block_template_t *block_template;
|
|
uint32_t extra_nonce;
|
|
uint64_t target;
|
|
uint128_t *submissions;
|
|
size_t submissions_count;
|
|
} job_t;
|
|
|
|
typedef struct client_t
|
|
{
|
|
int fd;
|
|
int json_id;
|
|
struct bufferevent *bev;
|
|
char address[ADDRESS_MAX];
|
|
char worker_id[64];
|
|
char client_id[32];
|
|
char agent[256];
|
|
job_t active_jobs[CLIENT_JOBS_MAX];
|
|
uint64_t hashes;
|
|
time_t connected_since;
|
|
bool is_proxy;
|
|
} client_t;
|
|
|
|
typedef struct pool_clients_t
|
|
{
|
|
client_t *clients;
|
|
size_t count;
|
|
} pool_clients_t;
|
|
|
|
typedef struct share_t
|
|
{
|
|
uint64_t height;
|
|
uint64_t difficulty;
|
|
char address[ADDRESS_MAX];
|
|
time_t timestamp;
|
|
} share_t;
|
|
|
|
typedef struct block_t
|
|
{
|
|
uint64_t height;
|
|
char hash[64];
|
|
char prev_hash[64];
|
|
uint64_t difficulty;
|
|
uint32_t status;
|
|
uint64_t reward;
|
|
time_t timestamp;
|
|
} block_t;
|
|
|
|
typedef struct payment_t
|
|
{
|
|
char tx_hash[64];
|
|
uint64_t amount;
|
|
time_t timestamp;
|
|
} payment_t;
|
|
|
|
typedef struct rpc_callback_t
|
|
{
|
|
void (*cb)(const char*, struct rpc_callback_t*);
|
|
void *data;
|
|
} rpc_callback_t;
|
|
|
|
static int database_init();
|
|
static void database_close();
|
|
static int store_share(uint64_t height, share_t *share);
|
|
static int store_block(uint64_t height, block_t *block);
|
|
static int process_blocks(block_t *blocks, size_t count);
|
|
static int payout_block(block_t *block, MDB_txn *parent);
|
|
static int balance_add(const char *address, uint64_t amount, MDB_txn *parent);
|
|
static int send_payments();
|
|
static int startup_pauout(uint64_t height);
|
|
static void update_pool_hr();
|
|
static void block_template_free(block_template_t *block_template);
|
|
static void block_templates_free();
|
|
static void last_block_headers_free();
|
|
static void pool_clients_init();
|
|
static void pool_clients_free();
|
|
static void pool_clients_send_job();
|
|
static void target_to_hex(uint64_t target, char *target_hex);
|
|
static void stratum_get_proxy_job_body(char *body, const client_t *client,
|
|
const char *block_hex, bool response);
|
|
static void stratum_get_job_body(char *body, const client_t *client, bool response);
|
|
static inline void stratum_get_error_body(char *body, int json_id, const char *error);
|
|
static inline void stratum_get_status_body(char *body, int json_id, const char *status);
|
|
static void client_add(int fd, struct bufferevent *bev);
|
|
static void client_find(struct bufferevent *bev, client_t **client);
|
|
static void client_clear(struct bufferevent *bev);
|
|
static void client_send_job(client_t *client, bool response);
|
|
static void client_clear_jobs(client_t *client);
|
|
static job_t * client_find_job(client_t *client, const char *job_id);
|
|
static void response_to_block_template(json_object *result, block_template_t *block_template);
|
|
static void response_to_block(json_object *block_header, block_t *block);
|
|
static void rpc_get_request_body(char *body, const char* method, char* fmt, ...);
|
|
static void rpc_on_response(struct evhttp_request *req, void *arg);
|
|
static void rpc_request(struct event_base *base, const char *body, rpc_callback_t *callback);
|
|
static void rpc_wallet_request(struct event_base *base, const char *body, rpc_callback_t *callback);
|
|
static void rpc_on_block_template(const char* data, rpc_callback_t *callback);
|
|
static void rpc_on_block_headers_range(const char* data, rpc_callback_t *callback);
|
|
static void rpc_on_block_header_by_height(const char* data, rpc_callback_t *callback);
|
|
static void rpc_on_last_block_header(const char* data, rpc_callback_t *callback);
|
|
static void rpc_on_block_submitted(const char* data, rpc_callback_t *callback);
|
|
static void rpc_on_wallet_transferred(const char* data, rpc_callback_t *callback);
|
|
static void timer_on_120s(int fd, short kind, void *ctx);
|
|
static void timer_on_10m(int fd, short kind, void *ctx);
|
|
static void client_on_login(json_object *message, client_t *client);
|
|
static void client_on_submit(json_object *message, client_t *client);
|
|
static void client_on_read(struct bufferevent *bev, void *ctx);
|
|
static void client_on_error(struct bufferevent *bev, short error, void *ctx);
|
|
static void client_on_accept(evutil_socket_t listener, short event, void *arg);
|
|
static void send_validation_error(const client_t *client, const char *message);
|
|
|
|
static config_t config;
|
|
static pool_clients_t pool_clients;
|
|
static block_t *last_block_headers[BLOCK_HEADERS_MAX];
|
|
static block_template_t *block_templates[BLOCK_TEMPLATES_MAX];
|
|
static struct event_base *base;
|
|
static struct event *timer_120s;
|
|
static struct event *timer_10m;
|
|
static uint32_t extra_nonce;
|
|
static block_t block_headers_range[BLOCK_HEADERS_RANGE];
|
|
static MDB_env *env;
|
|
static MDB_dbi db_shares;
|
|
static MDB_dbi db_blocks;
|
|
static MDB_dbi db_balance;
|
|
static MDB_dbi db_payments;
|
|
static BN_CTX *bn_ctx;
|
|
static BIGNUM *base_diff;
|
|
static pool_stats_t pool_stats;
|
|
static pthread_mutex_t mutex_clients = PTHREAD_MUTEX_INITIALIZER;
|
|
static FILE *fd_log;
|
|
|
|
#define JSON_GET_OR_ERROR(name, parent, type, client) \
|
|
json_object *name = NULL; \
|
|
if (!json_object_object_get_ex(parent, #name, &name)) \
|
|
return send_validation_error(client, #name " not found"); \
|
|
if (!json_object_is_type(name, type)) \
|
|
return send_validation_error(client, #name " not a " #type);
|
|
|
|
#define JSON_GET_OR_WARN(name, parent, type) \
|
|
json_object *name = NULL; \
|
|
if (!json_object_object_get_ex(parent, #name, &name)) { \
|
|
log_warn(#name " not found"); \
|
|
} else { \
|
|
if (!json_object_is_type(name, type)) { \
|
|
log_warn(#name " not a " #type); \
|
|
} \
|
|
}
|
|
|
|
static int
|
|
compare_uint64(const MDB_val *a, const MDB_val *b)
|
|
{
|
|
const uint64_t va = *(const uint64_t *)a->mv_data;
|
|
const uint64_t vb = *(const uint64_t *)b->mv_data;
|
|
return (va < vb) ? -1 : va > vb;
|
|
}
|
|
|
|
static int
|
|
compare_string(const MDB_val *a, const MDB_val *b)
|
|
{
|
|
const char *va = (const char*) a->mv_data;
|
|
const char *vb = (const char*) b->mv_data;
|
|
return strcmp(va, vb);
|
|
}
|
|
|
|
static int
|
|
compare_block(const MDB_val *a, const MDB_val *b)
|
|
{
|
|
const block_t *va = (const block_t*) a->mv_data;
|
|
const block_t *vb = (const block_t*) b->mv_data;
|
|
int sc = memcmp(va->hash, vb->hash, 64);
|
|
if (sc == 0)
|
|
return (va->timestamp < vb->timestamp) ? -1 : va->timestamp > vb->timestamp;
|
|
else
|
|
return sc;
|
|
}
|
|
|
|
static int
|
|
compare_share(const MDB_val *a, const MDB_val *b)
|
|
{
|
|
const share_t *va = (const share_t*) a->mv_data;
|
|
const share_t *vb = (const share_t*) b->mv_data;
|
|
int sc = strcmp(va->address, vb->address);
|
|
if (sc == 0)
|
|
return (va->timestamp < vb->timestamp) ? -1 : va->timestamp > vb->timestamp;
|
|
else
|
|
return sc;
|
|
}
|
|
|
|
static int
|
|
compare_payment(const MDB_val *a, const MDB_val *b)
|
|
{
|
|
const payment_t *va = (const payment_t*) a->mv_data;
|
|
const payment_t *vb = (const payment_t*) b->mv_data;
|
|
return (va->timestamp < vb->timestamp) ? -1 : va->timestamp > vb->timestamp;
|
|
}
|
|
|
|
static int
|
|
database_init()
|
|
{
|
|
int rc;
|
|
char *err;
|
|
MDB_txn *txn;
|
|
|
|
rc = mdb_env_create(&env);
|
|
mdb_env_set_maxdbs(env, (MDB_dbi) DB_COUNT_MAX);
|
|
mdb_env_set_mapsize(env, DB_SIZE);
|
|
if ((rc = mdb_env_open(env, "./data", 0, 0664)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_fatal("%s\n", err);
|
|
exit(rc);
|
|
}
|
|
if ((rc = mdb_txn_begin(env, NULL, 0, &txn)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_fatal("%s\n", err);
|
|
exit(rc);
|
|
}
|
|
if ((rc = mdb_dbi_open(txn, "shares", MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, &db_shares)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_fatal("%s\n", err);
|
|
exit(rc);
|
|
}
|
|
if ((rc = mdb_dbi_open(txn, "blocks", MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, &db_blocks)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_fatal("%s\n", err);
|
|
exit(rc);
|
|
}
|
|
if ((rc = mdb_dbi_open(txn, "payments", MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, &db_payments)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_fatal("%s\n", err);
|
|
exit(rc);
|
|
}
|
|
if ((rc = mdb_dbi_open(txn, "balance", MDB_CREATE, &db_balance)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_fatal("%s\n", err);
|
|
exit(rc);
|
|
}
|
|
|
|
mdb_set_compare(txn, db_shares, compare_uint64);
|
|
mdb_set_dupsort(txn, db_shares, compare_share);
|
|
|
|
mdb_set_compare(txn, db_blocks, compare_uint64);
|
|
mdb_set_dupsort(txn, db_blocks, compare_block);
|
|
|
|
mdb_set_compare(txn, db_payments, compare_string);
|
|
mdb_set_dupsort(txn, db_payments, compare_payment);
|
|
|
|
mdb_set_compare(txn, db_balance, compare_string);
|
|
|
|
rc = mdb_txn_commit(txn);
|
|
return rc;
|
|
}
|
|
|
|
static void
|
|
database_close()
|
|
{
|
|
log_info("Closing database");
|
|
mdb_dbi_close(env, db_shares);
|
|
mdb_dbi_close(env, db_blocks);
|
|
mdb_dbi_close(env, db_balance);
|
|
mdb_dbi_close(env, db_payments);
|
|
return mdb_env_close(env);
|
|
}
|
|
|
|
static int
|
|
store_share(uint64_t height, share_t *share)
|
|
{
|
|
int rc;
|
|
char *err;
|
|
MDB_txn *txn;
|
|
MDB_cursor *cursor;
|
|
if ((rc = mdb_txn_begin(env, NULL, 0, &txn)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
return rc;
|
|
}
|
|
if ((rc = mdb_cursor_open(txn, db_shares, &cursor)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
mdb_txn_abort(txn);
|
|
return rc;
|
|
}
|
|
|
|
MDB_val key = { sizeof(height), (void*)&height };
|
|
MDB_val val = { sizeof(share_t), (void*)share };
|
|
mdb_cursor_put(cursor, &key, &val, MDB_APPENDDUP);
|
|
|
|
rc = mdb_txn_commit(txn);
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
store_block(uint64_t height, block_t *block)
|
|
{
|
|
int rc;
|
|
char *err;
|
|
MDB_txn *txn;
|
|
MDB_cursor *cursor;
|
|
if ((rc = mdb_txn_begin(env, NULL, 0, &txn)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
return rc;
|
|
}
|
|
if ((rc = mdb_cursor_open(txn, db_blocks, &cursor)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
mdb_txn_abort(txn);
|
|
return rc;
|
|
}
|
|
|
|
MDB_val key = { sizeof(height), (void*)&height };
|
|
MDB_val val = { sizeof(block_t), (void*)block };
|
|
mdb_cursor_put(cursor, &key, &val, MDB_APPENDDUP);
|
|
|
|
rc = mdb_txn_commit(txn);
|
|
return rc;
|
|
}
|
|
|
|
uint64_t
|
|
miner_hr(const char *address)
|
|
{
|
|
pthread_mutex_lock(&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 != 0 && strncmp(c->address, address, ADDRESS_MAX) == 0)
|
|
{
|
|
double d = difftime(time(NULL), c->connected_since);
|
|
if (d == 0.0)
|
|
continue;
|
|
hr += c->hashes / d;
|
|
continue;
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&mutex_clients);
|
|
return hr;
|
|
}
|
|
|
|
uint64_t
|
|
miner_balance(const char *address)
|
|
{
|
|
if (strlen(address) > ADDRESS_MAX)
|
|
return 0;
|
|
int rc;
|
|
char *err;
|
|
MDB_txn *txn;
|
|
MDB_cursor *cursor;
|
|
if ((rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
return 0;
|
|
}
|
|
if ((rc = mdb_cursor_open(txn, db_balance, &cursor)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
mdb_txn_abort(txn);
|
|
return 0;
|
|
}
|
|
|
|
MDB_val key = {ADDRESS_MAX, (void*)address};
|
|
MDB_val val;
|
|
uint64_t balance = 0;
|
|
|
|
rc = mdb_cursor_get(cursor, &key, &val, MDB_SET);
|
|
if (rc != 0 && rc != MDB_NOTFOUND)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
goto cleanup;
|
|
}
|
|
if (rc != 0)
|
|
goto cleanup;
|
|
|
|
balance = *(uint64_t*)val.mv_data;
|
|
|
|
cleanup:
|
|
mdb_cursor_close(cursor);
|
|
mdb_txn_abort(txn);
|
|
return balance;
|
|
}
|
|
|
|
static int
|
|
process_blocks(block_t *blocks, size_t count)
|
|
{
|
|
log_debug("Processing blocks");
|
|
/*
|
|
For each block, lookup block in db.
|
|
If found, make sure found is locked and not orphaned.
|
|
If both not orphaned and unlocked, payout, set unlocked.
|
|
If block heights differ / orphaned, set orphaned.
|
|
*/
|
|
int rc;
|
|
char *err;
|
|
MDB_txn *txn;
|
|
MDB_cursor *cursor;
|
|
if ((rc = mdb_txn_begin(env, NULL, 0, &txn)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
return rc;
|
|
}
|
|
if ((rc = mdb_cursor_open(txn, db_blocks, &cursor)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
mdb_txn_abort(txn);
|
|
return rc;
|
|
}
|
|
|
|
for (int i=0; i<count; i++)
|
|
{
|
|
block_t *ib = &blocks[i];
|
|
log_trace("Processing block at height %"PRIu64, ib->height);
|
|
MDB_val key = { sizeof(ib->height), (void*)&ib->height };
|
|
MDB_val val;
|
|
MDB_cursor_op op = MDB_SET;
|
|
while (1)
|
|
{
|
|
rc = mdb_cursor_get(cursor, &key, &val, op);
|
|
op = MDB_NEXT_DUP;
|
|
if (rc == MDB_NOTFOUND || rc != 0)
|
|
{
|
|
log_trace("No stored block at height %"PRIu64, ib->height);
|
|
if (rc != MDB_NOTFOUND)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_debug("No stored block at height %"PRIu64" with error: %d", ib->height, err);
|
|
}
|
|
break;
|
|
}
|
|
block_t *sb = (block_t*)val.mv_data;
|
|
if (sb->status != BLOCK_LOCKED)
|
|
{
|
|
continue;
|
|
}
|
|
if (ib->status & BLOCK_ORPHANED)
|
|
{
|
|
log_debug("Orphaned block at height %"PRIu64, ib->height);
|
|
block_t bp;
|
|
memcpy(&bp, sb, sizeof(block_t));
|
|
bp.status |= BLOCK_ORPHANED;
|
|
MDB_val new_val = {sizeof(block_t), (void*)&bp};
|
|
mdb_cursor_put(cursor, &key, &new_val, MDB_CURRENT);
|
|
continue;
|
|
}
|
|
if (memcmp(ib->hash, sb->hash, 64) == 0 && memcmp(ib->prev_hash, sb->prev_hash, 64) != 0)
|
|
{
|
|
log_warn("Have a block with matching heights but differing parents! Setting orphaned.\n");
|
|
block_t bp;
|
|
memcpy(&bp, sb, sizeof(block_t));
|
|
bp.status |= BLOCK_ORPHANED;
|
|
MDB_val new_val = {sizeof(block_t), (void*)&bp};
|
|
mdb_cursor_put(cursor, &key, &new_val, MDB_CURRENT);
|
|
continue;
|
|
}
|
|
block_t bp;
|
|
memcpy(&bp, sb, sizeof(block_t));
|
|
bp.status |= BLOCK_UNLOCKED;
|
|
bp.reward = ib->reward;
|
|
rc = payout_block(&bp, txn);
|
|
if (rc == 0)
|
|
{
|
|
log_debug("Paided out block %"PRIu64, bp.height);
|
|
MDB_val new_val = {sizeof(block_t), (void*)&bp};
|
|
mdb_cursor_put(cursor, &key, &new_val, MDB_CURRENT);
|
|
}
|
|
else
|
|
log_trace("%s", mdb_strerror(rc));
|
|
}
|
|
}
|
|
|
|
rc = mdb_txn_commit(txn);
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
payout_block(block_t *block, MDB_txn *parent)
|
|
{
|
|
/*
|
|
PPLNS
|
|
*/
|
|
log_info("Payout on block at height %"PRIu64, block->height);
|
|
int rc;
|
|
char *err;
|
|
MDB_txn *txn;
|
|
MDB_cursor *cursor;
|
|
uint64_t height = block->height;
|
|
uint64_t total_paid = 0;
|
|
if ((rc = mdb_txn_begin(env, parent, 0, &txn)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
return rc;
|
|
}
|
|
if ((rc = mdb_cursor_open(txn, db_shares, &cursor)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
mdb_txn_abort(txn);
|
|
return rc;
|
|
}
|
|
|
|
MDB_cursor_op op = MDB_SET;
|
|
while (1)
|
|
{
|
|
uint64_t current_height = height;
|
|
MDB_val key = { sizeof(current_height), (void*)¤t_height };
|
|
MDB_val val;
|
|
rc = mdb_cursor_get(cursor, &key, &val, op);
|
|
op = MDB_NEXT_DUP;
|
|
if (rc == MDB_NOTFOUND && total_paid < block->reward)
|
|
{
|
|
if (height == 0)
|
|
break;
|
|
height--;
|
|
op = MDB_SET;
|
|
continue;
|
|
}
|
|
if (rc != 0 && rc != MDB_NOTFOUND)
|
|
{
|
|
log_error("Error getting balance: %s", mdb_strerror(rc));
|
|
break;
|
|
}
|
|
if (total_paid == block->reward)
|
|
break;
|
|
|
|
share_t *share = (share_t*)val.mv_data;
|
|
uint64_t amount = floor((double)share->difficulty / ((double)block->difficulty * config.share_mul) * block->reward);
|
|
if (total_paid + amount > block->reward)
|
|
amount = block->reward - total_paid;
|
|
total_paid += amount;
|
|
uint64_t fee = amount * config.pool_fee;
|
|
amount -= fee;
|
|
if (amount == 0)
|
|
continue;
|
|
rc = balance_add(share->address, amount, txn);
|
|
if (rc != 0)
|
|
{
|
|
mdb_cursor_close(cursor);
|
|
mdb_txn_abort(txn);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
rc = mdb_txn_commit(txn);
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
balance_add(const char *address, uint64_t amount, MDB_txn *parent)
|
|
{
|
|
log_trace("Adding %"PRIu64" to %s's balance", amount, address);
|
|
int rc;
|
|
char *err;
|
|
MDB_txn *txn;
|
|
MDB_cursor *cursor;
|
|
if ((rc = mdb_txn_begin(env, parent, 0, &txn)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
return rc;
|
|
}
|
|
if ((rc = mdb_cursor_open(txn, db_balance, &cursor)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
mdb_txn_abort(txn);
|
|
return rc;
|
|
}
|
|
|
|
MDB_val key = {ADDRESS_MAX, (void*)address};
|
|
MDB_val val;
|
|
rc = mdb_cursor_get(cursor, &key, &val, MDB_SET);
|
|
if (rc == MDB_NOTFOUND)
|
|
{
|
|
log_trace("Adding new balance entry");
|
|
MDB_val new_val = { sizeof(amount), (void*)&amount };
|
|
mdb_cursor_put(cursor, &key, &new_val, MDB_APPEND);
|
|
}
|
|
else if (rc == 0)
|
|
{
|
|
log_trace("Updating existing balance entry");
|
|
uint64_t current_amount = *(uint64_t*)val.mv_data;
|
|
current_amount += amount;
|
|
MDB_val new_val = {sizeof(current_amount), (void*)¤t_amount};
|
|
rc = mdb_cursor_put(cursor, &key, &new_val, MDB_CURRENT);
|
|
if (rc != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
mdb_txn_abort(txn);
|
|
return rc;
|
|
}
|
|
|
|
rc = mdb_txn_commit(txn);
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
send_payments()
|
|
{
|
|
uint64_t threshold = 1000000000000 * config.payment_threshold;
|
|
int rc;
|
|
char *err;
|
|
MDB_txn *txn;
|
|
MDB_cursor *cursor;
|
|
if ((rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
return rc;
|
|
}
|
|
if ((rc = mdb_cursor_open(txn, db_balance, &cursor)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
mdb_txn_abort(txn);
|
|
return rc;
|
|
}
|
|
|
|
MDB_cursor_op op = MDB_FIRST;
|
|
while (1)
|
|
{
|
|
MDB_val key;
|
|
MDB_val val;
|
|
rc = mdb_cursor_get(cursor, &key, &val, op);
|
|
op = MDB_NEXT;
|
|
if (rc != 0)
|
|
break;
|
|
|
|
const char *address = (const char*)key.mv_data;
|
|
uint64_t amount = *(uint64_t*)val.mv_data;
|
|
|
|
if (amount < threshold)
|
|
continue;
|
|
|
|
log_info("Sending payment of %"PRIu64" to %s\n", amount, address);
|
|
|
|
char body[RPC_BODY_MAX];
|
|
snprintf(body, RPC_BODY_MAX, "{\"id\":\"0\",\"jsonrpc\":\"2.0\",\"method\":\"transfer\",\"params\":{"
|
|
"\"destinations\":[{\"amount\":%"PRIu64",\"address\":\"%s\"}],\"mixin\":10}}",
|
|
amount, address);
|
|
log_trace(body);
|
|
rpc_callback_t *callback = calloc(1, sizeof(rpc_callback_t));
|
|
callback->data = calloc(ADDRESS_MAX, sizeof(char));
|
|
memcpy(callback->data, address, ADDRESS_MAX);
|
|
callback->cb = rpc_on_wallet_transferred;
|
|
rpc_wallet_request(base, body, callback);
|
|
}
|
|
mdb_cursor_close(cursor);
|
|
mdb_txn_abort(txn);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
startup_pauout(uint64_t height)
|
|
{
|
|
/*
|
|
Loop stored blocks < height - 60
|
|
If block locked & not orphaned, payout
|
|
*/
|
|
int rc;
|
|
char *err;
|
|
MDB_txn *txn;
|
|
MDB_cursor *cursor;
|
|
if ((rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
return rc;
|
|
}
|
|
if ((rc = mdb_cursor_open(txn, db_blocks, &cursor)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
mdb_txn_abort(txn);
|
|
return rc;
|
|
}
|
|
|
|
pool_stats.pool_blocks_found = 0;
|
|
MDB_cursor_op op = MDB_FIRST;
|
|
while (1)
|
|
{
|
|
MDB_val key;
|
|
MDB_val val;
|
|
rc = mdb_cursor_get(cursor, &key, &val, op);
|
|
op = MDB_NEXT;
|
|
if (rc != 0 && rc != MDB_NOTFOUND)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
break;
|
|
}
|
|
if (rc == MDB_NOTFOUND)
|
|
break;
|
|
|
|
pool_stats.pool_blocks_found++;
|
|
|
|
block_t *block = (block_t*)val.mv_data;
|
|
pool_stats.last_block_found = block->timestamp;
|
|
|
|
if (block->height > height - 60)
|
|
break;
|
|
if (block->status & BLOCK_UNLOCKED || block->status & BLOCK_ORPHANED)
|
|
continue;
|
|
|
|
char body[RPC_BODY_MAX];
|
|
rpc_get_request_body(body, "get_block_header_by_height", "sd", "height", block->height);
|
|
rpc_callback_t *c = calloc(1, sizeof(rpc_callback_t));
|
|
c->cb = rpc_on_block_header_by_height;
|
|
rpc_request(base, body, c);
|
|
}
|
|
|
|
mdb_cursor_close(cursor);
|
|
mdb_txn_abort(txn);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
update_pool_hr()
|
|
{
|
|
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 != 0)
|
|
{
|
|
double d = difftime(time(NULL), c->connected_since);
|
|
if (d == 0.0)
|
|
continue;
|
|
hr += c->hashes / d;
|
|
}
|
|
}
|
|
pool_stats.pool_hashrate = hr;
|
|
}
|
|
|
|
static void
|
|
block_template_free(block_template_t *block_template)
|
|
{
|
|
free(block_template->blockhashing_blob);
|
|
free(block_template->blocktemplate_blob);
|
|
free(block_template);
|
|
}
|
|
|
|
static void
|
|
block_templates_free()
|
|
{
|
|
for (size_t i=0; i<BLOCK_TEMPLATES_MAX; i++)
|
|
{
|
|
block_template_t *bt = block_templates[i];
|
|
if (bt != NULL)
|
|
{
|
|
free(bt->blockhashing_blob);
|
|
free(bt->blocktemplate_blob);
|
|
free(bt);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
last_block_headers_free()
|
|
{
|
|
for (size_t i=0; i<BLOCK_HEADERS_MAX; i++)
|
|
{
|
|
block_t *block = last_block_headers[i];
|
|
if (block != NULL)
|
|
free(block);
|
|
}
|
|
}
|
|
|
|
static void
|
|
pool_clients_init()
|
|
{
|
|
assert(pool_clients.count == 0);
|
|
pool_clients.count = POOL_CLIENTS_GROW;
|
|
pool_clients.clients = (client_t*) calloc(pool_clients.count, sizeof(client_t));
|
|
}
|
|
|
|
static void
|
|
pool_clients_free()
|
|
{
|
|
assert(pool_clients.count != 0);
|
|
client_t *c = pool_clients.clients;
|
|
for (size_t i = 0; i < pool_clients.count; i++, c++)
|
|
{
|
|
client_clear_jobs(c);
|
|
}
|
|
free(pool_clients.clients);
|
|
}
|
|
|
|
static job_t *
|
|
client_find_job(client_t *client, const char *job_id)
|
|
{
|
|
char jid[33];
|
|
for (size_t i=0; i<CLIENT_JOBS_MAX; i++)
|
|
{
|
|
memset(jid, 0, 33);
|
|
job_t *job = &client->active_jobs[i];
|
|
bin_to_hex((const char*)job->id, sizeof(uuid_t), jid);
|
|
jid[32] = '\0';
|
|
if (strcmp(job_id, jid) == 0)
|
|
return job;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
pool_clients_send_job()
|
|
{
|
|
client_t *c = pool_clients.clients;
|
|
for (size_t i = 0; i < pool_clients.count; i++, c++)
|
|
{
|
|
if (c->fd == 0)
|
|
continue;
|
|
client_send_job(c, false);
|
|
}
|
|
}
|
|
|
|
static void
|
|
target_to_hex(uint64_t target, char *target_hex)
|
|
{
|
|
if (target & 0xFFFFFFFF00000000)
|
|
{
|
|
log_debug("High target requested: %"PRIu64, target);
|
|
bin_to_hex((const char*)&target, 8, &target_hex[0]);
|
|
target_hex[16] = '\0';
|
|
return;
|
|
}
|
|
BIGNUM *diff = BN_new();
|
|
BIGNUM *bnt = BN_new();
|
|
#ifdef SIXTY_FOUR_BIT_LONG
|
|
BN_set_word(bnt, target);
|
|
#else
|
|
char st[24];
|
|
snprintf(st, 24, "%"PRIu64, target);
|
|
BN_dec2bn(&bnt, st);
|
|
#endif
|
|
BN_div(diff, NULL, base_diff, bnt, bn_ctx);
|
|
BN_rshift(diff, diff, 224);
|
|
uint32_t w = BN_get_word(diff);
|
|
bin_to_hex((const char*)&w, 4, &target_hex[0]);
|
|
target_hex[8] = '\0';
|
|
BN_free(bnt);
|
|
BN_free(diff);
|
|
}
|
|
|
|
static void
|
|
stratum_get_proxy_job_body(char *body, const client_t *client, const char *block_hex, bool response)
|
|
{
|
|
int json_id = client->json_id;
|
|
const char *client_id = client->client_id;
|
|
const job_t *job = &client->active_jobs[0];
|
|
char job_id[33];
|
|
bin_to_hex((const char*)job->id, sizeof(uuid_t), job_id);
|
|
uint64_t target = job->target;
|
|
char target_hex[17];
|
|
target_to_hex(target, &target_hex[0]);
|
|
const block_template_t *bt = job->block_template;
|
|
|
|
if (response)
|
|
{
|
|
snprintf(body, JOB_BODY_MAX, "{\"id\":%d,\"jsonrpc\":\"2.0\",\"error\":null,\"result\""
|
|
":{\"id\":\"%.32s\",\"job\":{\"blocktemplate_blob\":\"%s\",\"job_id\":\"%.32s\","
|
|
"\"difficulty\":%"PRIu64",\"height\":%"PRIu64",\"reserved_offset\":%u,\"client_nonce_offset\":%u,"
|
|
"\"client_pool_offset\":%u,\"target_diff\":%"PRIu64",\"target_diff_hex\":\"%s\"},"
|
|
"\"status\":\"OK\"}}\n", json_id, client_id, block_hex, job_id,
|
|
bt->difficulty, bt->height, bt->reserved_offset, bt->reserved_offset + 12,
|
|
bt->reserved_offset + 8, target, target_hex);
|
|
}
|
|
else
|
|
{
|
|
snprintf(body, JOB_BODY_MAX, "{\"jsonrpc\":\"2.0\",\"method\":\"job\",\"params\""
|
|
":{\"id\":\"%.32s\",\"job\":{\"blocktemplate_blob\":\"%s\",\"job_id\":\"%.32s\","
|
|
"\"difficulty\":%"PRIu64",\"height\":%"PRIu64",\"reserved_offset\":%u,\"client_nonce_offset\":%u,"
|
|
"\"client_pool_offset\":%u,\"target_diff\":%"PRIu64",\"target_diff_hex\":\"%s\"},"
|
|
"\"status\":\"OK\"}}\n", client_id, block_hex, job_id,
|
|
bt->difficulty, bt->height, bt->reserved_offset, bt->reserved_offset + 12,
|
|
bt->reserved_offset + 8, target, target_hex);
|
|
}
|
|
}
|
|
|
|
static void
|
|
stratum_get_job_body(char *body, const client_t *client, bool response)
|
|
{
|
|
int json_id = client->json_id;
|
|
const char *client_id = client->client_id;
|
|
const job_t *job = &client->active_jobs[0];
|
|
char job_id[33];
|
|
bin_to_hex((const char*)job->id, sizeof(uuid_t), job_id);
|
|
const char *blob = job->blob;
|
|
uint64_t target = job->target;
|
|
uint64_t height = job->block_template->height;
|
|
char target_hex[17];
|
|
target_to_hex(target, &target_hex[0]);
|
|
|
|
if (response)
|
|
{
|
|
snprintf(body, JOB_BODY_MAX, "{\"id\":%d,\"jsonrpc\":\"2.0\",\"error\":null,\"result\""
|
|
":{\"id\":\"%.32s\",\"job\":{"
|
|
"\"blob\":\"%s\",\"job_id\":\"%.32s\",\"target\":\"%s\",\"height\":%"PRIu64"},"
|
|
"\"status\":\"OK\"}}\n", json_id, client_id, blob, job_id, target_hex, height);
|
|
}
|
|
else
|
|
{
|
|
snprintf(body, JOB_BODY_MAX, "{\"jsonrpc\":\"2.0\",\"method\":\"job\",\"params\""
|
|
":{\"id\":\"%.32s\",\"blob\":\"%s\",\"job_id\":\"%.32s\",\"target\":\"%s\","
|
|
"\"height\":%"PRIu64"}}\n",
|
|
client_id, blob, job_id, target_hex, height);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
stratum_get_error_body(char *body, int json_id, const char *error)
|
|
{
|
|
snprintf(body, ERROR_BODY_MAX, "{\"id\":%d,\"jsonrpc\":\"2.0\",\"error\":"
|
|
"{\"code\":-1, \"message\":\"%s\"}}\n", json_id, error);
|
|
}
|
|
|
|
static inline void
|
|
stratum_get_status_body(char *body, int json_id, const char *status)
|
|
{
|
|
snprintf(body, STATUS_BODY_MAX, "{\"id\":%d,\"jsonrpc\":\"2.0\",\"error\":null,\"result\":{\"status\":\"%s\"}}\n",
|
|
json_id, status);
|
|
}
|
|
|
|
static void
|
|
response_to_block_template(json_object *result, block_template_t *block_template)
|
|
{
|
|
JSON_GET_OR_WARN(blockhashing_blob, result, json_type_string);
|
|
JSON_GET_OR_WARN(blocktemplate_blob, result, json_type_string);
|
|
JSON_GET_OR_WARN(difficulty, result, json_type_int);
|
|
JSON_GET_OR_WARN(height, result, json_type_int);
|
|
JSON_GET_OR_WARN(prev_hash, result, json_type_string);
|
|
JSON_GET_OR_WARN(reserved_offset, result, json_type_int);
|
|
block_template->blockhashing_blob = strdup(json_object_get_string(blockhashing_blob));
|
|
block_template->blocktemplate_blob = strdup(json_object_get_string(blocktemplate_blob));
|
|
block_template->difficulty = json_object_get_int64(difficulty);
|
|
block_template->height = json_object_get_int64(height);
|
|
memcpy(block_template->prev_hash, json_object_get_string(prev_hash), 64);
|
|
block_template->reserved_offset = json_object_get_int(reserved_offset);
|
|
}
|
|
|
|
static void
|
|
response_to_block(json_object *block_header, block_t *block)
|
|
{
|
|
memset(block, 0, sizeof(block_t));
|
|
JSON_GET_OR_WARN(height, block_header, json_type_int);
|
|
JSON_GET_OR_WARN(difficulty, block_header, json_type_int);
|
|
JSON_GET_OR_WARN(hash, block_header, json_type_string);
|
|
JSON_GET_OR_WARN(prev_hash, block_header, json_type_string);
|
|
JSON_GET_OR_WARN(timestamp, block_header, json_type_int);
|
|
JSON_GET_OR_WARN(reward, block_header, json_type_int);
|
|
JSON_GET_OR_WARN(orphan_status, block_header, json_type_boolean);
|
|
block->height = json_object_get_int64(height);
|
|
block->difficulty = json_object_get_int64(difficulty);
|
|
memcpy(block->hash, json_object_get_string(hash), 64);
|
|
memcpy(block->prev_hash, json_object_get_string(prev_hash), 64);
|
|
block->timestamp = json_object_get_int64(timestamp);
|
|
block->reward = json_object_get_int64(reward);
|
|
if (json_object_get_int(orphan_status))
|
|
block->status |= BLOCK_ORPHANED;
|
|
}
|
|
|
|
static void
|
|
rpc_get_request_body(char *body, const char* method, char* fmt, ...)
|
|
{
|
|
char *pb = body;
|
|
|
|
snprintf(pb, RPC_BODY_MAX, "%s%s%s", "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"", method, "\"");
|
|
pb += strlen(pb);
|
|
|
|
if (fmt && *fmt)
|
|
{
|
|
char *s;
|
|
uint64_t d;
|
|
snprintf(pb, RPC_BODY_MAX - strlen(body), "%s", ",\"params\":{");
|
|
pb += strlen(pb);
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
uint8_t count = 0;
|
|
while (*fmt)
|
|
{
|
|
switch (*fmt++)
|
|
{
|
|
case 's':
|
|
s = va_arg(args, char *);
|
|
snprintf(pb, RPC_BODY_MAX - strlen(body), "\"%s\"", s);
|
|
pb += strlen(pb);
|
|
break;
|
|
case 'd':
|
|
d = va_arg(args, uint64_t);
|
|
snprintf(pb, RPC_BODY_MAX - strlen(body), "%"PRIu64, d);
|
|
pb += strlen(pb);
|
|
break;
|
|
}
|
|
char append = ':';
|
|
if (count++ % 2 != 0)
|
|
append = ',';
|
|
*pb++ = append;
|
|
}
|
|
va_end(args);
|
|
*--pb = '}';
|
|
pb++;
|
|
}
|
|
*pb = '}';
|
|
log_trace("Payload: %s", body);
|
|
}
|
|
|
|
static void
|
|
rpc_on_response(struct evhttp_request *req, void *arg)
|
|
{
|
|
struct evbuffer *input;
|
|
rpc_callback_t *callback = (rpc_callback_t*) arg;
|
|
|
|
if (!req)
|
|
{
|
|
log_error("Request failure. Aborting.");
|
|
goto cleanup;
|
|
}
|
|
|
|
int rc = evhttp_request_get_response_code(req);
|
|
if (rc < 200 || rc >= 300)
|
|
{
|
|
log_error("HTTP status code %d for %s. Aborting.", rc, evhttp_request_get_uri(req));
|
|
goto cleanup;
|
|
}
|
|
|
|
input = evhttp_request_get_input_buffer(req);
|
|
size_t len = evbuffer_get_length(input);
|
|
char *data = (char*) alloca(len+1);
|
|
evbuffer_remove(input, data, len);
|
|
data[len] = '\0';
|
|
callback->cb(data, callback);
|
|
cleanup:
|
|
if (callback->data)
|
|
free(callback->data);
|
|
free(callback);
|
|
}
|
|
|
|
static void
|
|
rpc_request(struct event_base *base, const char *body, rpc_callback_t *callback)
|
|
{
|
|
struct evhttp_connection *con;
|
|
struct evhttp_request *req;
|
|
struct evkeyvalq *headers;
|
|
struct evbuffer *output;
|
|
|
|
con = evhttp_connection_base_new(base, NULL, config.rpc_host, config.rpc_port);
|
|
evhttp_connection_free_on_completion(con);
|
|
evhttp_connection_set_timeout(con, config.rpc_timeout);
|
|
req = evhttp_request_new(rpc_on_response, callback);
|
|
output = evhttp_request_get_output_buffer(req);
|
|
evbuffer_add(output, body, strlen(body));
|
|
headers = evhttp_request_get_output_headers(req);
|
|
evhttp_add_header(headers, "Content-Type", "application/json");
|
|
evhttp_add_header(headers, "Connection", "close");
|
|
evhttp_make_request(con, req, EVHTTP_REQ_POST, RPC_PATH);
|
|
}
|
|
|
|
static void
|
|
rpc_wallet_request(struct event_base *base, const char *body, rpc_callback_t *callback)
|
|
{
|
|
struct evhttp_connection *con;
|
|
struct evhttp_request *req;
|
|
struct evkeyvalq *headers;
|
|
struct evbuffer *output;
|
|
|
|
con = evhttp_connection_base_new(base, NULL, config.wallet_rpc_host, config.wallet_rpc_port);
|
|
evhttp_connection_free_on_completion(con);
|
|
evhttp_connection_set_timeout(con, config.rpc_timeout);
|
|
req = evhttp_request_new(rpc_on_response, callback);
|
|
output = evhttp_request_get_output_buffer(req);
|
|
evbuffer_add(output, body, strlen(body));
|
|
headers = evhttp_request_get_output_headers(req);
|
|
evhttp_add_header(headers, "Content-Type", "application/json");
|
|
evhttp_add_header(headers, "Connection", "close");
|
|
evhttp_make_request(con, req, EVHTTP_REQ_POST, RPC_PATH);
|
|
}
|
|
|
|
static void
|
|
rpc_on_block_headers_range(const char* data, rpc_callback_t *callback)
|
|
{
|
|
json_object *root = json_tokener_parse(data);
|
|
JSON_GET_OR_WARN(result, root, json_type_object);
|
|
JSON_GET_OR_WARN(status, result, json_type_string);
|
|
const char *ss = json_object_get_string(status);
|
|
json_object *error = NULL;
|
|
json_object_object_get_ex(root, "error", &error);
|
|
if (error != NULL)
|
|
{
|
|
JSON_GET_OR_WARN(code, error, json_type_object);
|
|
JSON_GET_OR_WARN(message, error, json_type_string);
|
|
int ec = json_object_get_int(code);
|
|
const char *em = json_object_get_string(message);
|
|
log_warn("Error (%d) getting block headers by range: %s", ec, em);
|
|
json_object_put(root);
|
|
return;
|
|
}
|
|
if (status == NULL || strcmp(ss, "OK") != 0)
|
|
{
|
|
log_warn("Error getting block headers by range: %s", ss);
|
|
json_object_put(root);
|
|
return;
|
|
}
|
|
|
|
JSON_GET_OR_WARN(headers, result, json_type_array);
|
|
size_t headers_len = json_object_array_length(headers);
|
|
assert(headers_len == BLOCK_HEADERS_RANGE);
|
|
for (int i=0; i<headers_len; i++)
|
|
{
|
|
json_object *header = json_object_array_get_idx(headers, i);
|
|
block_t *bh = &block_headers_range[i];
|
|
response_to_block(header, bh);
|
|
}
|
|
process_blocks(block_headers_range, BLOCK_HEADERS_RANGE);
|
|
json_object_put(root);
|
|
}
|
|
|
|
static void
|
|
rpc_on_block_header_by_height(const char* data, rpc_callback_t *callback)
|
|
{
|
|
log_trace("Got block header by height: \n%s", data);
|
|
json_object *root = json_tokener_parse(data);
|
|
JSON_GET_OR_WARN(result, root, json_type_object);
|
|
JSON_GET_OR_WARN(status, result, json_type_string);
|
|
const char *ss = json_object_get_string(status);
|
|
json_object *error = NULL;
|
|
json_object_object_get_ex(root, "error", &error);
|
|
if (error != NULL)
|
|
{
|
|
JSON_GET_OR_WARN(code, error, json_type_object);
|
|
JSON_GET_OR_WARN(message, error, json_type_string);
|
|
int ec = json_object_get_int(code);
|
|
const char *em = json_object_get_string(message);
|
|
log_error("Error (%d) getting block header by height: %s", ec, em);
|
|
json_object_put(root);
|
|
return;
|
|
}
|
|
if (status == NULL || strcmp(ss, "OK") != 0)
|
|
{
|
|
log_error("Error getting block header by height: %s", ss);
|
|
json_object_put(root);
|
|
return;
|
|
}
|
|
block_t rb;
|
|
JSON_GET_OR_WARN(block_header, result, json_type_object);
|
|
response_to_block(block_header, &rb);
|
|
process_blocks(&rb, 1);
|
|
json_object_put(root);
|
|
}
|
|
|
|
static void
|
|
rpc_on_block_template(const char* data, rpc_callback_t *callback)
|
|
{
|
|
log_trace("Got block template: \n%s", data);
|
|
json_object *root = json_tokener_parse(data);
|
|
JSON_GET_OR_WARN(result, root, json_type_object);
|
|
JSON_GET_OR_WARN(status, result, json_type_string);
|
|
const char *ss = json_object_get_string(status);
|
|
json_object *error = NULL;
|
|
json_object_object_get_ex(root, "error", &error);
|
|
if (error != NULL)
|
|
{
|
|
JSON_GET_OR_WARN(code, error, json_type_object);
|
|
JSON_GET_OR_WARN(message, error, json_type_string);
|
|
int ec = json_object_get_int(code);
|
|
const char *em = json_object_get_string(message);
|
|
log_error("Error (%d) getting block template: %s", ec, em);
|
|
json_object_put(root);
|
|
return;
|
|
}
|
|
if (status == NULL || strcmp(ss, "OK") != 0)
|
|
{
|
|
log_error("Error getting block template: %s", ss);
|
|
json_object_put(root);
|
|
return;
|
|
}
|
|
|
|
block_template_t *bt = calloc(1, sizeof(block_template_t));
|
|
response_to_block_template(result, bt);
|
|
block_template_t *front = block_templates[0];
|
|
if (front == NULL)
|
|
{
|
|
block_templates[0] = bt;
|
|
}
|
|
else
|
|
{
|
|
size_t i = BLOCK_TEMPLATES_MAX;
|
|
while (--i)
|
|
{
|
|
if (i == BLOCK_TEMPLATES_MAX - 1 && block_templates[i] != NULL)
|
|
{
|
|
block_template_free(block_templates[i]);
|
|
}
|
|
block_templates[i] = block_templates[i-1];
|
|
}
|
|
block_templates[0] = bt;
|
|
}
|
|
pool_clients_send_job();
|
|
json_object_put(root);
|
|
}
|
|
|
|
static void
|
|
rpc_on_last_block_header(const char* data, rpc_callback_t *callback)
|
|
{
|
|
log_trace("Got last block header: \n%s", data);
|
|
json_object *root = json_tokener_parse(data);
|
|
JSON_GET_OR_WARN(result, root, json_type_object);
|
|
JSON_GET_OR_WARN(status, result, json_type_string);
|
|
const char *ss = json_object_get_string(status);
|
|
json_object *error = NULL;
|
|
json_object_object_get_ex(root, "error", &error);
|
|
if (error != NULL)
|
|
{
|
|
JSON_GET_OR_WARN(code, error, json_type_object);
|
|
JSON_GET_OR_WARN(message, error, json_type_string);
|
|
int ec = json_object_get_int(code);
|
|
const char *em = json_object_get_string(message);
|
|
log_error("Error (%d) getting last block header: %s", ec, em);
|
|
json_object_put(root);
|
|
return;
|
|
}
|
|
if (status == NULL || strcmp(ss, "OK") != 0)
|
|
{
|
|
log_error("Error getting last block header: %s", ss);
|
|
json_object_put(root);
|
|
return;
|
|
}
|
|
block_t *front = last_block_headers[0];
|
|
block_t *block = calloc(1, sizeof(block_t));
|
|
JSON_GET_OR_WARN(block_header, result, json_type_object);
|
|
response_to_block(block_header, block);
|
|
if (front == NULL)
|
|
{
|
|
startup_pauout(block->height);
|
|
}
|
|
bool need_new_template = false;
|
|
if (front != NULL && block->height > front->height)
|
|
{
|
|
size_t i = BLOCK_HEADERS_MAX;
|
|
while (--i)
|
|
{
|
|
if (i == BLOCK_HEADERS_MAX - 1 && last_block_headers[i] != NULL)
|
|
{
|
|
free(last_block_headers[i]);
|
|
}
|
|
last_block_headers[i] = last_block_headers[i-1];
|
|
}
|
|
last_block_headers[0] = block;
|
|
need_new_template = true;
|
|
}
|
|
else if (front == NULL)
|
|
{
|
|
last_block_headers[0] = block;
|
|
need_new_template = true;
|
|
}
|
|
else
|
|
free(block);
|
|
|
|
pool_stats.network_difficulty = last_block_headers[0]->difficulty;
|
|
pool_stats.network_hashrate = last_block_headers[0]->difficulty / BLOCK_TIME;
|
|
pool_stats.network_height = last_block_headers[0]->height;
|
|
update_pool_hr();
|
|
|
|
if (need_new_template)
|
|
{
|
|
log_info("Fetching new block template");
|
|
char body[RPC_BODY_MAX];
|
|
uint64_t reserve = 17;
|
|
rpc_get_request_body(body, "get_block_template", "sssd", "wallet_address", config.pool_wallet, "reserve_size", reserve);
|
|
rpc_callback_t *c1 = calloc(1, sizeof(rpc_callback_t));
|
|
c1->cb = rpc_on_block_template;
|
|
rpc_request(base, body, c1);
|
|
|
|
uint64_t end = block->height - 60;
|
|
uint64_t start = end - BLOCK_HEADERS_RANGE + 1;
|
|
rpc_get_request_body(body, "get_block_headers_range", "sdsd", "start_height", start, "end_height", end);
|
|
rpc_callback_t *c2 = calloc(1, sizeof(rpc_callback_t));
|
|
c2->cb = rpc_on_block_headers_range;
|
|
rpc_request(base, body, c2);
|
|
}
|
|
|
|
json_object_put(root);
|
|
}
|
|
|
|
static void
|
|
rpc_on_block_submitted(const char* data, rpc_callback_t *callback)
|
|
{
|
|
json_object *root = json_tokener_parse(data);
|
|
JSON_GET_OR_WARN(result, root, json_type_object);
|
|
JSON_GET_OR_WARN(status, result, json_type_string);
|
|
const char *ss = json_object_get_string(status);
|
|
json_object *error = NULL;
|
|
json_object_object_get_ex(root, "error", &error);
|
|
/*
|
|
The RPC reports submission as an error even when it's added as
|
|
an alternative block. Thus, still store it. This doesn't matter
|
|
as upon payout, blocks are checked whether they are orphaned or not.
|
|
*/
|
|
if (error != NULL)
|
|
{
|
|
JSON_GET_OR_WARN(code, error, json_type_object);
|
|
JSON_GET_OR_WARN(message, error, json_type_string);
|
|
int ec = json_object_get_int(code);
|
|
const char *em = json_object_get_string(message);
|
|
log_debug("Error (%d) with block submission: %s", ec, em);
|
|
}
|
|
if (status == NULL || strcmp(ss, "OK") != 0)
|
|
{
|
|
log_debug("Error submitting block: %s", ss);
|
|
}
|
|
pool_stats.pool_blocks_found++;
|
|
block_t *b = (block_t*)callback->data;
|
|
pool_stats.last_block_found = b->timestamp;
|
|
log_info("Block submitted at height: %"PRIu64, b->height);
|
|
int rc = store_block(b->height, b);
|
|
if (rc != 0)
|
|
log_warn("Failed to store block: %s", mdb_strerror(rc));
|
|
json_object_put(root);
|
|
}
|
|
|
|
static void
|
|
rpc_on_wallet_transferred(const char* data, rpc_callback_t *callback)
|
|
{
|
|
log_trace("Transfer response: \n%s", data);
|
|
const char* address = callback->data;
|
|
json_object *root = json_tokener_parse(data);
|
|
JSON_GET_OR_WARN(result, root, json_type_object);
|
|
json_object *error = NULL;
|
|
json_object_object_get_ex(root, "error", &error);
|
|
if (error != NULL)
|
|
{
|
|
JSON_GET_OR_WARN(code, error, json_type_object);
|
|
JSON_GET_OR_WARN(message, error, json_type_string);
|
|
int ec = json_object_get_int(code);
|
|
const char *em = json_object_get_string(message);
|
|
log_error("Error (%d) with wallet transfer: %s", ec, em);
|
|
goto cleanup;
|
|
}
|
|
log_info("Payout transfer successfull");
|
|
|
|
int rc;
|
|
char *err;
|
|
MDB_txn *txn;
|
|
MDB_cursor *cursor;
|
|
if ((rc = mdb_txn_begin(env, NULL, 0, &txn)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
goto cleanup;
|
|
}
|
|
if ((rc = mdb_cursor_open(txn, db_balance, &cursor)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
mdb_txn_abort(txn);
|
|
goto cleanup;
|
|
}
|
|
|
|
MDB_cursor_op op = MDB_SET;
|
|
MDB_val key = {ADDRESS_MAX, (void*)address};
|
|
MDB_val val;
|
|
rc = mdb_cursor_get(cursor, &key, &val, op);
|
|
if (rc == MDB_NOTFOUND)
|
|
{
|
|
log_error("Payment made to non-existent address");
|
|
mdb_txn_abort(txn);
|
|
goto cleanup;
|
|
}
|
|
else if (rc != 0 && rc != MDB_NOTFOUND)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
mdb_txn_abort(txn);
|
|
goto cleanup;
|
|
}
|
|
mdb_cursor_del(cursor, 0);
|
|
mdb_txn_commit(txn);
|
|
|
|
/* Now store payment info */
|
|
JSON_GET_OR_WARN(tx_hash, result, json_type_string);
|
|
JSON_GET_OR_WARN(amount, result, json_type_int);
|
|
const char *ths = json_object_get_string(tx_hash);
|
|
uint64_t ai = json_object_get_int64(amount);
|
|
time_t now = time(NULL);
|
|
payment_t payment;
|
|
memcpy(payment.tx_hash, ths, sizeof(payment.tx_hash));
|
|
payment.amount = ai;
|
|
payment.timestamp = now;
|
|
|
|
if ((rc = mdb_txn_begin(env, NULL, 0, &txn)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
goto cleanup;
|
|
}
|
|
if ((rc = mdb_cursor_open(txn, db_payments, &cursor)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("%s", err);
|
|
mdb_txn_abort(txn);
|
|
goto cleanup;
|
|
}
|
|
key.mv_data = (void*)address;
|
|
key.mv_size = ADDRESS_MAX;
|
|
val.mv_data = &payment;
|
|
val.mv_size = sizeof(payment);
|
|
if ((rc = mdb_cursor_put(cursor, &key, &val, MDB_APPENDDUP)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("Error putting payment: %s", err);
|
|
mdb_txn_abort(txn);
|
|
goto cleanup;
|
|
}
|
|
if ((rc = mdb_txn_commit(txn)) != 0)
|
|
{
|
|
err = mdb_strerror(rc);
|
|
log_error("Error committing payment: %s", err);
|
|
mdb_txn_abort(txn);
|
|
goto cleanup;
|
|
}
|
|
cleanup:
|
|
json_object_put(root);
|
|
}
|
|
|
|
static void
|
|
timer_on_120s(int fd, short kind, void *ctx)
|
|
{
|
|
log_info("Fetching last block header");
|
|
char body[RPC_BODY_MAX];
|
|
rpc_get_request_body(body, "get_last_block_header", NULL);
|
|
rpc_callback_t *callback = calloc(1, sizeof(rpc_callback_t));
|
|
callback->cb = rpc_on_last_block_header;
|
|
rpc_request(base, body, callback);
|
|
struct timeval timeout = { .tv_sec = 120, .tv_usec = 0 };
|
|
evtimer_add(timer_120s, &timeout);
|
|
}
|
|
|
|
static void
|
|
timer_on_10m(int fd, short kind, void *ctx)
|
|
{
|
|
send_payments();
|
|
struct timeval timeout = { .tv_sec = 600, .tv_usec = 0 };
|
|
evtimer_add(timer_10m, &timeout);
|
|
}
|
|
|
|
static void
|
|
client_add(int fd, struct bufferevent *bev)
|
|
{
|
|
log_info("New client connected");
|
|
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;
|
|
}
|
|
}
|
|
if (resize)
|
|
{
|
|
pthread_mutex_lock(&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;
|
|
pthread_mutex_unlock(&mutex_clients);
|
|
log_debug("Client pool can now hold %zu clients", pool_clients.count);
|
|
}
|
|
memset(c, 0, sizeof(client_t));
|
|
c->fd = fd;
|
|
c->bev = bev;
|
|
c->connected_since = time(NULL);
|
|
pool_stats.connected_miners++;
|
|
}
|
|
|
|
static void
|
|
client_find(struct bufferevent *bev, client_t **client)
|
|
{
|
|
int fd = bufferevent_getfd(bev);
|
|
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;
|
|
}
|
|
|
|
static void
|
|
client_clear(struct bufferevent *bev)
|
|
{
|
|
client_t *client;
|
|
client_find(bev, &client);
|
|
if (client == NULL)
|
|
return;
|
|
client_clear_jobs(client);
|
|
memset(client, 0, sizeof(client_t));
|
|
bufferevent_free(bev);
|
|
pool_stats.connected_miners--;
|
|
}
|
|
|
|
static void
|
|
client_send_job(client_t *client, bool response)
|
|
{
|
|
/* First cycle jobs */
|
|
job_t *last = &client->active_jobs[CLIENT_JOBS_MAX-1];
|
|
if (last->blob != NULL)
|
|
{
|
|
free(last->blob);
|
|
last->blob = NULL;
|
|
}
|
|
if (last->submissions != NULL)
|
|
{
|
|
free(last->submissions);
|
|
last->submissions = NULL;
|
|
last->submissions_count = 0;
|
|
}
|
|
for (size_t i=CLIENT_JOBS_MAX-1; i>0; i--)
|
|
{
|
|
job_t *current = &client->active_jobs[i];
|
|
job_t *prev = &client->active_jobs[i-1];
|
|
memcpy(current, prev, sizeof(job_t));
|
|
}
|
|
job_t *job = &client->active_jobs[0];
|
|
memset(job, 0, sizeof(job_t));
|
|
|
|
/* Quick check we actually have a block template */
|
|
block_template_t *bt = block_templates[0];
|
|
if (!bt)
|
|
{
|
|
log_warn("Cannot send client a job as have not yet recieved a block template");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
1. Convert blocktemplate_blob to binary
|
|
2. Update bytes in reserved space at reserved_offset
|
|
3. Get block hashing blob for job
|
|
4. Send
|
|
*/
|
|
|
|
/* Convert template to blob */
|
|
size_t bin_size = strlen(bt->blocktemplate_blob) >> 1;
|
|
char *block = calloc(bin_size, sizeof(char));
|
|
hex_to_bin(bt->blocktemplate_blob, block, bin_size);
|
|
|
|
/* Set the extra nonce in our reserved space */
|
|
char *p = block;
|
|
p += bt->reserved_offset;
|
|
++extra_nonce;
|
|
memcpy(p, &extra_nonce, sizeof(extra_nonce));
|
|
job->extra_nonce = extra_nonce;
|
|
|
|
/* Get hashong blob */
|
|
size_t hashing_blob_size;
|
|
char *hashing_blob = NULL;
|
|
get_hashing_blob(block, bin_size, &hashing_blob, &hashing_blob_size);
|
|
|
|
/* Make hex */
|
|
job->blob = calloc((hashing_blob_size << 1) +1, sizeof(char));
|
|
bin_to_hex(hashing_blob, hashing_blob_size, job->blob);
|
|
log_trace("Miner hashing blob: %s", job->blob);
|
|
|
|
/* Save a job id */
|
|
uuid_generate(job->id);
|
|
|
|
/* Hold reference to block template */
|
|
job->block_template = bt;
|
|
|
|
/* Send */
|
|
char job_id[33];
|
|
bin_to_hex((const char*)job->id, sizeof(uuid_t), job_id);
|
|
|
|
/* Retarget */
|
|
double duration = difftime(time(NULL), client->connected_since);
|
|
uint8_t retarget_time = client->is_proxy ? 5 : 120;
|
|
uint64_t target = fmax((double)client->hashes / duration * retarget_time, config.pool_start_diff);
|
|
job->target = target;
|
|
log_debug("Client %.32s target now %"PRIu64, client->client_id, target);
|
|
|
|
char body[JOB_BODY_MAX];
|
|
if (!client->is_proxy)
|
|
{
|
|
stratum_get_job_body(body, client, response);
|
|
}
|
|
else
|
|
{
|
|
char *block_hex = calloc(bin_size+1, sizeof(char));
|
|
bin_to_hex(block, bin_size, block_hex);
|
|
stratum_get_proxy_job_body(body, client, block_hex, response);
|
|
free(block_hex);
|
|
}
|
|
log_trace("Client job: %s", body);
|
|
struct evbuffer *output = bufferevent_get_output(client->bev);
|
|
evbuffer_add(output, body, strlen(body));
|
|
free(block);
|
|
free(hashing_blob);
|
|
}
|
|
|
|
static void
|
|
client_clear_jobs(client_t *client)
|
|
{
|
|
for (size_t i=0; i<CLIENT_JOBS_MAX; i++)
|
|
{
|
|
job_t *job = &client->active_jobs[i];
|
|
if (job->blob != NULL)
|
|
{
|
|
free(job->blob);
|
|
job->blob = NULL;
|
|
}
|
|
if (job->submissions != NULL)
|
|
{
|
|
free(job->submissions);
|
|
job->submissions = NULL;
|
|
job->submissions_count = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
send_validation_error(const client_t *client, const char *message)
|
|
{
|
|
struct evbuffer *output = bufferevent_get_output(client->bev);
|
|
char body[ERROR_BODY_MAX];
|
|
stratum_get_error_body(body, client->json_id, message);
|
|
evbuffer_add(output, body, strlen(body));
|
|
log_debug("Validation error: %s", message);
|
|
}
|
|
|
|
static void
|
|
client_on_login(json_object *message, client_t *client)
|
|
{
|
|
JSON_GET_OR_ERROR(params, message, json_type_object, client);
|
|
JSON_GET_OR_ERROR(login, params, json_type_string, client);
|
|
JSON_GET_OR_ERROR(pass, params, json_type_string, client);
|
|
|
|
const char *address = json_object_get_string(login);
|
|
uint64_t prefix;
|
|
parse_address(address, &prefix);
|
|
if (prefix != MAINNET_ADDRESS_PREFIX && prefix != TESTNET_ADDRESS_PREFIX)
|
|
return send_validation_error(client, "login only main wallet addresses are supported");
|
|
|
|
const char *worker_id = json_object_get_string(pass);
|
|
|
|
json_object *agent = NULL;
|
|
if (json_object_object_get_ex(params, "agent", &agent))
|
|
{
|
|
const char *user_agent = json_object_get_string(agent);
|
|
if (user_agent)
|
|
{
|
|
strncpy(client->agent, user_agent, 255);
|
|
client->is_proxy = strstr(user_agent, "proxy") != NULL ? true : false;
|
|
}
|
|
}
|
|
|
|
strncpy(client->address, address, sizeof(client->address));
|
|
strncpy(client->worker_id, worker_id, sizeof(client->worker_id));
|
|
uuid_t cid;
|
|
uuid_generate(cid);
|
|
bin_to_hex((const char*)cid, sizeof(uuid_t), client->client_id);
|
|
char status[256];
|
|
snprintf(status, 256, "Logged in: %s %s\n", worker_id, address);
|
|
client_send_job(client, true);
|
|
}
|
|
|
|
static void
|
|
client_on_submit(json_object *message, client_t *client)
|
|
{
|
|
struct evbuffer *output = bufferevent_get_output(client->bev);
|
|
|
|
JSON_GET_OR_ERROR(params, message, json_type_object, client);
|
|
JSON_GET_OR_ERROR(nonce, params, json_type_string, client);
|
|
JSON_GET_OR_ERROR(result, params, json_type_string, client);
|
|
JSON_GET_OR_ERROR(job_id, params, json_type_string, client);
|
|
|
|
char *endptr = NULL;
|
|
const char *nptr = json_object_get_string(nonce);
|
|
errno = 0;
|
|
unsigned long int uli = strtoul(nptr, &endptr, 16);
|
|
if (errno != 0 || nptr == endptr)
|
|
return send_validation_error(client, "nonce not an unsigned long int");
|
|
const uint32_t result_nonce = ntohl(uli);
|
|
|
|
const char *result_hex = json_object_get_string(result);
|
|
if (strlen(result_hex) != 64)
|
|
return send_validation_error(client, "result invalid length");
|
|
if (is_hex_string(result_hex) != 0)
|
|
return send_validation_error(client, "result not hex string");
|
|
|
|
const char *jid = json_object_get_string(job_id);
|
|
if (strlen(jid) != 32)
|
|
return send_validation_error(client, "job_id invalid length");
|
|
|
|
job_t *job = client_find_job(client, jid);
|
|
if (!job)
|
|
return send_validation_error(client, "cannot find job with job_id");
|
|
|
|
log_trace("Client submitted nonce=%u, result=%s", result_nonce, result_hex);
|
|
/*
|
|
1. Validate submission
|
|
active_job->blocktemplate_blob to bin
|
|
add extra_nonce at reserved offset
|
|
add nonce
|
|
get hashing blob
|
|
hash
|
|
compare result
|
|
check result hash against block difficulty (if ge then mined block)
|
|
check result hash against target difficulty (if not ge, invalid share)
|
|
2. Process share
|
|
check result hash against template difficulty (submit to network if good)
|
|
add share to db
|
|
|
|
Note reserved space is: extra_nonce, instance_id, pool_nonce, worker_nonce
|
|
4 bytes each. instance_id would be used for pool threads.
|
|
*/
|
|
|
|
/* Convert template to blob */
|
|
block_template_t *bt = job->block_template;
|
|
char *btb = bt->blocktemplate_blob;
|
|
size_t bin_size = strlen(btb) >> 1;
|
|
char *block = calloc(bin_size, sizeof(char));
|
|
hex_to_bin(bt->blocktemplate_blob, block, bin_size);
|
|
|
|
/* Set the extra nonce in our reserved space */
|
|
char *p = block;
|
|
p += bt->reserved_offset;
|
|
memcpy(p, &job->extra_nonce, sizeof(extra_nonce));
|
|
|
|
uint32_t pool_nonce = 0;
|
|
uint32_t worker_nonce = 0;
|
|
if (client->is_proxy)
|
|
{
|
|
/*
|
|
A proxy supplies pool_nonce and worker_nonce
|
|
so add them in the resrved space too.
|
|
*/
|
|
JSON_GET_OR_WARN(poolNonce, params, json_type_int);
|
|
JSON_GET_OR_WARN(workerNonce, params, json_type_int);
|
|
pool_nonce = json_object_get_int(poolNonce);
|
|
worker_nonce = json_object_get_int(workerNonce);
|
|
p += 8;
|
|
memcpy(p, &pool_nonce, sizeof(pool_nonce));
|
|
p += 4;
|
|
memcpy(p, &worker_nonce, sizeof(worker_nonce));
|
|
}
|
|
uint128_t sub = 0;
|
|
uint32_t *psub = (uint32_t*) ⊂
|
|
*psub++ = result_nonce;
|
|
*psub++ = job->extra_nonce;
|
|
*psub++ = pool_nonce;
|
|
*psub++ = worker_nonce;
|
|
|
|
psub -= 4;
|
|
log_trace("Submission reserved values: %u %u %u %u", *psub, *(psub+1), *(psub+2), *(psub+3));
|
|
|
|
/* Check not already submitted */
|
|
uint128_t *submissions = job->submissions;
|
|
for (size_t i=0; i<job->submissions_count; i++)
|
|
{
|
|
if (submissions[i] == sub)
|
|
{
|
|
char body[ERROR_BODY_MAX];
|
|
stratum_get_error_body(body, client->json_id, "Duplicate share");
|
|
evbuffer_add(output, body, strlen(body));
|
|
log_debug("Duplicate share");
|
|
free(block);
|
|
return;
|
|
}
|
|
}
|
|
job->submissions = realloc((void*)submissions,
|
|
sizeof(uint128_t) * ++job->submissions_count);
|
|
job->submissions[job->submissions_count-1] = sub;
|
|
|
|
/* And the supplied nonce */
|
|
p = block;
|
|
p += 39;
|
|
memcpy(p, &result_nonce, sizeof(result_nonce));
|
|
|
|
/* Get hashong blob */
|
|
size_t hashing_blob_size;
|
|
char *hashing_blob = NULL;
|
|
if (get_hashing_blob(block, bin_size, &hashing_blob, &hashing_blob_size) != 0)
|
|
{
|
|
char body[ERROR_BODY_MAX];
|
|
stratum_get_error_body(body, client->json_id, "Invalid block");
|
|
evbuffer_add(output, body, strlen(body));
|
|
log_debug("Invalid block");
|
|
free(block);
|
|
return;
|
|
}
|
|
|
|
/* Hash and compare */
|
|
char result_hash[32];
|
|
char submitted_hash[32];
|
|
uint8_t major_version = (uint8_t)block[0];
|
|
const int cn_variant = major_version >= 7 ? major_version - 6 : 0;
|
|
get_hash(hashing_blob, hashing_blob_size, (char**)&result_hash, cn_variant, bt->height);
|
|
hex_to_bin(result_hex, submitted_hash, 32);
|
|
|
|
if (memcmp(submitted_hash, result_hash, 32) != 0)
|
|
{
|
|
char body[ERROR_BODY_MAX];
|
|
stratum_get_error_body(body, client->json_id, "Invalid share");
|
|
evbuffer_add(output, body, strlen(body));
|
|
log_debug("Invalid share");
|
|
/* TODO: record and ban if too many */
|
|
free(block);
|
|
free(hashing_blob);
|
|
return;
|
|
}
|
|
|
|
BIGNUM *hd = BN_new();
|
|
BIGNUM *jd = BN_new();
|
|
BIGNUM *bd = BN_new();
|
|
BIGNUM *rh = NULL;
|
|
BN_set_word(jd, job->target);
|
|
BN_set_word(bd, bt->difficulty);
|
|
reverse_bin(result_hash, 32);
|
|
rh = BN_bin2bn((const unsigned char*)result_hash, 32, NULL);
|
|
BN_div(hd, NULL, base_diff, rh, bn_ctx);
|
|
BN_free(rh);
|
|
|
|
/* Process share */
|
|
client->hashes += job->target;
|
|
time_t now = time(NULL);
|
|
log_trace("Checking hash against blobk difficulty: %lu, job difficulty: %lu",
|
|
BN_get_word(bd), BN_get_word(jd));
|
|
bool can_store = false;
|
|
|
|
if (BN_cmp(hd, bd) >= 0)
|
|
{
|
|
can_store = true;
|
|
/* Yay! Mined a block so submit to network */
|
|
log_info("+++ MINED A BLOCK +++");
|
|
char *block_hex = calloc((bin_size << 1)+1, sizeof(char));
|
|
bin_to_hex(block, bin_size, block_hex);
|
|
char body[RPC_BODY_MAX];
|
|
snprintf(body, RPC_BODY_MAX,
|
|
"{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"submit_block\", \"params\":[\"%s\"]}",
|
|
block_hex);
|
|
|
|
rpc_callback_t *callback = calloc(1, sizeof(rpc_callback_t));
|
|
callback->cb = rpc_on_block_submitted;
|
|
callback->data = calloc(1, sizeof(block_t));
|
|
|
|
block_t* b = (block_t*)callback->data;
|
|
b->height = bt->height;
|
|
bin_to_hex(submitted_hash, 32, b->hash);
|
|
memcpy(b->prev_hash, bt->prev_hash, 64);
|
|
b->difficulty = bt->difficulty;
|
|
b->status = BLOCK_LOCKED;
|
|
b->timestamp = now;
|
|
|
|
rpc_request(base, body, callback);
|
|
free(block_hex);
|
|
}
|
|
else if (BN_cmp(hd, jd) < 0)
|
|
{
|
|
can_store = false;
|
|
char body[ERROR_BODY_MAX];
|
|
stratum_get_error_body(body, client->json_id, "Low difficulty share");
|
|
log_debug("Low difficulty (%lu) share", BN_get_word(jd));
|
|
evbuffer_add(output, body, strlen(body));
|
|
}
|
|
else
|
|
can_store = true;
|
|
|
|
BN_free(hd);
|
|
BN_free(jd);
|
|
BN_free(bd);
|
|
free(block);
|
|
free(hashing_blob);
|
|
|
|
if (can_store)
|
|
{
|
|
share_t share;
|
|
share.height = bt->height;
|
|
share.difficulty = job->target;
|
|
strncpy(share.address, client->address, sizeof(share.address));
|
|
share.timestamp = now;
|
|
log_debug("Storing share with difficulty: %"PRIu64, share.difficulty);
|
|
int rc = store_share(share.height, &share);
|
|
if (rc != 0)
|
|
log_warn("Failed to store share: %s", mdb_strerror(rc));
|
|
char body[STATUS_BODY_MAX];
|
|
stratum_get_status_body(body, client->json_id, "OK");
|
|
evbuffer_add(output, body, strlen(body));
|
|
}
|
|
}
|
|
|
|
static void
|
|
client_on_read(struct bufferevent *bev, void *ctx)
|
|
{
|
|
struct evbuffer *input, *output;
|
|
char *line;
|
|
size_t n;
|
|
client_t *client;
|
|
|
|
client_find(bev, &client);
|
|
if (client == NULL)
|
|
return;
|
|
|
|
input = bufferevent_get_input(bev);
|
|
output = bufferevent_get_output(bev);
|
|
|
|
size_t len = evbuffer_get_length(input);
|
|
if (len >= MAX_LINE)
|
|
{
|
|
const char *too_long = "Message too long\n";
|
|
evbuffer_add(output, too_long, strlen(too_long));
|
|
log_info("Removing client. Message too long.");
|
|
evbuffer_drain(input, len);
|
|
client_clear(bev);
|
|
return;
|
|
}
|
|
|
|
while ((line = evbuffer_readln(input, &n, EVBUFFER_EOL_LF)))
|
|
{
|
|
json_object *message = json_tokener_parse(line);
|
|
JSON_GET_OR_WARN(method, message, json_type_string);
|
|
JSON_GET_OR_WARN(id, message, json_type_int);
|
|
const char *method_name = json_object_get_string(method);
|
|
const char unknown[] = "Unknown method";
|
|
client->json_id = json_object_get_int(id);
|
|
|
|
bool unknown_message = false;
|
|
|
|
if (method_name == NULL)
|
|
{
|
|
char body[ERROR_BODY_MAX];
|
|
stratum_get_error_body(body, client->json_id, unknown);
|
|
evbuffer_add(output, body, strlen(body));
|
|
unknown_message = true;
|
|
}
|
|
else if (strcmp(method_name, "login") == 0)
|
|
{
|
|
client_on_login(message, client);
|
|
}
|
|
else if (strcmp(method_name, "submit") == 0)
|
|
{
|
|
client_on_submit(message, client);
|
|
}
|
|
else if (strcmp(method_name, "getjob") == 0)
|
|
{
|
|
client_send_job(client, false);
|
|
}
|
|
else if (strcmp(method_name, "keepalived") == 0)
|
|
{
|
|
char body[STATUS_BODY_MAX];
|
|
stratum_get_status_body(body, client->json_id, "KEEPALIVED");
|
|
evbuffer_add(output, body, strlen(body));
|
|
}
|
|
else
|
|
{
|
|
char body[ERROR_BODY_MAX];
|
|
stratum_get_error_body(body, client->json_id, unknown);
|
|
evbuffer_add(output, body, strlen(body));
|
|
unknown_message = true;
|
|
}
|
|
|
|
json_object_put(message);
|
|
free(line);
|
|
|
|
if (unknown_message)
|
|
{
|
|
char *body = stratum_new_error_body(client->json_id, unknown);
|
|
evbuffer_add(output, body, strlen(body));
|
|
free(body);
|
|
log_info("Removing client. Unknown message.");
|
|
evbuffer_drain(input, len);
|
|
client_clear(bev);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
client_on_error(struct bufferevent *bev, short error, void *ctx)
|
|
{
|
|
if (error & BEV_EVENT_EOF)
|
|
{
|
|
/* connection has been closed */
|
|
log_debug("Client disconnected. Removing.");
|
|
}
|
|
else if (error & BEV_EVENT_ERROR)
|
|
{
|
|
/* check errno to see what error occurred */
|
|
log_debug("Client error: %d. Removing.", errno);
|
|
}
|
|
else if (error & BEV_EVENT_TIMEOUT)
|
|
{
|
|
/* must be a timeout event handle, handle it */
|
|
log_debug("Client timeout. Removing.");
|
|
}
|
|
client_clear(bev);
|
|
}
|
|
|
|
static void
|
|
client_on_accept(evutil_socket_t listener, short event, void *arg)
|
|
{
|
|
struct event_base *base = (struct event_base*)arg;
|
|
struct sockaddr_storage ss;
|
|
socklen_t slen = sizeof(ss);
|
|
int fd = accept(listener, (struct sockaddr*)&ss, &slen);
|
|
if (fd < 0)
|
|
{
|
|
perror("accept");
|
|
}
|
|
else if (fd > FD_SETSIZE)
|
|
{
|
|
close(fd);
|
|
}
|
|
else
|
|
{
|
|
struct bufferevent *bev;
|
|
evutil_make_socket_nonblocking(fd);
|
|
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
|
|
bufferevent_setcb(bev, client_on_read, NULL, client_on_error, NULL);
|
|
bufferevent_setwatermark(bev, EV_READ, 0, MAX_LINE);
|
|
bufferevent_enable(bev, EV_READ|EV_WRITE);
|
|
client_add(fd, bev);
|
|
}
|
|
}
|
|
|
|
static void
|
|
read_config(const char *config_file, const char *log_file)
|
|
{
|
|
/* Start with some defaults for any missing... */
|
|
strncpy(config.rpc_host, "127.0.0.1", 10);
|
|
config.rpc_port = 18081;
|
|
config.rpc_timeout = 15;
|
|
config.pool_start_diff = 100;
|
|
config.share_mul = 2.0;
|
|
config.pool_fee = 0.01;
|
|
config.payment_threshold = 0.33;
|
|
config.pool_port = 4242;
|
|
config.log_level = 5;
|
|
config.webui_port = 4243;
|
|
|
|
char path[MAX_PATH];
|
|
if (config_file)
|
|
{
|
|
strncpy(path, config_file, MAX_PATH);
|
|
}
|
|
else
|
|
{
|
|
if (!getcwd(path, MAX_PATH))
|
|
{
|
|
log_fatal("Cannot getcwd (%s). Aborting.", errno);
|
|
abort();
|
|
}
|
|
strcat(path, "/pool.conf");
|
|
if (access(path, R_OK) != 0)
|
|
{
|
|
strncpy(path, getenv("HOME"), MAX_PATH);
|
|
strcat(path, "/pool.conf");
|
|
if (access(path, R_OK) != 0)
|
|
{
|
|
log_fatal("Cannot find a config file in ./ or ~/ and no option supplied. Aborting.");
|
|
abort();
|
|
}
|
|
}
|
|
}
|
|
log_info("Reading config at: %s", path);
|
|
|
|
FILE *fp = fopen(path, "r");
|
|
if (fp == NULL)
|
|
{
|
|
log_fatal("Cannot open config file. Aborting.");
|
|
abort();
|
|
}
|
|
char line[256];
|
|
char *key;
|
|
char *val;
|
|
const char *tok = " =";
|
|
while (fgets(line, sizeof(line), fp) != NULL)
|
|
{
|
|
key = strtok(line, tok);
|
|
if (key == NULL)
|
|
continue;
|
|
val = strtok(NULL, tok);
|
|
if (val == NULL)
|
|
continue;
|
|
val[strcspn(val, "\r\n")] = 0;
|
|
if (strcmp(key, "rpc-host") == 0)
|
|
{
|
|
strncpy(config.rpc_host, val, sizeof(config.rpc_host));
|
|
}
|
|
else if (strcmp(key, "rpc-port") == 0)
|
|
{
|
|
config.rpc_port = atoi(val);
|
|
}
|
|
else if (strcmp(key, "rpc-timeout") == 0)
|
|
{
|
|
config.rpc_timeout = atoi(val);
|
|
}
|
|
else if (strcmp(key, "wallet-rpc-host") == 0)
|
|
{
|
|
strncpy(config.wallet_rpc_host, val, sizeof(config.rpc_host));
|
|
}
|
|
else if (strcmp(key, "wallet-rpc-port") == 0)
|
|
{
|
|
config.wallet_rpc_port = atoi(val);
|
|
}
|
|
else if (strcmp(key, "pool-wallet") == 0)
|
|
{
|
|
strncpy(config.pool_wallet, val, sizeof(config.pool_wallet));
|
|
}
|
|
else if (strcmp(key, "pool-start-diff") == 0)
|
|
{
|
|
config.pool_start_diff = strtoumax(val, NULL, 10);
|
|
}
|
|
else if (strcmp(key, "share-mul") == 0)
|
|
{
|
|
config.share_mul = atof(val);
|
|
}
|
|
else if (strcmp(key, "pool-fee") == 0)
|
|
{
|
|
config.pool_fee = atof(val);
|
|
}
|
|
else if (strcmp(key, "payment-threshold") == 0)
|
|
{
|
|
config.payment_threshold = atof(val);
|
|
}
|
|
else if (strcmp(key, "pool-port") == 0)
|
|
{
|
|
config.pool_port = atoi(val);
|
|
}
|
|
else if (strcmp(key, "log-level") == 0)
|
|
{
|
|
config.log_level = atoi(val);
|
|
}
|
|
else if (strcmp(key, "webui-port") == 0)
|
|
{
|
|
config.webui_port = atoi(val);
|
|
}
|
|
else if (strcmp(key, "log-file") == 0)
|
|
{
|
|
strncpy(config.log_file, val, sizeof(config.log_file));
|
|
}
|
|
}
|
|
fclose(fp);
|
|
|
|
if (log_file != NULL)
|
|
strncpy(config.log_file, log_file, sizeof(config.log_file));
|
|
|
|
if (!config.pool_wallet[0])
|
|
{
|
|
log_fatal("No pool wallet supplied. Aborting.");
|
|
abort();
|
|
}
|
|
if (!config.wallet_rpc_host[0] || config.wallet_rpc_port == 0)
|
|
{
|
|
log_fatal("Both wallet-rpc-host and wallet-rpc-port need setting. Aborting.");
|
|
abort();
|
|
}
|
|
log_info("\nCONFIG:\n rpc_host = %s\n rpc_port = %u\n rpc_timeout = %u\n pool_wallet = %s\n "
|
|
"pool_start_diff = %"PRIu64"\n share_mul = %.2f\n pool_fee = %.2f\n payment_threshold = %.2f\n "
|
|
"wallet_rpc_host = %s\n wallet_rpc_port = %u\n pool_port = %u\n "
|
|
"log_level = %u\n webui_port=%u\n log-file = %s\n",
|
|
config.rpc_host, config.rpc_port, config.rpc_timeout,
|
|
config.pool_wallet, config.pool_start_diff, config.share_mul,
|
|
config.pool_fee, config.payment_threshold,
|
|
config.wallet_rpc_host, config.wallet_rpc_port, config.pool_port,
|
|
config.log_level, config.webui_port, config.log_file);
|
|
}
|
|
|
|
static void
|
|
run(void)
|
|
{
|
|
evutil_socket_t listener;
|
|
struct sockaddr_in sin;
|
|
struct event *listener_event;
|
|
|
|
base = event_base_new();
|
|
if (!base)
|
|
{
|
|
log_fatal("Failed to create event base");
|
|
return;
|
|
}
|
|
|
|
sin.sin_family = AF_INET;
|
|
sin.sin_addr.s_addr = 0;
|
|
sin.sin_port = htons(config.pool_port);
|
|
|
|
listener = socket(AF_INET, SOCK_STREAM, 0);
|
|
evutil_make_socket_nonblocking(listener);
|
|
|
|
#ifndef WIN32
|
|
{
|
|
int one = 1;
|
|
setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
|
|
}
|
|
#endif
|
|
|
|
if (bind(listener, (struct sockaddr*)&sin, sizeof(sin)) < 0)
|
|
{
|
|
perror("bind");
|
|
return;
|
|
}
|
|
|
|
if (listen(listener, 16)<0)
|
|
{
|
|
perror("listen");
|
|
return;
|
|
}
|
|
|
|
listener_event = event_new(base, listener, EV_READ|EV_PERSIST, client_on_accept, (void*)base);
|
|
if (event_add(listener_event, NULL) != 0)
|
|
{
|
|
log_fatal("Failed to add socket listener event");
|
|
return;
|
|
}
|
|
|
|
timer_120s = evtimer_new(base, timer_on_120s, NULL);
|
|
timer_on_120s(-1, EV_TIMEOUT, NULL);
|
|
|
|
timer_10m = evtimer_new(base, timer_on_10m, NULL);
|
|
timer_on_10m(-1, EV_TIMEOUT, NULL);
|
|
|
|
event_base_dispatch(base);
|
|
}
|
|
|
|
static void
|
|
cleanup()
|
|
{
|
|
printf("\n");
|
|
log_info("Performing cleanup");
|
|
stop_web_ui();
|
|
evtimer_del(timer_120s);
|
|
event_free(timer_120s);
|
|
evtimer_del(timer_10m);
|
|
event_free(timer_10m);
|
|
event_base_free(base);
|
|
pool_clients_free();
|
|
last_block_headers_free();
|
|
block_templates_free();
|
|
database_close();
|
|
BN_free(base_diff);
|
|
BN_CTX_free(bn_ctx);
|
|
pthread_mutex_destroy(&mutex_clients);
|
|
log_info("Pool shutdown successfully");
|
|
if (fd_log != NULL)
|
|
fclose(fd_log);
|
|
}
|
|
|
|
static void
|
|
sigint_handler(int sig)
|
|
{
|
|
signal(SIGINT, SIG_DFL);
|
|
exit(0);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
setvbuf(stdout, NULL, _IONBF, 0);
|
|
signal(SIGINT, sigint_handler);
|
|
atexit(cleanup);
|
|
|
|
log_set_level(LOG_INFO);
|
|
log_info("Starting pool");
|
|
|
|
static struct option options[] =
|
|
{
|
|
{"config-file", required_argument, 0, 'c'},
|
|
{"log-file", required_argument, 0, 'l'},
|
|
{0, 0, 0, 0}
|
|
};
|
|
char *config_file = NULL;
|
|
char *log_file = NULL;
|
|
int c;
|
|
while (1)
|
|
{
|
|
int option_index = 0;
|
|
c = getopt_long (argc, argv, "c:l:",
|
|
options, &option_index);
|
|
if (c == -1)
|
|
break;
|
|
switch (c)
|
|
{
|
|
case 'c':
|
|
config_file = strdup(optarg);
|
|
break;
|
|
case 'l':
|
|
log_file = strdup(optarg);
|
|
break;
|
|
}
|
|
}
|
|
read_config(config_file, log_file);
|
|
|
|
log_set_level(LOG_FATAL - config.log_level);
|
|
if (config.log_file[0] != '\0')
|
|
{
|
|
fd_log = fopen(config.log_file, "a");
|
|
if (fd_log == NULL)
|
|
log_info("Failed to open log file: %s", config.log_file);
|
|
else
|
|
log_set_fp(fd_log);
|
|
}
|
|
|
|
if (config_file != NULL)
|
|
free(config_file);
|
|
|
|
if (log_file != NULL)
|
|
free(log_file);
|
|
|
|
int err = 0;
|
|
if ((err = database_init()) != 0)
|
|
{
|
|
log_fatal("Failed to initialize database. Return code: %d", err);
|
|
goto cleanup;
|
|
}
|
|
|
|
bn_ctx = BN_CTX_new();
|
|
base_diff = NULL;
|
|
BN_hex2bn(&base_diff, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
|
|
|
|
pool_clients_init();
|
|
|
|
wui_context_t uic;
|
|
uic.port = config.webui_port;
|
|
uic.pool_stats = &pool_stats;
|
|
uic.pool_fee = config.pool_fee;
|
|
uic.pool_port = config.pool_port;
|
|
uic.payment_threshold = config.payment_threshold;
|
|
start_web_ui(&uic);
|
|
|
|
run();
|
|
|
|
cleanup:
|
|
return 0;
|
|
}
|