oclvanityminer.c
/*
 * Vanitygen, vanity bitcoin address generator
 * Copyright (C) 2011 <samr7@cs.washington.edu>
 *
 * Vanitygen is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version. 
 *
 * Vanitygen is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Vanitygen.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <assert.h>

#include <pthread.h>

#include <openssl/ec.h>
#include <openssl/bn.h>
#include <openssl/rand.h>

#include <curl/curl.h>

#include "oclengine.h"
#include "pattern.h"
#include "util.h"
#include "avl.h"

#include "ticker.h"
char ticker[10];

char workurl[2048];
int workurlFlag = 0;

int GRSFlag = 0;
int TRXFlag = 0;

const char *version = VANITYGEN_VERSION;
const int debug = 0;


/*
 * Bounty work item, which includes a pattern and a public key
 * Indexed in an AVL tree by pattern
 */

typedef struct workitem_s {
	avl_item_t avlent;
	const char *pattern;
	const char *comment;
	EC_POINT *pubkey;
	int addrtype;
	double difficulty;
	double reward;
	double value;
} workitem_t;


static int
workitem_cmp(workitem_t *a, workitem_t *b)
{
	int cmpres;
	cmpres = strcmp(a->pattern, b->pattern);
	if (!cmpres)
		cmpres = (a->reward < b->reward) ? -1 :
			((a->reward > b->reward) ? 1 : 0);
	return cmpres;
}

static workitem_t *
workitem_avl_search(avl_root_t *rootp, const char *pattern)
{
	workitem_t *vp;
	avl_item_t *itemp = rootp->ar_root;

	while (itemp) {
		int cmpres;
		vp = avl_item_entry(itemp, workitem_t, avlent);
		cmpres = strcmp(vp->pattern, pattern);
		if (cmpres > 0) {
			itemp = itemp->ai_left;
		} else {
			if (cmpres < 0) {
				itemp = itemp->ai_right;
			} else
				return vp;
		}
	}
	return NULL;
}

static workitem_t *
workitem_avl_insert(avl_root_t *rootp, workitem_t *vpnew)
{
	workitem_t *vp;
	avl_item_t *itemp = NULL;
	avl_item_t **ptrp = &rootp->ar_root;
	while (*ptrp) {
		int cmpres;
		itemp = *ptrp;
		vp = avl_item_entry(itemp, workitem_t, avlent);
		cmpres = workitem_cmp(vp, vpnew);
		if (cmpres > 0) {
			ptrp = &itemp->ai_left;
		} else {
			if (cmpres < 0) {
				ptrp = &itemp->ai_right;
			} else
				return vp;
		}
	}
	vpnew->avlent.ai_up = itemp;
	itemp = &vpnew->avlent;
	*ptrp = itemp;
	avl_insert_fix(rootp, itemp);
	return NULL;
}

static workitem_t *
workitem_avl_first(avl_root_t *rootp)
{
	avl_item_t *itemp;
	itemp = avl_first(rootp);
	if (itemp)
		return avl_item_entry(itemp, workitem_t, avlent);
	return NULL;
}

static workitem_t *
workitem_avl_next(workitem_t *vp)
{
	avl_item_t *itemp = &vp->avlent;
	itemp = avl_next(itemp);
	if (itemp)
		return avl_item_entry(itemp, workitem_t, avlent);
	return NULL;
}

void
server_workitem_free(workitem_t *wip)
{
	if (wip->pubkey)
		EC_POINT_free(wip->pubkey);
	free(wip);
}


/*
 * pubkeybatch, which includes a set of pattern bounties with the same
 * base public key.  Patterns may be searched for concurrently due to
 * having the same base public key.
 *
 * Indexed in an AVL tree by public key.
 */

typedef struct pubkeybatch_s {
	avl_item_t avlent;
	EC_POINT *pubkey;
	const char *pubkey_hex;
	avl_root_t items;
	int nitems;
	double total_value;
} pubkeybatch_t;

void
server_batch_free(pubkeybatch_t *pbatch)
{
	workitem_t *wip;
	while ((wip = workitem_avl_first(&pbatch->items)) != NULL) {
		if (wip->pubkey == pbatch->pubkey)
			wip->pubkey = NULL;
		avl_remove(&pbatch->items, &wip->avlent);
		server_workitem_free(wip);
	}
	if (pbatch->pubkey)
		EC_POINT_free(pbatch->pubkey);
	if (pbatch->pubkey_hex)
		OPENSSL_free((char*)pbatch->pubkey_hex);
	free(pbatch);
}

static int
pubkeybatch_cmp(pubkeybatch_t *a, pubkeybatch_t *b)
{
	return strcmp(a->pubkey_hex, b->pubkey_hex);
}

static pubkeybatch_t *
pubkeybatch_avl_search(avl_root_t *rootp, const EC_POINT *pubkey,
		       const EC_GROUP *pgroup)
{
	char *pubkey_hex;
	pubkeybatch_t *vp;
	avl_item_t *itemp = rootp->ar_root;
	pubkey_hex = EC_POINT_point2hex(pgroup,
					pubkey,
					POINT_CONVERSION_UNCOMPRESSED,
					NULL);
	while (itemp) {
		int cmpres;
		vp = avl_item_entry(itemp, pubkeybatch_t, avlent);
		cmpres = strcmp(pubkey_hex, vp->pubkey_hex);
		if (cmpres > 0) {
			itemp = itemp->ai_left;
		} else {
			if (cmpres < 0) {
				itemp = itemp->ai_right;
			} else {
				OPENSSL_free(pubkey_hex);
				return vp;
			}
		}
	}
	OPENSSL_free(pubkey_hex);
	return NULL;
}

static pubkeybatch_t *
pubkeybatch_avl_insert(avl_root_t *rootp, pubkeybatch_t *vpnew)
{
	pubkeybatch_t *vp;
	avl_item_t *itemp = NULL;
	avl_item_t **ptrp = &rootp->ar_root;
	while (*ptrp) {
		int cmpres;
		itemp = *ptrp;
		vp = avl_item_entry(itemp, pubkeybatch_t, avlent);
		cmpres = pubkeybatch_cmp(vpnew, vp);
		if (cmpres > 0) {
			ptrp = &itemp->ai_left;
		} else {
			if (cmpres < 0) {
				ptrp = &itemp->ai_right;
			} else
				return vp;
		}
	}
	vpnew->avlent.ai_up = itemp;
	itemp = &vpnew->avlent;
	*ptrp = itemp;
	avl_insert_fix(rootp, itemp);
	return NULL;
}

static pubkeybatch_t *
pubkeybatch_avl_first(avl_root_t *rootp)
{
	avl_item_t *itemp;
	itemp = avl_first(rootp);
	if (itemp)
		return avl_item_entry(itemp, pubkeybatch_t, avlent);
	return NULL;
}

static pubkeybatch_t *
pubkeybatch_avl_next(pubkeybatch_t *vp)
{
	avl_item_t *itemp = &vp->avlent;
	itemp = avl_next(itemp);
	if (itemp)
		return avl_item_entry(itemp, pubkeybatch_t, avlent);
	return NULL;
}


typedef struct server_request_s {
	int request_status;
	const EC_GROUP *group;

	char *part_buf;
	size_t part_off;
	size_t part_end;
	size_t part_size;

	avl_root_t items;
	int nitems;
} server_request_t;


static workitem_t *
server_workitem_new(server_request_t *reqp,
		    const char *pfx, const char *pubkey_s,
		    const char *addrtype_s, const char *reward_s,
		    const char *comment)
{
	workitem_t *wip;
	EC_POINT *pubkey;
	int addrtype;
	double reward;
	double difficulty;

	addrtype = atoi(addrtype_s);
	if ((addrtype < 0) || (addrtype > 255))
		return NULL;

	reward = strtod(reward_s, NULL);
	if (reward < 0.0)
		return NULL;

	difficulty = vg_prefix_get_difficulty(addrtype, pfx);
	if (difficulty == 0.0)
		return NULL;

	pubkey = EC_POINT_hex2point(reqp->group, pubkey_s, NULL, NULL);
	if (pubkey == NULL)
		return NULL;


	wip = (workitem_t *) malloc(sizeof(*wip) +
				    strlen(pfx) +
				    strlen(comment) + 2);
	memset(wip, 0, sizeof(*wip));
	avl_item_init(&wip->avlent);
	wip->pattern = (char *) (wip + 1);
	strcpy((char *)wip->pattern, pfx);
	wip->comment = wip->pattern + (strlen(wip->pattern) + 1);
	strcpy((char *) wip->comment, comment);
	wip->pubkey = pubkey;
	wip->addrtype = addrtype;
	wip->difficulty = difficulty;
	wip->reward = reward;
	wip->value = (reward * 1000000000000.0) / difficulty;

	return wip;
}


typedef struct server_context_s {
	EC_KEY *dummy_key;
	const char *url;
	const char *credit_addr;
	char *getwork;
	char *submit;
	int verbose;
	avl_root_t items;
	int nitems;
} server_context_t;


static int
server_workitem_equal(workitem_t *a, workitem_t *b)
{
	return (a->reward == b->reward) && !strcmp(a->pattern, b->pattern);
}

static int
server_pubkeybatch_equal(server_context_t *ctxp,
			 pubkeybatch_t *a, pubkeybatch_t *b)
{
	workitem_t *wipa, *wipb;

	if (a->nitems != b->nitems)
		return 0;
	if (EC_POINT_cmp(EC_KEY_get0_group(ctxp->dummy_key),
			 a->pubkey, b->pubkey, NULL))
		return 0;

	for (wipa = workitem_avl_first(&a->items),
		     wipb = workitem_avl_first(&b->items);
	     wipa && wipb;
	     wipa = workitem_avl_next(wipa), wipb = workitem_avl_next(wipb)) {
		if (!server_workitem_equal(wipa, wipb))
			return 0;
	}
	return 1;
}

void
server_context_free(server_context_t *ctxp)
{
	if (ctxp->dummy_key)
		EC_KEY_free(ctxp->dummy_key);
	if (ctxp->getwork)
		free(ctxp->getwork);
	if (ctxp->submit)
		free(ctxp->submit);
	free(ctxp);
}

server_context_t *
server_context_new(const char *url, const char *credit_addr)
{
	server_context_t *ctxp;
	int urllen = strlen(url);
	int addrlen = strlen(credit_addr);
	ctxp = (server_context_t *)
		malloc(sizeof(*ctxp) + urllen + addrlen + 2);
	memset(ctxp, 0, sizeof(*ctxp));
	avl_root_init(&ctxp->items);
	ctxp->url = (const char *) (ctxp + 1);
	ctxp->credit_addr = (const char *) (ctxp->url + urllen + 1);
	strcpy((char *) ctxp->url, url);
	strcpy((char *) ctxp->credit_addr, credit_addr);

	ctxp->dummy_key = vg_exec_context_new_key();
	ctxp->getwork = (char *) malloc(urllen + 9);
	ctxp->submit = (char *) malloc(urllen + 7);

	if (workurlFlag == 0)
	{

		if (url[urllen - 1] == '/') {
			snprintf(ctxp->getwork, urllen + 9, "%sgetWork", url);
			snprintf(ctxp->submit, urllen + 7, "%ssolve", url);
		} else {
			snprintf(ctxp->getwork, urllen + 9, "%s/getWork", url);
			snprintf(ctxp->submit, urllen + 7, "%s/solve", url);
		}
	}
	else
	{
		int workurllen = strlen(workurl);
		snprintf(ctxp->getwork, workurllen + 2, "%s", workurl);
		if (url[urllen - 1] == '/') {
			snprintf(ctxp->submit, urllen + 7, "%ssolve", url);
		} else {
			snprintf(ctxp->submit, urllen + 7, "%s/solve", url);
		}
	}

	return ctxp;
}


int
server_workitem_add(server_request_t *reqp, workitem_t *wip)
{
	workitem_t *xwip;
	pubkeybatch_t *pbatch = NULL;

	pbatch = pubkeybatch_avl_search(&reqp->items, wip->pubkey, reqp->group);
	if (pbatch == NULL) {
		pbatch = (pubkeybatch_t *) malloc(sizeof(*pbatch));
		if (pbatch == NULL)
			return -1;
		memset(pbatch, 0, sizeof(*pbatch));
		avl_item_init(&pbatch->avlent);
		avl_root_init(&pbatch->items);
		pbatch->total_value = 0;
		pbatch->pubkey = wip->pubkey;
		pbatch->pubkey_hex = EC_POINT_point2hex(reqp->group, 
					wip->pubkey, 
					POINT_CONVERSION_UNCOMPRESSED, 
					NULL);
		pubkeybatch_avl_insert(&reqp->items, pbatch);
		reqp->nitems++;
	}

	/* Make sure there isn't an overlap */
	xwip = workitem_avl_insert(&pbatch->items, wip);
	if (xwip)
		return -1;

	if (wip->pubkey && wip->pubkey != pbatch->pubkey)
		EC_POINT_free(wip->pubkey);
	wip->pubkey = pbatch->pubkey;
	
	pbatch->nitems++;
	pbatch->total_value += wip->value;
	return 0;
}


static size_t
server_body_reader(const char *buf, size_t elemsize, size_t len, void *param)
{
	server_request_t *reqp = (server_request_t *) param;
	char *line, *sep, *pfx, *pubkey_s, *addrtype_s, *reward_s, *comment;
	workitem_t *wip;

	if (!len)
		return 0;

	if ((reqp->part_size < (reqp->part_end + len + 1)) &&
	    (reqp->part_off > 0)) {
		memmove(reqp->part_buf,
			reqp->part_buf + reqp->part_off,
			reqp->part_end - reqp->part_off);
		reqp->part_end -= reqp->part_off;
		reqp->part_off = 0;
	}

	if (reqp->part_size < (reqp->part_end + len + 1)) {
		if (reqp->part_size == 0)
			reqp->part_size = 4096;
		while (reqp->part_size < (reqp->part_end + len + 1)) {
			reqp->part_size *= 2;
			if (reqp->part_size > (1024*1024)) {
				fprintf(stderr, "Line too long from server");
				reqp->request_status = 0;
				return 0;
			}
		}
		reqp->part_buf = (char *) realloc(reqp->part_buf,
						  reqp->part_size);
		if (!reqp->part_buf) {
			fprintf(stderr, "Out of memory");
			return 0;
		}
	}

	memcpy(reqp->part_buf + reqp->part_end, buf, len);
	reqp->part_end += len;
	reqp->part_buf[reqp->part_end] = '\0';

	line = reqp->part_buf + reqp->part_off;
	while (1) {
		sep = strchr(line, '\n');
		if (!sep)
			break;
		pfx = line;
		*sep = '\0';
		line = sep + 1;
		sep = strchr(pfx, ':');
		if (!sep)
			goto bad_line;
	        *sep = '\0'; sep += 1;
		pubkey_s = sep;
		sep = strchr(sep, ':');
		if (!sep)
			goto bad_line;
	        *sep = '\0'; sep += 1;
		addrtype_s = sep;
		sep = strchr(sep, ':');
		if (!sep)
			goto bad_line;
	        *sep = '\0'; sep += 1;
		reward_s = sep;
		sep = strchr(sep, ';');
		if (!sep)
			goto bad_line;
	        *sep = '\0'; sep += 1;
		comment = sep;

		wip = server_workitem_new(reqp, pfx, pubkey_s, addrtype_s,
					  reward_s, comment);
		if (!wip)
			goto bad_line;
		if (server_workitem_add(reqp, wip)) {
			server_workitem_free(wip);
			goto bad_line;
		}
		continue;

	bad_line:
		;
	}

	reqp->part_off = line - reqp->part_buf;
	if (reqp->part_off == reqp->part_end) {
		reqp->part_off = 0;
		reqp->part_end = 0;
	}

	return len;
}

void
dump_work(server_context_t *scp)
{
avl_root_t *work = &scp->items;
	pubkeybatch_t *pbatch;
	workitem_t *wip;
	printf("Available bounties:\n");

	for (pbatch = pubkeybatch_avl_first(work);
	     pbatch != NULL;
	     pbatch = pubkeybatch_avl_next(pbatch)) {

		for (wip = workitem_avl_first(&pbatch->items);
		     wip != NULL;
		     wip = workitem_avl_next(wip)) {
                     char *pubhex = EC_POINT_point2hex(EC_KEY_get0_group(scp->dummy_key),
                                    wip->pubkey,
                                    POINT_CONVERSION_UNCOMPRESSED,
                                    NULL);

			printf("PubKey: \"%s\" Pattern: \"%s\" Reward: %f "
			       "Value: %f BTC/Mkey\n",
			       pubhex,
			       wip->pattern,
			       wip->reward,
			       wip->value);
		}
		if (pbatch->nitems > 1)
			printf("Batch of %d, total value: %f BTC/Mkey\n",
			       pbatch->nitems, pbatch->total_value);
	}
}

void
free_pkb_tree(avl_root_t *rootp, pubkeybatch_t *save_pkb)
{
	pubkeybatch_t *pkb;
	while ((pkb = pubkeybatch_avl_first(rootp)) != NULL) {
		avl_remove(rootp, &pkb->avlent);
		if (pkb != save_pkb)
			server_batch_free(pkb);
	}
}

void
server_request_free(server_request_t *reqp)
{
	if (reqp->part_buf != NULL)
		free(reqp->part_buf);
	if (!avl_root_empty(&reqp->items))
		free_pkb_tree(&reqp->items, NULL);
	free(reqp);
}

int
server_context_getwork(server_context_t *ctxp)
{
	CURLcode res;
	server_request_t *reqp;
	CURL *creq;

	reqp = (server_request_t *) malloc(sizeof(*reqp));
	memset(reqp, 0, sizeof(*reqp));

	reqp->group = EC_KEY_get0_group(ctxp->dummy_key);

	creq = curl_easy_init();
	if (curl_easy_setopt(creq, CURLOPT_URL, ctxp->getwork) ||
	    curl_easy_setopt(creq, CURLOPT_VERBOSE, ctxp->verbose > 1) ||
	    curl_easy_setopt(creq, CURLOPT_WRITEFUNCTION,
			     server_body_reader) ||
	    curl_easy_setopt(creq, CURLOPT_FOLLOWLOCATION, 1) ||
	    curl_easy_setopt(creq, CURLOPT_WRITEDATA, reqp)) {
		fprintf(stderr, "Failed to set up libcurl\n");
		exit(1);
	}

	res = curl_easy_perform(creq);
	curl_easy_cleanup(creq);

	if (res != CURLE_OK) {
		fprintf(stderr, "Get work request failed: %s\n",
			curl_easy_strerror(res));
		server_request_free(reqp);
		return -1;
	}

	ctxp->items.ar_root = reqp->items.ar_root;
	return 0;
}


int
server_context_submit_solution(server_context_t *ctxp,
			       workitem_t *work,
			       const char *privkey)
{
	char urlbuf[8192];
	char *pubhex;
	CURL *creq;
	CURLcode res;

	pubhex = EC_POINT_point2hex(EC_KEY_get0_group(ctxp->dummy_key),
				    work->pubkey,
				    POINT_CONVERSION_UNCOMPRESSED,
				    NULL);
	snprintf(urlbuf, sizeof(urlbuf),
		 "%s?key=%s%%3A%s&privateKey=%s&bitcoinAddress=%s",
		 ctxp->submit,
		 work->pattern,
		 pubhex,
		 privkey,
		 ctxp->credit_addr);
	OPENSSL_free(pubhex);
	creq = curl_easy_init();
	if (curl_easy_setopt(creq, CURLOPT_URL, urlbuf) ||
	    curl_easy_setopt(creq, CURLOPT_VERBOSE, ctxp->verbose > 1) ||
	    curl_easy_setopt(creq, CURLOPT_FOLLOWLOCATION, 1)) {
		fprintf(stderr, "Failed to set up libcurl\n");
		exit(1);
	}

	res = curl_easy_perform(creq);
	if (res != CURLE_OK) {
		fprintf(stderr, "Submission failed: %s\n",
			curl_easy_strerror(res));
		curl_easy_cleanup(creq);
		return -1;
	}

	curl_easy_cleanup(creq);
	return 0;
}

static pthread_mutex_t soln_lock;
static pthread_cond_t soln_cond;
static char *soln_pattern = NULL;
static char *soln_private_key = NULL;

void
free_soln()
{
	if (soln_pattern) {
		free(soln_pattern);
		soln_pattern = NULL;
	}
	if (soln_private_key) {
		OPENSSL_free(soln_private_key);
		soln_private_key = NULL;
	}
}

void
output_match_work_complete(vg_context_t *vcp, EC_KEY *pkey, const char *pattern)
{
	vg_output_match_console(vcp, pkey, pattern);
	pthread_mutex_lock(&soln_lock);
	free_soln();
	soln_pattern = strdup(pattern);
	soln_private_key = BN_bn2hex(EC_KEY_get0_private_key(pkey));

	/* Signal the generator to stop */
	vcp->vc_halt = 1;

	/* Wake up the main thread, if it's sleeping */
	pthread_cond_broadcast(&soln_cond);
	pthread_mutex_unlock(&soln_lock);
}

int
check_solution(server_context_t *scp, pubkeybatch_t *pbatch)
{
	int res = 0;
	pthread_mutex_lock(&soln_lock);
	if (soln_private_key != NULL) {
		workitem_t *wip = workitem_avl_search(&pbatch->items,
						      soln_pattern);
		assert(wip != NULL);
		avl_remove(&pbatch->items, &wip->avlent);
		pbatch->nitems--;
		pbatch->total_value -= wip->value;
		server_context_submit_solution(scp, wip, soln_private_key);
		if (wip->pubkey == pbatch->pubkey)
			wip->pubkey = NULL;
		server_workitem_free(wip);
		free_soln();
		res = 1;
	}
	pthread_mutex_unlock(&soln_lock);
	return res;
}

pubkeybatch_t *
most_valuable_pkb(server_context_t *scp)
{
	pubkeybatch_t *pbatch, *res = NULL;
	for (pbatch = pubkeybatch_avl_first(&scp->items);
	     pbatch != NULL;
	     pbatch = pubkeybatch_avl_next(pbatch)) {
		if (!res || (res->total_value < pbatch->total_value))
			res = pbatch;
	}
	return res;
}

void
usage(const char *name)
{
	fprintf(stderr,
"oclVanityMiner %s (" OPENSSL_VERSION_TEXT ")\n"
"Usage: %s -u <URL> -a <credit address>\n"
"Organized vanity address mining client using OpenCL.  Contacts the specified\n"
"bounty pool server, downloads a list of active bounties, and attempts to\n"
"generate the address with the best difficulty to reward ratio.  Maintains\n"
"contact with the bounty pool server and periodically refreshes the bounty\n"
"list.\n"
"By default, if no device is specified, and the system has exactly one OpenCL\n"
"device, it will be selected automatically, otherwise if the system has\n"
"multiple OpenCL devices and no device is specified, an error will be\n"
"reported.  To use multiple devices simultaneously, specify the -D option for\n"
"each device.\n"
"\n"
"Options:\n"
"-u <URL>      Bounty pool URL\n"
"-a <address>  Credit address for completed work\n"
"-i <interval> Set server polling interval in seconds (default 90)\n"
"              TIP: For pay-per-share vanity pools, set interval\n"
"                   lower to process the maximum shares possible!\n"
"-x <altURL>   Specify an alternate url for getWork requests.\n"
"              Example: \"-x http://127.0.0.1/getWork\"\n"
"              Useful for manually choosing what prefix to work on.\n"
"              Copy \"/getWork\" from pool then choose prefixes and self host.\n"
"              Solved prefixes will still be sent to the url set in \"-u <URL>\".\n"
"-v            Verbose output\n"
"-q            Quiet output\n"
"-p <platform> Select OpenCL platform\n"
"-d <device>   Select OpenCL device\n"
"-D <devstr>   Use OpenCL device, identified by device string\n"
"              Form: <platform>:<devicenumber>[,<options>]\n"
"              Example: 0:0,grid=1024x1024\n"
"-S            Safe mode, disable OpenCL loop unrolling optimizations\n"
"-w <worksize> Set work items per thread in a work unit\n"
"-t <threads>  Set target thread count per multiprocessor\n"
"-g <x>x<y>    Set grid size\n"
"-b <invsize>  Set modular inverse ops per thread\n"
"-V            Enable kernel/OpenCL/hardware verification (SLOW)\n"
"-m <minvalue> Set minimum value (in BTC/Mkey) for the miner to accept work\n",
version, name);
}

#define MAX_DEVS 32

int
main(int argc, char **argv)
{
	const char *url = NULL;
	const char *credit_addr = NULL;
	int opt;
	int platformidx = -1, deviceidx = -1;
	char *pend;
	int verbose = 1;
	int interval = 90;
	int nthreads = 0;
	int worksize = 0;
	int nrows = 0, ncols = 0;
	int invsize = 0;
	int verify_mode = 0;
	int safe_mode = 0;
	float min_value = 0;

	char *devstrs[MAX_DEVS];
	int ndevstrs = 0;

	vg_context_t *vcp = NULL;
	vg_ocl_context_t *vocp = NULL;

	int res;
	int thread_started = 0;
	pubkeybatch_t *active_pkb = NULL;

	server_context_t *scp = NULL;
	pubkeybatch_t *pkb;
	int was_sleeping = 0;

	struct timeval tv;
	struct timespec sleepy;

	pthread_mutex_init(&soln_lock, NULL);
	pthread_cond_init(&soln_cond, NULL);

	if (argc == 1) {
		usage(argv[0]);
		return 1;
	}

	while ((opt = getopt(argc, argv,
			     "x:u:a:vqp:d:w:t:g:b:VD:Sh?i:m:")) != -1) {
		switch (opt) {
		case 'x':
			strcpy(workurl, optarg);
			workurlFlag = 1;
			break;
		case 'u':
			url = optarg;
			break;
		case 'a':
			credit_addr = optarg;
			break;
		case 'v':
			verbose = 2;
			break;
		case 'q':
			verbose = 0;
			break;
		case 'i':
			interval = atoi(optarg);
			if (interval < 1) {
				fprintf(stderr,
					"Invalid interval '%s'\n", optarg);
				return 1;
			}
			break;
		case 'p':
			platformidx = atoi(optarg);
			break;
		case 'd':
			deviceidx = atoi(optarg);
			break;
		case 'w':
			worksize = atoi(optarg);
			if (worksize == 0) {
				fprintf(stderr,
					"Invalid work size '%s'\n", optarg);
				return 1;
			}
			break;
		case 't':
			nthreads = atoi(optarg);
			if (nthreads == 0) {
				fprintf(stderr,
					"Invalid thread count '%s'\n", optarg);
				return 1;
			}
			break;
		case 'g':
			nrows = 0;
			ncols = strtol(optarg, &pend, 0);
			if (pend && *pend == 'x') {
				nrows = strtol(pend+1, NULL, 0);
			}
			if (!nrows || !ncols) {
				fprintf(stderr,
					"Invalid grid size '%s'\n", optarg);
				return 1;
			}
			break;
		case 'b':
			invsize = atoi(optarg);
			if (!invsize) {
				fprintf(stderr,
					"Invalid modular inverse size '%s'\n",
					optarg);
				return 1;
			}
			if (invsize & (invsize - 1)) {
				fprintf(stderr,
					"Modular inverse size must be "
					"a power of 2\n");
				return 1;
			}
			break;
		case 'V':
			verify_mode = 1;
			break;
		case 'S':
			safe_mode = 1;
			break;
		case 'D':
			if (ndevstrs >= MAX_DEVS) {
				fprintf(stderr,
					"Too many OpenCL devices (limit %d)\n",
					MAX_DEVS);
				return 1;
			}
			devstrs[ndevstrs++] = optarg;
			break;
		case 'm':
			min_value = atof(optarg);
			break;
		default:
			usage(argv[0]);
			return 1;
		}
	}

#if OPENSSL_VERSION_NUMBER < 0x10000000L
	/* Complain about older versions of OpenSSL */
	if (verbose > 0) {
		fprintf(stderr,
			"WARNING: Built with " OPENSSL_VERSION_TEXT "\n"
			"WARNING: Use OpenSSL 1.0.0d+ for best performance\n");
	}
#endif
	curl_easy_init();

	vcp = vg_prefix_context_new(0, 128, 0);

	vcp->vc_verbose = verbose;

	vcp->vc_output_match = output_match_work_complete;
	vcp->vc_output_timing = vg_output_timing_console;


	if (!url) {
		fprintf(stderr, "ERROR: No server URL specified\n");
		return 1;
	}
	if (!credit_addr) {
		fprintf(stderr, "ERROR: No reward address specified\n");
		return 1;
	}
	if (!vg_b58_decode_check(credit_addr, NULL, 0)) {
		fprintf(stderr, "ERROR: Invalid reward address specified\n");
		return 1;
	}

	scp = server_context_new(url, credit_addr);
	scp->verbose = verbose;

	/* Get the initial bounty list, abort on failure */
	if (server_context_getwork(scp))
		return 1;

	/* Set up OpenCL */
	res = 0;
	if (ndevstrs) {
		for (opt = 0; opt < ndevstrs; opt++) {
			vocp = vg_ocl_context_new_from_devstr(vcp, devstrs[opt],
							      safe_mode,
							      verify_mode);
			if (!vocp) {
				fprintf(stderr,
				"Could not open device '%s', ignoring\n",
					devstrs[opt]);
			} else {
				res++;
			}
		}
	} else {
		vocp = vg_ocl_context_new(vcp, platformidx, deviceidx,
					  safe_mode, verify_mode,
					  worksize, nthreads,
					  nrows, ncols, invsize);
		if (vocp)
			res++;
	}
	if (!res) {
		vg_ocl_enumerate_devices();
		return 1;
	}

	if (verbose > 1)
		dump_work(scp);

	while (1) {
		if (avl_root_empty(&scp->items))
			server_context_getwork(scp);

		pkb = most_valuable_pkb(scp);
		
		if( pkb && pkb->total_value < min_value ) {
			fprintf(stderr,
				"Value of current work (%f BTC/Mkey) does not meet minimum value (%f BTC/Mkey)\n",
				pkb->total_value, min_value);
			fprintf(stderr, "Sleeping\n");
			was_sleeping = 1;
			pkb = NULL;
		}
		
		/* If the work item is the same as the one we're executing,
		   keep it */
		if (pkb && active_pkb &&
		    server_pubkeybatch_equal(scp, active_pkb, pkb))
			pkb = active_pkb;

		if (thread_started && (!active_pkb || (pkb != active_pkb))) {
			/* If a thread is running, stop it */
			vg_context_stop_threads(vcp);
			thread_started = 0;
			if (active_pkb) {
				check_solution(scp, active_pkb);
				active_pkb = NULL;
			}
			vg_context_clear_all_patterns(vcp);

			if (verbose > 1)
				dump_work(scp);
		}

		if (!pkb) {
			if (!was_sleeping) {
				fprintf(stderr,
					"No work available, sleeping\n");
				was_sleeping = 1;
			}

		} else if (!active_pkb) {
			workitem_t *wip;
			was_sleeping = 0;
			vcp->vc_pubkey_base = pkb->pubkey;
			for (wip = workitem_avl_first(&pkb->items);
			     wip != NULL;
			     wip = workitem_avl_next(wip)) {
				fprintf(stderr,
					"Searching for pattern: \"%s\" "
					"Reward: %f Value: %f BTC/Mkey\n",
					wip->pattern,
					wip->reward,
					wip->value);
				vcp->vc_addrtype = wip->addrtype;
				if (!vg_context_add_patterns(vcp,
							     &wip->pattern,
							     1)) {
					fprintf(stderr,
					   "WARNING: could not add pattern\n");
				}
				
				assert(vcp->vc_npatterns);
			}

			fprintf(stderr, 
				"\nTotal value for current work: %f BTC/Mkey\n", 
				pkb->total_value);
			
			res = vg_context_start_threads(vcp);
			if (res)
				return 1;
			thread_started = 1;
			active_pkb = pkb;
		}

		/* Wait for something to happen */
		gettimeofday(&tv, NULL);
		sleepy.tv_sec = tv.tv_sec;
		sleepy.tv_nsec = tv.tv_usec * 1000;
		sleepy.tv_sec += interval;

		pthread_mutex_lock(&soln_lock);
		res = 0;
		if (!soln_private_key)
			res = pthread_cond_timedwait(&soln_cond,
						     &soln_lock, &sleepy);
		pthread_mutex_unlock(&soln_lock);

		if (res == 0) {
			if (check_solution(scp, active_pkb))
				active_pkb = NULL;
		}
		else if (res == ETIMEDOUT) {
			free_pkb_tree(&scp->items, active_pkb);
		}
	}

	return 0;
}
