Browse Source

Initial version of coinbase checking function for GBT and stratum

Huang Le 11 years ago
parent
commit
80277a61a4
3 changed files with 283 additions and 0 deletions
  1. 109 0
      miner.c
  2. 21 0
      miner.h
  3. 153 0
      util.c

+ 109 - 0
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"
@@ -1572,6 +1573,86 @@ 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 addr_hash *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_STR(pool->cb_param.addr_ht, addr, 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(struct addr_hash));
+			ah->addr = addr;
+			HASH_ADD_STR(pool->cb_param.addr_ht, addr, 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.addr_ht)
+		return "Define --coinbase-check-addr list first, then the --coinbase-check-total argument";
+	
+	pool->cb_param.perc = atof(arg);
+	if (pool->cb_param.perc < 0.0 || pool->cb_param.perc > 1.0)
+		return "The percentage should be between 0 and 1";
+	
+	return NULL;
+}
+
 static char *set_pool_priority(const char *arg)
 {
 	struct pool *pool;
@@ -2442,6 +2523,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"),
@@ -3114,6 +3213,12 @@ 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;
 			
+			if (!check_coinbase(cbtxn, cbtxnsz, &pool->cb_param))
+			{
+				applog(LOG_ERR, "Mark pool %d as misbehaving for failing to pass coinbase check", pool->pool_no);
+				disable_pool(pool, POOL_MISBEHAVING);
+			}
+			
 			cg_wlock(&pool->data_lock);
 			swork->tr = work->tr;
 			bytes_assimilate_raw(&swork->coinbase, cbtxn, cbtxnsz, cbtxnsz);
@@ -7320,6 +7425,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",
@@ -12228,6 +12336,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();

+ 21 - 0
miner.h

@@ -1181,6 +1181,7 @@ enum pool_enable {
 	POOL_DISABLED,
 	POOL_ENABLED,
 	POOL_REJECTING,
+	POOL_MISBEHAVING,
 };
 
 enum pool_protocol {
@@ -1241,6 +1242,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 addr_hash {
+	char* addr;
+	UT_hash_handle hh;
+};
+
+struct coinbase_param {
+	bool testnet;
+	struct addr_hash *addr_ht;
+	int64_t total;
+	float perc;
+};
+
 struct pool {
 	int pool_no;
 	int prio;
@@ -1351,6 +1368,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;
 };
@@ -1527,6 +1547,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,

+ 153 - 0
util.c

@@ -53,6 +53,7 @@
 # include <mmsystem.h>
 #endif
 
+#include <libbase58.h>
 #include <utlist.h>
 
 #ifdef NEED_BFG_LOWL_VCOM
@@ -2351,6 +2352,151 @@ 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)
+	{
+		char addr[35];
+		
+		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;
+		
+		i = script_to_address(addr, sizeof(addr), coinbase + pos, curr_pk_script_len, cb_param->testnet);
+		if (i && i <= sizeof(addr))
+		{
+			/* So this script is to payout to an valid address */
+			struct addr_hash *ah = NULL;
+			HASH_FIND_STR(cb_param->addr_ht, addr, ah);
+			if (ah)
+			{
+				found_target = true;
+				target += amount;
+			}
+			if (opt_debug)
+				applog(LOG_DEBUG, "Coinbase output: %10"PRIu64" -- %34s%c", amount, addr, ah ? '*' : '\0');
+		}
+		else
+		if (opt_debug)
+		{
+			char *hex = addr;
+			if (curr_pk_script_len * 2 >= sizeof(addr))
+				hex = malloc(curr_pk_script_len * 2 + 1);
+			if (hex)
+			{
+				bin2hex(hex, coinbase + pos, curr_pk_script_len);
+				applog(LOG_DEBUG, "Coinbase output: %10"PRIu64" PK %34s", amount, hex);
+				if (hex != addr)
+					free(hex);
+			}
+			else
+				applog(LOG_DEBUG, "Coinbase output: %10"PRIu64" PK (Unknown)", amount);
+		}
+		
+		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->addr_ht)
+	{
+		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->addr_ht)), target, total);
+	
+	return true;
+}
+
 static bool parse_notify(struct pool *pool, json_t *val)
 {
 	const char *prev_hash, *coinbase1, *coinbase2, *bbversion, *nbit, *ntime;
@@ -2437,6 +2583,13 @@ static bool parse_notify(struct pool *pool, json_t *val)
 	
 	memcpy(pool->swork.target, pool->next_target, 0x20);
 	
+	if (!check_coinbase(coinbase, bytes_len(&pool->swork.coinbase), &pool->cb_param))
+	{
+		applog(LOG_ERR, "Mark pool %d as misbehaving for failing to pass coinbase check", pool->pool_no);
+		/* Just go through the rest process to avoid an "unknown stratum message" log */
+		disable_pool(pool, POOL_MISBEHAVING);
+	}
+	
 	cg_wunlock(&pool->data_lock);
 
 	applog(LOG_DEBUG, "Received stratum notify from pool %u with job_id=%s",