Browse Source

Merge branch 'coinbase_check' into bfgminer

Conflicts:
	miner.c
Luke Dashjr 11 years ago
parent
commit
47466dc25b
4 changed files with 290 additions and 1 deletions
  1. 3 0
      README
  2. 129 1
      miner.c
  3. 22 0
      miner.h
  4. 136 0
      util.c

+ 3 - 0
README

@@ -252,6 +252,9 @@ Options for both config file and command line:
 --cmd-sick <arg>    Execute a command when a device is declared sick
 --cmd-dead <arg>    Execute a command when a device is declared dead
 --coinbase-addr <arg> Set coinbase payout address for solo mining
+--coinbase-check-addr <arg> A list of address to check against in coinbase payout list received from the previous-defined pool, separated by ','
+--coinbase-check-total <arg> The least total payout amount expected in coinbase received from the previous-defined pool
+--coinbase-check-percent <arg> The least benefit percentage expected for the sum of addr(s) listed in --cbaddr argument for previous-defined pool
 --coinbase-sig <arg> Set coinbase signature when possible
 --compact           Use compact display without per device statistics
 --debug|-D          Enable debug output

+ 129 - 1
miner.c

@@ -71,6 +71,7 @@
 #include <blkmaker.h>
 #include <blkmaker_jansson.h>
 #include <blktemplate.h>
+#include <libbase58.h>
 
 #include "compat.h"
 #include "deviceapi.h"
@@ -1574,6 +1575,87 @@ static char *set_userpass(const char *arg)
 	return NULL;
 }
 
+static char *set_cbcaddr(char *arg)
+{
+	struct pool *pool;
+	char *p, *addr;
+	bytes_t target_script = BYTES_INIT;
+	
+	if (!total_pools)
+		return "Define pool first, then the --coinbase-check-addr list";
+	
+	pool = pools[total_pools - 1];
+	
+	/* NOTE: 'x' is a new prefix which leads both mainnet and testnet address, we would
+	 * need support it later, but now leave the code just so.
+	 *
+	 * Regarding details of address prefix 'x', check the below URL:
+	 * https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Serialization_format
+	 */
+	pool->cb_param.testnet = (arg[0] != '1' && arg[0] != '3' && arg[0] != 'x');
+	
+	for (; (addr = strtok_r(arg, ",", &p)); arg = NULL)
+	{
+		struct bytes_hashtbl *ah;
+		
+		if (set_b58addr(addr, &target_script))
+			/* No bother to free memory since we are going to exit anyway */
+			return "Invalid address in --coinbase-check-address list";
+		
+		HASH_FIND(hh, pool->cb_param.scripts, bytes_buf(&target_script), bytes_len(&target_script), ah);
+		if (!ah)
+		{
+			/* Note: for the below allocated memory we have good way to release its memory
+			 * since we can't be sure there are no reference to the pool struct when remove_pool() 
+			 * get called.
+			 *
+			 * We just hope the remove_pool() would not be called many many times during
+			 * the whole running life of this program.
+			 */
+			ah = malloc(sizeof(*ah));
+			bytes_init(&ah->b);
+			bytes_assimilate(&ah->b, &target_script);
+			HASH_ADD(hh, pool->cb_param.scripts, b.buf, bytes_len(&ah->b), ah);
+		}
+	}
+	bytes_free(&target_script);
+	
+	return NULL;
+}
+
+static char *set_cbctotal(const char *arg)
+{
+	struct pool *pool;
+	
+	if (!total_pools)
+		return "Define pool first, then the --coinbase-check-total argument";
+	
+	pool = pools[total_pools - 1];
+	pool->cb_param.total = atoll(arg);
+	if (pool->cb_param.total < 0)
+		return "The total payout amount in coinbase should be greater than 0";
+	
+	return NULL;
+}
+
+static char *set_cbcperc(const char *arg)
+{
+	struct pool *pool;
+	
+	if (!total_pools)
+		return "Define pool first, then the --coinbase-check-percent argument";
+	
+	pool = pools[total_pools - 1];
+	if (!pool->cb_param.scripts)
+		return "Define --coinbase-check-addr list first, then the --coinbase-check-total argument";
+	
+	pool->cb_param.perc = atof(arg) / 100;
+	if (pool->cb_param.perc < 0.0 || pool->cb_param.perc > 1.0)
+		return "The percentage should be between 0 and 100";
+	
+	return NULL;
+}
+
 static char *set_pool_priority(const char *arg)
 {
 	struct pool *pool;
@@ -2444,6 +2526,24 @@ static struct opt_table opt_config_table[] = {
 	OPT_WITH_ARG("--userpass|-O",
 		     set_userpass, NULL, NULL,
 		     "Username:Password pair for bitcoin JSON-RPC server"),
+	OPT_WITH_ARG("--coinbase-check-addr",
+			set_cbcaddr, NULL, NULL,
+			"A list of address to check against in coinbase payout list received from the previous-defined pool, separated by ','"),
+	OPT_WITH_ARG("--cbcheck-addr|--cbc-addr|--cbcaddr",
+			set_cbcaddr, NULL, NULL,
+			opt_hidden),
+	OPT_WITH_ARG("--coinbase-check-total",
+			set_cbctotal, NULL, NULL,
+			"The least total payout amount expected in coinbase received from the previous-defined pool"),
+	OPT_WITH_ARG("--cbcheck-total|--cbc-total|--cbctotal",
+			set_cbctotal, NULL, NULL,
+			opt_hidden),
+	OPT_WITH_ARG("--coinbase-check-percent",
+			set_cbcperc, NULL, NULL,
+			"The least benefit percentage expected for the sum of addr(s) listed in --cbaddr argument for previous-defined pool"),
+	OPT_WITH_ARG("--cbcheck-percent|--cbc-percent|--cbcpercent|--cbcperc",
+			set_cbcperc, NULL, NULL,
+			opt_hidden),
 	OPT_WITHOUT_ARG("--worktime",
 			opt_set_bool, &opt_worktime,
 			"Display extra work time debug information"),
@@ -2841,6 +2941,27 @@ bool pool_may_redirect_to(struct pool * const pool, const char * const uri)
 	return match_domains(pool->rpc_url, strlen(pool->rpc_url), uri, strlen(uri));
 }
 
+void pool_check_coinbase(struct pool * const pool, const uint8_t * const cbtxn, const size_t cbtxnsz)
+{
+	if (uri_get_param_bool(pool->rpc_url, "skipcbcheck", false))
+	{}
+	else
+	if (!check_coinbase(cbtxn, cbtxnsz, &pool->cb_param))
+	{
+		if (pool->enabled == POOL_ENABLED)
+		{
+			applog(LOG_ERR, "Pool %d misbehaving (%s), disabling!", pool->pool_no, "coinbase check");
+			disable_pool(pool, POOL_MISBEHAVING);
+		}
+	}
+	else
+	if (pool->enabled == POOL_MISBEHAVING)
+	{
+		applog(LOG_NOTICE, "Pool %d no longer misbehaving, re-enabling!", pool->pool_no);
+		enable_pool(pool);
+	}
+}
+
 void set_simple_ntime_roll_limit(struct ntime_roll_limits * const nrl, const uint32_t ntime_base, const int ntime_roll, const struct timeval * const tvp_ref)
 {
 	const int offsets = max(ntime_roll, 60);
@@ -3159,6 +3280,8 @@ static bool work_decode(struct pool *pool, struct work *work, json_t *val)
 			struct stratum_work * const swork = &pool->swork;
 			const size_t branchdatasz = branchcount * 0x20;
 			
+			pool_check_coinbase(pool, cbtxn, cbtxnsz);
+			
 			cg_wlock(&pool->data_lock);
 			swork->tr = work->tr;
 			bytes_assimilate_raw(&swork->coinbase, cbtxn, cbtxnsz, cbtxnsz);
@@ -7387,6 +7510,9 @@ updated:
 				case POOL_REJECTING:
 					wlogprint("Rejectin ");
 					break;
+				case POOL_MISBEHAVING:
+					wlogprint("Misbehav ");
+					break;
 			}
 			_wlogprint(pool_proto_str(pool));
 			wlogprint(" Quota %d Pool %d: %s  User:%s\n",
@@ -8683,7 +8809,8 @@ static bool cnx_needed(struct pool *pool)
 {
 	struct pool *cp;
 
-	if (pool->enabled != POOL_ENABLED)
+	// We want to keep a connection open for rejecting or misbehaving pools, to detect when/if they change their tune
+	if (pool->enabled == POOL_DISABLED)
 		return false;
 
 	/* Idle stratum pool needs something to kick it alive again */
@@ -12321,6 +12448,7 @@ int main(int argc, char *argv[])
 	
 	atexit(bfg_atexit);
 
+	b58_sha256_impl = my_blkmaker_sha256_callback;
 	blkmk_sha256_impl = my_blkmaker_sha256_callback;
 
 	bfg_init_threadlocal();

+ 22 - 0
miner.h

@@ -1182,6 +1182,7 @@ enum pool_enable {
 	POOL_DISABLED,
 	POOL_ENABLED,
 	POOL_REJECTING,
+	POOL_MISBEHAVING,
 };
 
 enum pool_protocol {
@@ -1243,6 +1244,22 @@ struct stratum_work {
 #define RBUFSIZE 8192
 #define RECVSIZE (RBUFSIZE - 4)
 
+/*
+ * Build an hash table in case there are lots
+ * of addresses to check against
+ */
+struct bytes_hashtbl {
+	bytes_t b;
+	UT_hash_handle hh;
+};
+
+struct coinbase_param {
+	bool testnet;
+	struct bytes_hashtbl *scripts;
+	int64_t total;
+	float perc;
+};
+
 struct pool {
 	int pool_no;
 	int prio;
@@ -1353,6 +1370,9 @@ struct pool {
 	pthread_mutex_t stratum_lock;
 	char *admin_msg;
 
+	/* param for coinbase check */
+	struct coinbase_param cb_param;
+	
 	pthread_mutex_t last_work_lock;
 	struct work *last_work_copy;
 };
@@ -1509,6 +1529,7 @@ extern void clear_logwin(void);
 extern void logwin_update(void);
 extern bool pool_tclear(struct pool *pool, bool *var);
 extern bool pool_may_redirect_to(struct pool *, const char *uri);
+extern void pool_check_coinbase(struct pool *, const uint8_t *cbtxn, size_t cbtxnsz);
 extern struct thread_q *tq_new(void);
 extern void tq_free(struct thread_q *tq);
 extern bool tq_push(struct thread_q *tq, void *data);
@@ -1550,6 +1571,7 @@ extern struct thr_info *get_thread(int thr_id);
 extern struct cgpu_info *get_devices(int id);
 extern int create_new_cgpus(void (*addfunc)(void*), void *arg);
 extern int scan_serial(const char *);
+extern bool check_coinbase(const uint8_t *, size_t, const struct coinbase_param *cb_param);
 
 enum api_data_type {
 	API_ESCAPE,

+ 136 - 0
util.c

@@ -53,6 +53,7 @@
 # include <mmsystem.h>
 #endif
 
+#include <libbase58.h>
 #include <utlist.h>
 
 #ifdef NEED_BFG_LOWL_VCOM
@@ -2403,6 +2404,139 @@ void stratum_probe_transparency(struct pool *pool)
 	pool->swork.transparency_probed = true;
 }
 
+size_t script_to_address(char *out, size_t outsz, const uint8_t *script, size_t scriptsz, bool testnet)
+{
+	char addr[35];
+	size_t size = sizeof(addr);
+	bool bok = false;
+	
+	if (scriptsz == 25 && script[0] == 0x76 && script[1] == 0xa9 && script[2] == 0x14 && script[23] == 0x88 && script[24] == 0xac)
+		bok = b58check_enc(addr, &size, testnet ? 0x6f : 0x00, &script[3], 20);
+	else if (scriptsz == 23 && script[0] == 0xa9 && script[1] == 0x14 && script[22] == 0x87)
+		bok = b58check_enc(addr, &size, testnet ? 0xc4 : 0x05, &script[2], 20);
+	if (!bok)
+		return 0;
+	if (outsz >= size)
+		strcpy(out, addr);
+	return size;
+}
+
+size_t varint_decode(const uint8_t *p, size_t size, uint64_t *n)
+{
+	if (size > 8 && p[0] == 0xff)
+	{
+		*n = upk_u64le(p, 0);
+		return 9;
+	}
+	if (size > 4 && p[0] == 0xfe)
+	{
+		*n = upk_u32le(p, 0);
+		return 5;
+	}
+	if (size > 2 && p[0] == 0xfd)
+	{
+		*n = upk_u16le(p, 0);
+		return 3;
+	}
+	if (size > 0)
+	{
+		*n = p[0];
+		return 1;
+	}
+	return 0;
+}
+
+/* Caller ensure cb_param is an valid pointer */
+bool check_coinbase(const uint8_t *coinbase, size_t cbsize, const struct coinbase_param *cb_param)
+{
+	int i;
+	size_t pos;
+	uint64_t len, total, target, amount, curr_pk_script_len;
+	bool found_target = false;
+	
+	if (cbsize < 62)
+		/* Smallest possible length */
+		applogr(false, LOG_ERR, "Coinbase check: invalid length -- %lu", (unsigned long)cbsize);
+	pos = 4; /* Skip the version */
+	
+	if (coinbase[pos] != 1)
+		applogr(false, LOG_ERR, "Coinbase check: multiple inputs in coinbase: 0x%02x", coinbase[pos]);
+	pos += 1 /* varint length */ + 32 /* prevhash */ + 4 /* 0xffffffff */;
+	
+	if (coinbase[pos] < 2 || coinbase[pos] > 100)
+		applogr(false, LOG_ERR, "Coinbase check: invalid input script sig length: 0x%02x", coinbase[pos]);
+	pos += 1 /* varint length */ + coinbase[pos] + 4 /* 0xffffffff */;
+	
+	if (cbsize <= pos)
+incomplete_cb:
+		applogr(false, LOG_ERR, "Coinbase check: incomplete coinbase for payout check");
+	
+	total = target = 0;
+	
+	i = varint_decode(coinbase + pos, cbsize - pos, &len);
+	if (!i)
+		goto incomplete_cb;
+	pos += i;
+	
+	while (len-- > 0)
+	{
+		if (cbsize <= pos + 8)
+			goto incomplete_cb;
+		
+		amount = upk_u64le(coinbase, pos);
+		pos += 8; /* amount length */
+		
+		total += amount;
+		
+		i = varint_decode(coinbase + pos, cbsize - pos, &curr_pk_script_len);
+		if (!i || cbsize <= pos + i + curr_pk_script_len)
+			goto incomplete_cb;
+		pos += i;
+		
+		struct bytes_hashtbl *ah = NULL;
+		HASH_FIND(hh, cb_param->scripts, &coinbase[pos], curr_pk_script_len, ah);
+		if (ah)
+		{
+			found_target = true;
+			target += amount;
+		}
+		
+		if (opt_debug)
+		{
+			char s[(curr_pk_script_len * 2) + 3];
+			i = script_to_address(s, sizeof(s), &coinbase[pos], curr_pk_script_len, cb_param->testnet);
+			if (!(i && i <= sizeof(s)))
+			{
+				s[0] = '[';
+				bin2hex(&s[1], &coinbase[pos], curr_pk_script_len);
+				strcpy(&s[(curr_pk_script_len * 2) + 1], "]");
+			}
+			applog(LOG_DEBUG, "Coinbase output: %10"PRIu64" -- %s%s", amount, s, ah ? "*" : "");
+		}
+		
+		pos += curr_pk_script_len;
+	}
+	if (total < cb_param->total)
+		applogr(false, LOG_ERR, "Coinbase check: lopsided total output amount = %"PRIu64", expecting >=%"PRIu64, total, cb_param->total);
+	if (cb_param->scripts)
+	{
+		if (cb_param->perc && !(total && (float)((double)target / total) >= cb_param->perc))
+			applogr(false, LOG_ERR, "Coinbase check: lopsided target/total = %g(%"PRIu64"/%"PRIu64"), expecting >=%g", (total ? (double)target / total : (double)0), target, total, cb_param->perc);
+		else
+		if (!found_target)
+			applogr(false, LOG_ERR, "Coinbase check: not found any target addr");
+	}
+	
+	if (cbsize < pos + 4)
+		applogr(false, LOG_ERR, "Coinbase check: No room for locktime");
+	pos += 4;
+	
+	if (opt_debug)
+		applog(LOG_DEBUG, "Coinbase: (size, pos, addr_count, target, total) = (%lu, %lu, %d, %"PRIu64", %"PRIu64")", (unsigned long)cbsize, (unsigned long)pos, (int)(HASH_COUNT(cb_param->scripts)), target, total);
+	
+	return true;
+}
+
 static bool parse_notify(struct pool *pool, json_t *val)
 {
 	const char *prev_hash, *coinbase1, *coinbase2, *bbversion, *nbit, *ntime;
@@ -2489,6 +2623,8 @@ static bool parse_notify(struct pool *pool, json_t *val)
 	
 	memcpy(pool->swork.target, pool->next_target, 0x20);
 	
+	pool_check_coinbase(pool, coinbase, bytes_len(&pool->swork.coinbase));
+	
 	cg_wunlock(&pool->data_lock);
 
 	applog(LOG_DEBUG, "Received stratum notify from pool %u with job_id=%s",