def generate_job(self, push=False, flush=False, new_block=False): """ Creates a new job for miners to work on. Push will trigger an event that sends new work but doesn't force a restart. If flush is true a job restart will be triggered. """ # aux monitors will often call this early when not needed at startup if not self._last_gbt: self.logger.warn("Cannot generate new job, missing last GBT info") return if self.merged_work: tree, size = bitcoin_data.make_auxpow_tree(self.merged_work) mm_hashes = [ self.merged_work.get(tree.get(i), dict(hash=0))['hash'] for i in xrange(size) ] mm_data = '\xfa\xbemm' mm_data += bitcoin_data.aux_pow_coinbase_type.pack( dict( merkle_root=bitcoin_data.merkle_hash(mm_hashes), size=size, nonce=0, )) merged_data = {} for aux_work in self.merged_work.itervalues(): data = dict(target=aux_work['target'], hash=aux_work['hash'], height=aux_work['height'], index=mm_hashes.index(aux_work['hash']), type=aux_work['type'], hashes=mm_hashes) merged_data[aux_work['type']] = data else: merged_data = {} mm_data = None self.logger.info("Generating new block template with {} trans. " "Diff {:,.4f}. Subsidy {:,.2f}. Height {:,}. " "Merged chains: {}".format( len(self._last_gbt['transactions']), bits_to_difficulty(self._last_gbt['bits']), self._last_gbt['coinbasevalue'] / 100000000.0, self._last_gbt['height'], ', '.join(merged_data.keys()))) # here we recalculate the current merkle branch and partial # coinbases for passing to the mining clients coinbase = Transaction() coinbase.version = 2 # create a coinbase input with encoded height and padding for the # extranonces so script length is accurate extranonce_length = (self.config['extranonce_size'] + self.config['extranonce_serv_size']) coinbase.inputs.append( Input.coinbase(self._last_gbt['height'], addtl_push=[mm_data] if mm_data else [], extra_script_sig=b'\0' * extranonce_length)) # Darkcoin payee amount if self._last_gbt.get('payee', '') != '': payout = self._last_gbt['coinbasevalue'] / 5 self._last_gbt['coinbasevalue'] -= payout coinbase.outputs.append( Output.to_address(payout, self._last_gbt['payee'])) self.logger.info( "Paying out masternode at addr {}. Payout {}. Blockval reduced to {}" .format(self._last_gbt['payee'], payout, self._last_gbt['coinbasevalue'])) # simple output to the proper address and value coinbase.outputs.append( Output.to_address(self._last_gbt['coinbasevalue'], self.config['pool_address'])) job_id = hexlify(struct.pack(str("I"), self._job_counter)) bt_obj = BlockTemplate.from_gbt( self._last_gbt, coinbase, extranonce_length, [ Transaction(unhexlify(t['data']), fees=t['fee']) for t in self._last_gbt['transactions'] ]) # add in our merged mining data if mm_data: hashes = [ bitcoin_data.hash256(tx.raw) for tx in bt_obj.transactions ] bt_obj.merkle_link = bitcoin_data.calculate_merkle_link([None] + hashes, 0) bt_obj.merged_data = merged_data bt_obj.job_id = job_id bt_obj.diff1 = self.config['diff1'] bt_obj.algo = self.config['algo'] bt_obj.pow_block_hash = self.config['pow_block_hash'] bt_obj.block_height = self._last_gbt['height'] bt_obj.acc_shares = set() if push: if flush: self.logger.info("New work announced! Wiping previous jobs...") self.jobs.clear() self.latest_job = None else: self.logger.info("New work announced!") self._job_counter += 1 self.jobs[job_id] = bt_obj self.latest_job = job_id if push: t = time.time() bt_obj.stratum_string() for idx, client in viewitems(self.stratum_manager.clients): try: if client.authenticated: client._push(bt_obj, flush=flush) except AttributeError: pass self.logger.info( "New job enqueued for transmission to {} users in {}".format( len(self.stratum_manager.clients), time_format(time.time() - t))) if flush: self.server['work_restarts'].incr() self.server['work_pushes'].incr() self.server['new_jobs'].incr() if new_block: hex_bits = hexlify(bt_obj.bits) self.current_net['difficulty'] = bits_to_difficulty(hex_bits) self.current_net['subsidy'] = bt_obj.total_value self.current_net['height'] = bt_obj.block_height - 1 self.current_net['prev_hash'] = bt_obj.hashprev_be_hex self.current_net['transactions'] = len(bt_obj.transactions)
def found_block(self, address, worker, header, coinbase_raw, job): aux_data = job.merged_data[self.config['name']] self.block_stats['solves'] += 1 self.logger.info("New {} Aux block at height {}".format( self.config['name'], aux_data['height'])) aux_block = ( pack.IntType(256, 'big').pack(aux_data['hash']).encode('hex'), bitcoin_data.aux_pow_type.pack( dict( merkle_tx=dict( tx=bitcoin_data.tx_type.unpack(coinbase_raw), block_hash=bitcoin_data.hash256(header), merkle_link=job.merkle_link, ), merkle_link=bitcoin_data.calculate_merkle_link( aux_data['hashes'], aux_data['index']), parent_block_header=bitcoin_data.block_header_type.unpack( header), )).encode('hex'), ) retries = 0 while retries < 5: retries += 1 new_height = aux_data['height'] + 1 res = False try: res = self.coinserv.getauxblock(*aux_block) except (bitcoinrpc.CoinRPCException, socket.error, ValueError) as e: self.logger.error( "{} Aux block failed to submit to the server!".format( self.config['name']), exc_info=True) self.logger.error(getattr(e, 'error')) if res is True: self.logger.info("NEW {} Aux BLOCK ACCEPTED!!!".format( self.config['name'])) # Record it for the stats self.block_stats['accepts'] += 1 self.recent_blocks.append( dict(height=new_height, timestamp=int(time.time()))) # submit it to our reporter if configured to do so if self.config['send']: self.logger.info( "Submitting {} new block to reporter".format( self.config['name'])) # A horrible mess that grabs the required information for # reporting the new block. Pretty failsafe so at least # partial information will be reporter regardless try: hsh = self.coinserv.getblockhash(new_height) except Exception: self.logger.info("", exc_info=True) hsh = '' try: block = self.coinserv.getblock(hsh) except Exception: self.logger.info("", exc_info=True) try: trans = self.coinserv.gettransaction(block['tx'][0]) amount = trans['details'][0]['amount'] except Exception: self.logger.info("", exc_info=True) amount = -1 self.block_stats['last_solve_hash'] = hsh self.reporter.add_block( address, new_height, int(amount * 100000000), -1, "%0.6X" % bitcoin_data.FloatingInteger.from_target_upper_bound( aux_data['target']).bits, hsh, merged=self.config['reporting_id'], worker=worker) break # break retry loop if success else: self.logger.error( "{} Aux Block failed to submit to the server, " "server returned {}!".format(self.config['name'], res), exc_info=True) sleep(1) else: self.block_stats['rejects'] += 1 self.block_stats['last_solve_height'] = aux_data['height'] + 1 self.block_stats['last_solve_worker'] = "{}.{}".format(address, worker) self.block_stats['last_solve_time'] = datetime.datetime.utcnow()
def found_block(self, address, worker, header, coinbase_raw, job, start): aux_data = job.merged_data[self.config['currency']] new_height = aux_data['height'] + 1 self.block_stats['solves'] += 1 stale = new_height <= self.current_net['height'] self.logger.info("New {} Aux block at height {}".format( self.config['currency'], new_height)) aux_block = ( pack.IntType(256, 'big').pack(aux_data['hash']).encode('hex'), bitcoin_data.aux_pow_type.pack( dict( merkle_tx=dict( tx=bitcoin_data.tx_type.unpack(coinbase_raw), block_hash=bitcoin_data.hash256(header), merkle_link=job.merkle_link, ), merkle_link=bitcoin_data.calculate_merkle_link( aux_data['hashes'], aux_data['index']), parent_block_header=bitcoin_data.block_header_type.unpack( header), )).encode('hex'), ) retries = 0 while retries < 5: retries += 1 res = False try: res = self.call_rpc('getauxblock', *aux_block) except (CoinRPCException, socket.error, ValueError) as e: self.logger.error( "{} Aux block failed to submit to the server!".format( self.config['currency']), exc_info=True) self.logger.error(getattr(e, 'error')) if res is True or res is None: # Record it for the stats self.block_stats['accepts'] += 1 self.recent_blocks.append( dict(height=new_height, timestamp=int(time.time()))) # submit it to our reporter if configured to do so if self.config['send']: if start: submission_time = time.time() - start self.manager.log_event( "{name}.block_submission_{curr}:{t}|ms".format( name=self.manager.config['procname'], curr=self.config['currency'], t=submission_time * 1000)) hsh = hexlify( pack.IntType(256, 'big').pack(aux_data['hash'])) self.logger.info("{} BLOCK {}:{} accepted after {}".format( self.config['currency'], hsh, new_height, submission_time)) # A bit of a mess that grabs the required information for # reporting the new block. Pretty failsafe so at least # partial information will be reporter regardless block = None amount = 0 try: block = self.call_rpc('getblock', hsh) except Exception: self.logger.info("", exc_info=True) else: try: trans = self.call_rpc('gettxout', block['tx'][0], 0) amount = trans['value'] except Exception: self.logger.info("", exc_info=True) self.block_stats['last_solve_hash'] = hsh return dict( address=address, height=new_height, total_subsidy=int(amount * 100000000), fees=None, hex_bits="%0.6X" % bitcoin_data.FloatingInteger.from_target_upper_bound( aux_data['target']).bits, hex_hash=hsh, currency=self.config['currency'], merged=True, algo=self.config['algo'], worker=worker) break # break retry loop if success else: self.logger.error( "{} Aux Block height {} failed to submit to the server, " "server returned {}!".format(self.config['currency'], new_height, res), exc_info=True) sleep(1) else: if stale: self.block_stats['stale'] += 1 else: self.block_stats['rejects'] += 1 self.block_stats['last_solve_height'] = aux_data['height'] + 1 self.block_stats['last_solve_worker'] = "{}.{}".format(address, worker) self.block_stats['last_solve_time'] = datetime.datetime.utcnow()
def generate_job(self, push=False, flush=False, new_block=False, network='main'): """ Creates a new job for miners to work on. Push will trigger an event that sends new work but doesn't force a restart. If flush is true a job restart will be triggered. """ # aux monitors will often call this early when not needed at startup if not self._last_gbt: self.logger.warn("Cannot generate new job, missing last GBT info") return if self.auxmons: merged_work = {} auxdata = {} for auxmon in self.auxmons: # If this network hasn't pushed a job yet, skip it if auxmon.last_work['hash'] is None: continue merged_work[auxmon.last_work['chainid']] = dict( hash=auxmon.last_work['hash'], target=auxmon.last_work['type']) tree, size = bitcoin_data.make_auxpow_tree(merged_work) mm_hashes = [ merged_work.get(tree.get(i), dict(hash=0))['hash'] for i in xrange(size) ] mm_data = '\xfa\xbemm' mm_data += bitcoin_data.aux_pow_coinbase_type.pack( dict( merkle_root=bitcoin_data.merkle_hash(mm_hashes), size=size, nonce=0, )) for auxmon in self.auxmons: if auxmon.last_work['hash'] is None: continue data = dict(target=auxmon.last_work['target'], hash=auxmon.last_work['hash'], height=auxmon.last_work['height'], found_block=auxmon.found_block, index=mm_hashes.index(auxmon.last_work['hash']), type=auxmon.last_work['type'], hashes=mm_hashes) auxdata[auxmon.config['currency']] = data else: auxdata = {} mm_data = None # here we recalculate the current merkle branch and partial # coinbases for passing to the mining clients coinbase = Transaction() coinbase.version = 2 # create a coinbase input with encoded height and padding for the # extranonces so script length is accurate extranonce_length = (self.manager.config['extranonce_size'] + self.manager.config['extranonce_serv_size']) coinbase.inputs.append( Input.coinbase(self._last_gbt['height'], addtl_push=[mm_data] if mm_data else [], extra_script_sig=b'\0' * extranonce_length)) coinbase_value = self._last_gbt['coinbasevalue'] # Payout Darkcoin masternodes mn_enforcement = self._last_gbt.get('enforce_masternode_payments', True) if (self.config['payout_drk_mn'] is True or mn_enforcement is True) \ and self._last_gbt.get('payee', '') != '': # Grab the darkcoin payout amount, default to 20% payout = self._last_gbt.get('payee_amount', coinbase_value / 5) coinbase_value -= payout coinbase.outputs.append( Output.to_address(payout, self._last_gbt['payee'])) self.logger.debug( "Created TX output for masternode at ({}:{}). Coinbase value " "reduced to {}".format(self._last_gbt['payee'], payout, coinbase_value)) # simple output to the proper address and value coinbase.outputs.append( Output.to_address(coinbase_value, self.config['pool_address'])) job_id = hexlify(struct.pack(str("I"), self._job_counter)) bt_obj = BlockTemplate.from_gbt( self._last_gbt, coinbase, extranonce_length, [ Transaction(unhexlify(t['data']), fees=t['fee']) for t in self._last_gbt['transactions'] ]) # add in our merged mining data if mm_data: hashes = [ bitcoin_data.hash256(tx.raw) for tx in bt_obj.transactions ] bt_obj.merkle_link = bitcoin_data.calculate_merkle_link([None] + hashes, 0) bt_obj.merged_data = auxdata bt_obj.job_id = job_id bt_obj.diff1 = self.config['diff1'] bt_obj.algo = self.config['algo'] bt_obj.currency = self.config['currency'] bt_obj.pow_block_hash = self.config['pow_block_hash'] bt_obj.block_height = self._last_gbt['height'] bt_obj.acc_shares = set() if flush: bt_obj.type = 0 elif push: bt_obj.type = 1 else: bt_obj.type = 2 bt_obj.found_block = self.found_block # Push the fresh job to users after updating details self._job_counter += 1 if flush: self.jobs.clear() self.jobs[job_id] = bt_obj self.latest_job = bt_obj self.new_job.job = bt_obj self.new_job.set() self.new_job.clear() event = ("{name}.jobmanager.new_job:1|c\n".format( name=self.manager.config['procname'])) if push or flush: self.logger.info( "{}: New block template with {:,} trans. " "Diff {:,.4f}. Subsidy {:,.2f}. Height {:,}. Merged: {}". format("FLUSH" if flush else "PUSH", len(self._last_gbt['transactions']), bits_to_difficulty(self._last_gbt['bits']), self._last_gbt['coinbasevalue'] / 100000000.0, self._last_gbt['height'], ', '.join(auxdata.keys()))) event += ("{name}.jobmanager.work_push:1|c\n".format( name=self.manager.config['procname'])) # Stats and notifications now that it's pushed if flush: event += ("{name}.jobmanager.work_restart:1|c\n".format( name=self.manager.config['procname'])) self.logger.info("New {} network block announced! Wiping previous" " jobs and pushing".format(network)) elif push: self.logger.info( "New {} network block announced, pushing new job!".format( network)) if new_block: hex_bits = hexlify(bt_obj.bits) self.current_net['difficulty'] = bits_to_difficulty(hex_bits) self.current_net['subsidy'] = bt_obj.total_value self.current_net['height'] = bt_obj.block_height - 1 self.current_net['last_block'] = time.time() self.current_net['prev_hash'] = bt_obj.hashprev_be_hex self.current_net['transactions'] = len(bt_obj.transactions) event += ("{name}.{curr}.difficulty:{diff}|g\n" "{name}.{curr}.subsidy:{subsidy}|g\n" "{name}.{curr}.job_generate:{t}|g\n" "{name}.{curr}.height:{height}|g".format( name=self.manager.config['procname'], curr=self.config['currency'], diff=self.current_net['difficulty'], subsidy=bt_obj.total_value, height=bt_obj.block_height - 1, t=(time.time() - self._last_gbt['update_time']) * 1000)) self.manager.log_event(event)
def generate_job(self, push=False, flush=False, new_block=False, network='main'): """ Creates a new job for miners to work on. Push will trigger an event that sends new work but doesn't force a restart. If flush is true a job restart will be triggered. """ # aux monitors will often call this early when not needed at startup if not self._last_gbt: self.logger.warn("Cannot generate new job, missing last GBT info") return if self.auxmons: merged_work = {} auxdata = {} for auxmon in self.auxmons: if auxmon.last_work['hash'] is None: continue merged_work[auxmon.last_work['chainid']] = dict( hash=auxmon.last_work['hash'], target=auxmon.last_work['type'] ) tree, size = bitcoin_data.make_auxpow_tree(merged_work) mm_hashes = [merged_work.get(tree.get(i), dict(hash=0))['hash'] for i in xrange(size)] mm_data = '\xfa\xbemm' mm_data += bitcoin_data.aux_pow_coinbase_type.pack(dict( merkle_root=bitcoin_data.merkle_hash(mm_hashes), size=size, nonce=0, )) for auxmon in self.auxmons: if auxmon.last_work['hash'] is None: continue data = dict(target=auxmon.last_work['target'], hash=auxmon.last_work['hash'], height=auxmon.last_work['height'], found_block=auxmon.found_block, index=mm_hashes.index(auxmon.last_work['hash']), type=auxmon.last_work['type'], hashes=mm_hashes) auxdata[auxmon.config['currency']] = data else: auxdata = {} mm_data = None # here we recalculate the current merkle branch and partial # coinbases for passing to the mining clients coinbase = Transaction() coinbase.version = 2 # create a coinbase input with encoded height and padding for the # extranonces so script length is accurate extranonce_length = (self.manager.config['extranonce_size'] + self.manager.config['extranonce_serv_size']) coinbase.inputs.append( Input.coinbase(self._last_gbt['height'], addtl_push=[mm_data] if mm_data else [], extra_script_sig=b'\0' * extranonce_length)) coinbase_value = self._last_gbt['coinbasevalue'] # Payout Darkcoin masternodes mn_enforcement = self._last_gbt.get('enforce_masternode_payments', True) if (self.config['payout_drk_mn'] is True or mn_enforcement is True) \ and self._last_gbt.get('payee', '') != '': # Grab the darkcoin payout amount, default to 20% payout = self._last_gbt.get('payee_amount', coinbase_value / 5) coinbase_value -= payout coinbase.outputs.append( Output.to_address(payout, self._last_gbt['payee'])) self.logger.debug( "Created TX output for masternode at ({}:{}). Coinbase value " "reduced to {}".format(self._last_gbt['payee'], payout, coinbase_value)) # simple output to the proper address and value coinbase.outputs.append( Output.to_address(coinbase_value, self.config['pool_address'])) job_id = hexlify(struct.pack(str("I"), self._job_counter)) bt_obj = BlockTemplate.from_gbt(self._last_gbt, coinbase, extranonce_length, [Transaction(unhexlify(t['data']), fees=t['fee']) for t in self._last_gbt['transactions']]) # add in our merged mining data if mm_data: hashes = [bitcoin_data.hash256(tx.raw) for tx in bt_obj.transactions] bt_obj.merkle_link = bitcoin_data.calculate_merkle_link([None] + hashes, 0) bt_obj.merged_data = auxdata bt_obj.job_id = job_id bt_obj.diff1 = self.config['diff1'] bt_obj.algo = self.config['algo'] bt_obj.currency = self.config['currency'] bt_obj.pow_block_hash = self.config['pow_block_hash'] bt_obj.block_height = self._last_gbt['height'] bt_obj.acc_shares = set() if flush: bt_obj.type = 0 elif push: bt_obj.type = 1 else: bt_obj.type = 2 bt_obj.found_block = self.found_block # Push the fresh job to users after updating details self._job_counter += 1 if flush: self.jobs.clear() self.jobs[job_id] = bt_obj self.latest_job = bt_obj self.new_job.job = bt_obj self.new_job.set() self.new_job.clear() event = ("{name}.jobmanager.new_job:1|c\n" .format(name=self.manager.config['procname'])) if push or flush: self.logger.info( "{}: New block template with {:,} trans. " "Diff {:,.4f}. Subsidy {:,.2f}. Height {:,}. Merged: {}" .format("FLUSH" if flush else "PUSH", len(self._last_gbt['transactions']), bits_to_difficulty(self._last_gbt['bits']), self._last_gbt['coinbasevalue'] / 100000000.0, self._last_gbt['height'], ', '.join(auxdata.keys()))) event += ("{name}.jobmanager.work_push:1|c\n" .format(name=self.manager.config['procname'])) # Stats and notifications now that it's pushed if flush: event += ("{name}.jobmanager.work_restart:1|c\n" .format(name=self.manager.config['procname'])) self.logger.info("New {} network block announced! Wiping previous" " jobs and pushing".format(network)) elif push: self.logger.info("New {} network block announced, pushing new job!" .format(network)) if new_block: hex_bits = hexlify(bt_obj.bits) self.current_net['difficulty'] = bits_to_difficulty(hex_bits) self.current_net['subsidy'] = bt_obj.total_value self.current_net['height'] = bt_obj.block_height - 1 self.current_net['last_block'] = time.time() self.current_net['prev_hash'] = bt_obj.hashprev_be_hex self.current_net['transactions'] = len(bt_obj.transactions) event += ( "{name}.{curr}.difficulty:{diff}|g\n" "{name}.{curr}.subsidy:{subsidy}|g\n" "{name}.{curr}.job_generate:{t}|g\n" "{name}.{curr}.height:{height}|g" .format(name=self.manager.config['procname'], curr=self.config['currency'], diff=self.current_net['difficulty'], subsidy=bt_obj.total_value, height=bt_obj.block_height - 1, t=(time.time() - self._last_gbt['update_time']) * 1000)) self.manager.log_event(event)
def found_block(self, address, worker, header, coinbase_raw, job, start): aux_data = job.merged_data[self.config['currency']] new_height = aux_data['height'] + 1 self.block_stats['solves'] += 1 stale = new_height <= self.current_net['height'] self.logger.info("New {} Aux block at height {}" .format(self.config['currency'], new_height)) aux_block = ( pack.IntType(256, 'big').pack(aux_data['hash']).encode('hex'), bitcoin_data.aux_pow_type.pack(dict( merkle_tx=dict( tx=bitcoin_data.tx_type.unpack(coinbase_raw), block_hash=bitcoin_data.hash256(header), merkle_link=job.merkle_link, ), merkle_link=bitcoin_data.calculate_merkle_link(aux_data['hashes'], aux_data['index']), parent_block_header=bitcoin_data.block_header_type.unpack(header), )).encode('hex'), ) retries = 0 while retries < 5: retries += 1 res = False try: res = self.call_rpc('getauxblock', *aux_block) except (CoinRPCException, socket.error, ValueError) as e: self.logger.error("{} Aux block failed to submit to the server!" .format(self.config['currency']), exc_info=True) self.logger.error(getattr(e, 'error')) if res is True: # Record it for the stats self.block_stats['accepts'] += 1 self.recent_blocks.append( dict(height=new_height, timestamp=int(time.time()))) # submit it to our reporter if configured to do so if self.config['send']: if start: submission_time = time.time() - start self.manager.log_event( "{name}.block_submission_{curr}:{t}|ms" .format(name=self.manager.config['procname'], curr=self.config['currency'], t=submission_time * 1000)) hsh = hexlify(pack.IntType(256, 'big').pack(aux_data['hash'])) self.logger.info( "{} BLOCK {}:{} accepted after {}" .format(self.config['currency'], hsh, new_height, submission_time)) # A bit of a mess that grabs the required information for # reporting the new block. Pretty failsafe so at least # partial information will be reporter regardless block = None amount = 0 try: block = self.call_rpc('getblock', hsh) except Exception: self.logger.info("", exc_info=True) else: try: trans = self.call_rpc('gettxout', block['tx'][0], 0) amount = trans['value'] except Exception: self.logger.info("", exc_info=True) self.block_stats['last_solve_hash'] = hsh return dict(address=address, height=new_height, total_subsidy=int(amount * 100000000), fees=None, hex_bits="%0.6X" % bitcoin_data.FloatingInteger.from_target_upper_bound(aux_data['target']).bits, hex_hash=hsh, currency=self.config['currency'], merged=True, algo=self.config['algo'], worker=worker) break # break retry loop if success else: self.logger.error( "{} Aux Block failed to submit to the server, " "server returned {}!".format(self.config['currency'], res), exc_info=True) sleep(1) else: if stale: self.block_stats['stale'] += 1 else: self.block_stats['rejects'] += 1 self.block_stats['last_solve_height'] = aux_data['height'] + 1 self.block_stats['last_solve_worker'] = "{}.{}".format(address, worker) self.block_stats['last_solve_time'] = datetime.datetime.utcnow()
def found_block(self, address, worker, header, coinbase_raw, job): aux_data = job.merged_data[self.config['name']] self.block_stats['solves'] += 1 self.logger.info("New {} Aux block at height {}" .format(self.config['name'], aux_data['height'])) aux_block = ( pack.IntType(256, 'big').pack(aux_data['hash']).encode('hex'), bitcoin_data.aux_pow_type.pack(dict( merkle_tx=dict( tx=bitcoin_data.tx_type.unpack(coinbase_raw), block_hash=bitcoin_data.hash256(header), merkle_link=job.merkle_link, ), merkle_link=bitcoin_data.calculate_merkle_link(aux_data['hashes'], aux_data['index']), parent_block_header=bitcoin_data.block_header_type.unpack(header), )).encode('hex'), ) retries = 0 while retries < 5: retries += 1 new_height = aux_data['height'] + 1 res = False try: res = self.coinserv.getauxblock(*aux_block) except (bitcoinrpc.CoinRPCException, socket.error, ValueError) as e: self.logger.error("{} Aux block failed to submit to the server!" .format(self.config['name']), exc_info=True) self.logger.error(getattr(e, 'error')) if res is True: self.logger.info("NEW {} Aux BLOCK ACCEPTED!!!".format(self.config['name'])) # Record it for the stats self.block_stats['accepts'] += 1 self.recent_blocks.append( dict(height=new_height, timestamp=int(time.time()))) # submit it to our reporter if configured to do so if self.config['send']: self.logger.info("Submitting {} new block to reporter" .format(self.config['name'])) # A horrible mess that grabs the required information for # reporting the new block. Pretty failsafe so at least # partial information will be reporter regardless try: hsh = self.coinserv.getblockhash(new_height) except Exception: self.logger.info("", exc_info=True) hsh = '' try: block = self.coinserv.getblock(hsh) except Exception: self.logger.info("", exc_info=True) try: trans = self.coinserv.gettransaction(block['tx'][0]) amount = trans['details'][0]['amount'] except Exception: self.logger.info("", exc_info=True) amount = -1 self.block_stats['last_solve_hash'] = hsh self.reporter.add_block( address, new_height, int(amount * 100000000), -1, "%0.6X" % bitcoin_data.FloatingInteger.from_target_upper_bound(aux_data['target']).bits, hsh, merged=self.config['reporting_id'], worker=worker) break # break retry loop if success else: self.logger.error( "{} Aux Block failed to submit to the server, " "server returned {}!".format(self.config['name'], res), exc_info=True) sleep(1) else: self.block_stats['rejects'] += 1 self.block_stats['last_solve_height'] = aux_data['height'] + 1 self.block_stats['last_solve_worker'] = "{}.{}".format(address, worker) self.block_stats['last_solve_time'] = datetime.datetime.utcnow()
def generate_job(self, push=False, flush=False, new_block=False): """ Creates a new job for miners to work on. Push will trigger an event that sends new work but doesn't force a restart. If flush is true a job restart will be triggered. """ # aux monitors will often call this early when not needed at startup if not self._last_gbt: self.logger.warn("Cannot generate new job, missing last GBT info") return if self.merged_work: tree, size = bitcoin_data.make_auxpow_tree(self.merged_work) mm_hashes = [self.merged_work.get(tree.get(i), dict(hash=0))['hash'] for i in xrange(size)] mm_data = '\xfa\xbemm' mm_data += bitcoin_data.aux_pow_coinbase_type.pack(dict( merkle_root=bitcoin_data.merkle_hash(mm_hashes), size=size, nonce=0, )) merged_data = {} for aux_work in self.merged_work.itervalues(): data = dict(target=aux_work['target'], hash=aux_work['hash'], height=aux_work['height'], index=mm_hashes.index(aux_work['hash']), type=aux_work['type'], hashes=mm_hashes) merged_data[aux_work['type']] = data else: merged_data = {} mm_data = None self.logger.info("Generating new block template with {} trans. " "Diff {:,.4f}. Subsidy {:,.2f}. Height {:,}. " "Merged chains: {}" .format(len(self._last_gbt['transactions']), bits_to_difficulty(self._last_gbt['bits']), self._last_gbt['coinbasevalue'] / 100000000.0, self._last_gbt['height'], ', '.join(merged_data.keys()))) # here we recalculate the current merkle branch and partial # coinbases for passing to the mining clients coinbase = Transaction() coinbase.version = 2 # create a coinbase input with encoded height and padding for the # extranonces so script length is accurate extranonce_length = (self.config['extranonce_size'] + self.config['extranonce_serv_size']) coinbase.inputs.append( Input.coinbase(self._last_gbt['height'], addtl_push=[mm_data] if mm_data else [], extra_script_sig=b'\0' * extranonce_length)) # Darkcoin payee amount if self._last_gbt.get('payee', '') != '': payout = self._last_gbt['coinbasevalue'] / 5 self._last_gbt['coinbasevalue'] -= payout coinbase.outputs.append( Output.to_address(payout, self._last_gbt['payee'])) self.logger.info("Paying out masternode at addr {}. Payout {}. Blockval reduced to {}" .format(self._last_gbt['payee'], payout, self._last_gbt['coinbasevalue'])) # simple output to the proper address and value coinbase.outputs.append( Output.to_address(self._last_gbt['coinbasevalue'], self.config['pool_address'])) job_id = hexlify(struct.pack(str("I"), self._job_counter)) bt_obj = BlockTemplate.from_gbt(self._last_gbt, coinbase, extranonce_length, [Transaction(unhexlify(t['data']), fees=t['fee']) for t in self._last_gbt['transactions']]) # add in our merged mining data if mm_data: hashes = [bitcoin_data.hash256(tx.raw) for tx in bt_obj.transactions] bt_obj.merkle_link = bitcoin_data.calculate_merkle_link([None] + hashes, 0) bt_obj.merged_data = merged_data bt_obj.job_id = job_id bt_obj.diff1 = self.config['diff1'] bt_obj.algo = self.config['algo'] bt_obj.pow_block_hash = self.config['pow_block_hash'] bt_obj.block_height = self._last_gbt['height'] bt_obj.acc_shares = set() if push: if flush: self.logger.info("New work announced! Wiping previous jobs...") self.jobs.clear() self.latest_job = None else: self.logger.info("New work announced!") self._job_counter += 1 self.jobs[job_id] = bt_obj self.latest_job = job_id if push: t = time.time() bt_obj.stratum_string() for idx, client in viewitems(self.stratum_manager.clients): try: if client.authenticated: client._push(bt_obj, flush=flush) except AttributeError: pass self.logger.info("New job enqueued for transmission to {} users in {}" .format(len(self.stratum_manager.clients), time_format(time.time() - t))) if flush: self.server['work_restarts'].incr() self.server['work_pushes'].incr() self.server['new_jobs'].incr() if new_block: hex_bits = hexlify(bt_obj.bits) self.current_net['difficulty'] = bits_to_difficulty(hex_bits) self.current_net['subsidy'] = bt_obj.total_value self.current_net['height'] = bt_obj.block_height - 1 self.current_net['prev_hash'] = bt_obj.hashprev_be_hex self.current_net['transactions'] = len(bt_obj.transactions)
def generate_job(self, push=False, flush=False, new_block=False): """ Creates a new job for miners to work on. Push will trigger an event that sends new work but doesn't force a restart. If flush is true a job restart will be triggered. """ # aux monitors will often call this early when not needed at startup if self.last_gbt is None: return merged_work = self.net_state['merged_work'] if self.net_state['merged_work']: tree, size = bitcoin_data.make_auxpow_tree(merged_work) mm_hashes = [ merged_work.get(tree.get(i), dict(hash=0))['hash'] for i in xrange(size) ] mm_data = '\xfa\xbemm' mm_data += bitcoin_data.aux_pow_coinbase_type.pack( dict( merkle_root=bitcoin_data.merkle_hash(mm_hashes), size=size, nonce=0, )) mm_later = [(aux_work, mm_hashes.index(aux_work['hash']), mm_hashes) for chain_id, aux_work in merged_work.iteritems()] else: mm_later = [] mm_data = None # here we recalculate the current merkle branch and partial # coinbases for passing to the mining clients coinbase = Transaction() coinbase.version = 2 # create a coinbase input with encoded height and padding for the # extranonces so script length is accurate extranonce_length = (self.config['extranonce_size'] + self.config['extranonce_serv_size']) coinbase.inputs.append( Input.coinbase(self.last_gbt['height'], addtl_push=[mm_data] if mm_data else [], extra_script_sig=b'\0' * extranonce_length)) # simple output to the proper address and value coinbase.outputs.append( Output.to_address(self.last_gbt['coinbasevalue'], self.config['pool_address'])) job_id = hexlify(struct.pack(str("I"), self.net_state['job_counter'])) logger.info( "Generating new block template with {} trans. Diff {}. Subsidy {}." .format(len(self.last_gbt['transactions']), bits_to_difficulty(self.last_gbt['bits']), self.last_gbt['coinbasevalue'])) bt_obj = BlockTemplate.from_gbt( self.last_gbt, coinbase, extranonce_length, [ Transaction(unhexlify(t['data']), fees=t['fee']) for t in self.last_gbt['transactions'] ]) bt_obj.mm_later = copy(mm_later) hashes = [bitcoin_data.hash256(tx.raw) for tx in bt_obj.transactions] bt_obj.merkle_link = bitcoin_data.calculate_merkle_link([None] + hashes, 0) bt_obj.job_id = job_id bt_obj.block_height = self.last_gbt['height'] bt_obj.acc_shares = set() if push: if flush: logger.info("New work announced! Wiping previous jobs...") self.net_state['jobs'].clear() self.net_state['latest_job'] = None else: logger.info("New work announced!") self.net_state['job_counter'] += 1 self.net_state['jobs'][job_id] = bt_obj self.net_state['latest_job'] = job_id if push: for idx, client in viewitems(self.stratum_clients): try: if flush: client.new_block_event.set() else: client.new_work_event.set() except AttributeError: pass if new_block: hex_bits = hexlify(bt_obj.bits) self.net_state['work']['difficulty'] = bits_to_difficulty(hex_bits) if self.config['send_new_block']: self.celery.send_task_pp('new_block', bt_obj.block_height, hex_bits, bt_obj.total_value)
def submit_job(self, data): """ Handles recieving work submission and checking that it is valid , if it meets network diff, etc. Sends reply to stratum client. """ params = data['params'] # [worker_name, job_id, extranonce2, ntime, nonce] # ["slush.miner1", "bf", "00000001", "504e86ed", "b2957c02"] self.logger.debug( "Recieved work submit:\n\tworker_name: {0}\n\t" "job_id: {1}\n\textranonce2: {2}\n\t" "ntime: {3}\n\tnonce: {4} ({int_nonce})" .format( *params, int_nonce=struct.unpack(str("<L"), unhexlify(params[4])))) try: difficulty, jobid = self.job_mapper[data['params'][1]] except KeyError: # since we can't identify the diff we just have to assume it's # current diff self.send_error(self.STALE_SHARE) self.server_state['reject_stale'].incr(self.difficulty) return self.STALE_SHARE, self.difficulty # lookup the job in the global job dictionary. If it's gone from here # then a new block was announced which wiped it try: job = self.net_state['jobs'][jobid] except KeyError: self.send_error(self.STALE_SHARE) self.server_state['reject_stale'].incr(difficulty) return self.STALE_SHARE, difficulty # assemble a complete block header bytestring header = job.block_header( nonce=params[4], extra1=self.id, extra2=params[2], ntime=params[3]) # Check a submitted share against previous shares to eliminate # duplicates share = (self.id, params[2], params[4], params[3]) if share in job.acc_shares: self.logger.info("Duplicate share rejected from worker {}.{}!" .format(self.address, self.worker)) self.send_error(self.DUP_SHARE) self.server_state['reject_dup'].incr(difficulty) return self.DUP_SHARE, difficulty job_target = target_from_diff(difficulty, self.config['diff1']) hash_int = self.config['pow_func'](header) if hash_int >= job_target: self.logger.info("Low diff share rejected from worker {}.{}!" .format(self.address, self.worker)) self.send_error(self.LOW_DIFF) self.server_state['reject_low'].incr(difficulty) return self.LOW_DIFF, difficulty # we want to send an ack ASAP, so do it here self.send_success(self.msg_id) self.logger.debug("Valid share accepted from worker {}.{}!" .format(self.address, self.worker)) # Add the share to the accepted set to check for dups job.acc_shares.add(share) self.server_state['shares'].incr(difficulty) header_hash = sha256(sha256(header).digest()).digest()[::-1] header_hash_int = bitcoin_data.hash256(header) def check_merged_block(mm_later): aux_work, index, hashes = mm_later if hash_int <= aux_work['target']: monitor = aux_work['monitor'] self.server_state['aux_state'][monitor.name]['solves'] += 1 self.logger.log(36, "New {} Aux Block identified!".format(monitor.name)) aux_block = ( pack.IntType(256, 'big').pack(aux_work['hash']).encode('hex'), bitcoin_data.aux_pow_type.pack(dict( merkle_tx=dict( tx=bitcoin_data.tx_type.unpack(job.coinbase.raw), block_hash=header_hash_int, merkle_link=job.merkle_link, ), merkle_link=bitcoin_data.calculate_merkle_link(hashes, index), parent_block_header=bitcoin_data.block_header_type.unpack(header), )).encode('hex'), ) retries = 0 while retries < 5: retries += 1 new_height = self.server_state['aux_state'][monitor.name]['height'] + 1 try: res = aux_work['merged_proxy'].getauxblock(*aux_block) except (CoinRPCException, socket.error, ValueError) as e: self.logger.error("{} Aux block failed to submit to the server {}!" .format(monitor.name), exc_info=True) self.logger.error(getattr(e, 'error')) if res is True: self.logger.info("NEW {} Aux BLOCK ACCEPTED!!!".format(monitor.name)) self.server_state['aux_state'][monitor.name]['block_solve'] = int(time()) self.server_state['aux_state'][monitor.name]['accepts'] += 1 self.server_state['aux_state'][monitor.name]['recent_blocks'].append( dict(height=new_height, timestamp=int(time()))) if monitor.send: self.logger.info("Submitting {} new block to celery".format(monitor.name)) try: hsh = aux_work['merged_proxy'].getblockhash(new_height) except Exception: self.logger.info("", exc_info=True) hsh = '' try: block = aux_work['merged_proxy'].getblock(hsh) except Exception: self.logger.info("", exc_info=True) try: trans = aux_work['merged_proxy'].gettransaction(block['tx'][0]) amount = trans['details'][0]['amount'] except Exception: self.logger.info("", exc_info=True) amount = -1 self.celery.send_task_pp( 'add_block', self.address, new_height, int(amount * 100000000), -1, "%0.6X" % bitcoin_data.FloatingInteger.from_target_upper_bound(aux_work['target']).bits, hsh, merged=monitor.celery_id) break # break retry loop if success else: self.logger.error( "{} Aux Block failed to submit to the server, " "server returned {}!".format(monitor.name, res), exc_info=True) sleep(1) else: self.server_state['aux_state'][monitor.name]['rejects'] += 1 for mm in job.mm_later: spawn(check_merged_block, mm) # valid network hash? if hash_int > job.bits_target: return self.VALID_SHARE, difficulty block = hexlify(job.submit_serial(header)) def submit_block(conn): retries = 0 while retries < 5: retries += 1 res = "failed" try: res = conn.getblocktemplate({'mode': 'submit', 'data': block}) except (CoinRPCException, socket.error, ValueError) as e: self.logger.info("Block failed to submit to the server {} with submitblock!" .format(conn.name)) if getattr(e, 'error', {}).get('code', 0) != -8: self.logger.error(getattr(e, 'error'), exc_info=True) try: res = conn.submitblock(block) except (CoinRPCException, socket.error, ValueError) as e: self.logger.error("Block failed to submit to the server {}!" .format(conn.name), exc_info=True) self.logger.error(getattr(e, 'error')) if res is None: self.net_state['work']['accepts'] += 1 self.net_state['work']['recent_blocks'].append( dict(height=job.block_height, timestamp=int(time()))) hash_hex = hexlify(header_hash) self.celery.send_task_pp( 'add_block', self.address, job.block_height, job.total_value, job.fee_total, hexlify(job.bits), hash_hex) self.logger.info("NEW BLOCK ACCEPTED by {}!!!" .format(conn.name)) self.server_state['block_solve'] = int(time()) break # break retry loop if success else: self.logger.error( "Block failed to submit to the server {}, " "server returned {}!".format(conn.name, res), exc_info=True) sleep(1) self.logger.info("Retry {} for connection {}".format(retries, conn.name)) else: self.net_state['work']['rejects'] += 1 for conn in self.net_state['live_connections']: # spawn a new greenlet for each submission to do them all async. # lower orphan chance spawn(submit_block, conn) try: self.logger.log(35, "Valid network block identified!") self.logger.info("New block at height %i" % self.net_state['work']['height']) self.logger.info("Block coinbase hash %s" % job.coinbase.lehexhash) self.logger.log(35, "New block hex dump:\n{}".format(block)) self.logger.log(35, "Coinbase: {}".format(str(job.coinbase.to_dict()))) for trans in job.transactions: self.logger.log(35, str(trans.to_dict())) except Exception: # because I'm paranoid... self.logger.error("Unexcpected exception in block logging!", exc_info=True) return self.BLOCK_FOUND, difficulty
def submit_job(self, data): """ Handles recieving work submission and checking that it is valid , if it meets network diff, etc. Sends reply to stratum client. """ params = data['params'] # [worker_name, job_id, extranonce2, ntime, nonce] # ["slush.miner1", "bf", "00000001", "504e86ed", "b2957c02"] self.logger.debug( "Recieved work submit:\n\tworker_name: {0}\n\t" "job_id: {1}\n\textranonce2: {2}\n\t" "ntime: {3}\n\tnonce: {4} ({int_nonce})" .format( *params, int_nonce=struct.unpack(str("<L"), unhexlify(params[4])))) try: difficulty, jobid = self.job_mapper[data['params'][1]] except KeyError: # since we can't identify the diff we just have to assume it's # current diff self.send_error(self.STALE_SHARE) self.server_state['reject_stale'].incr(self.difficulty) return self.STALE_SHARE, self.difficulty # lookup the job in the global job dictionary. If it's gone from here # then a new block was announced which wiped it try: job = self.net_state['jobs'][jobid] except KeyError: self.send_error(self.STALE_SHARE) self.server_state['reject_stale'].incr(difficulty) return self.STALE_SHARE, difficulty # assemble a complete block header bytestring header = job.block_header( nonce=params[4], extra1=self.id, extra2=params[2], ntime=params[3]) # Check a submitted share against previous shares to eliminate # duplicates share = (self.id, params[2], params[4], params[3]) if share in job.acc_shares: self.logger.info("Duplicate share rejected from worker {}.{}!" .format(self.address, self.worker)) self.send_error(self.DUP_SHARE) self.server_state['reject_dup'].incr(difficulty) return self.DUP_SHARE, difficulty job_target = target_from_diff(difficulty, self.config['diff1']) hash_int = self.config['pow_func'](header) if hash_int >= job_target: self.logger.info("Low diff share rejected from worker {}.{}!" .format(self.address, self.worker)) self.send_error(self.LOW_DIFF) self.server_state['reject_low'].incr(difficulty) return self.LOW_DIFF, difficulty # we want to send an ack ASAP, so do it here self.send_success(self.msg_id) self.logger.debug("Valid share accepted from worker {}.{}!" .format(self.address, self.worker)) # Add the share to the accepted set to check for dups job.acc_shares.add(share) self.server_state['shares'].incr(difficulty) header_hash = sha256(sha256(header).digest()).digest()[::-1] header_hash_int = bitcoin_data.hash256(header) def check_merged_block(mm_later): aux_work, index, hashes = mm_later if hash_int <= aux_work['target']: monitor = aux_work['monitor'] self.server_state['aux_state'][monitor.name]['solves'] += 1 self.logger.log(36, "New {} Aux Block identified!".format(monitor.name)) aux_block = ( pack.IntType(256, 'big').pack(aux_work['hash']).encode('hex'), bitcoin_data.aux_pow_type.pack(dict( merkle_tx=dict( tx=bitcoin_data.tx_type.unpack(job.coinbase.raw), block_hash=header_hash_int, merkle_link=job.merkle_link, ), merkle_link=bitcoin_data.calculate_merkle_link(hashes, index), parent_block_header=bitcoin_data.block_header_type.unpack(header), )).encode('hex'), ) retries = 0 while retries < 5: retries += 1 new_height = self.server_state['aux_state'][monitor.name]['height'] + 1 try: res = aux_work['merged_proxy'].getauxblock(*aux_block) except (CoinRPCException, socket.error, ValueError) as e: self.logger.error("{} Aux block failed to submit to the server {}!" .format(monitor.name), exc_info=True) self.logger.error(getattr(e, 'error')) if res is True: self.logger.info("NEW {} Aux BLOCK ACCEPTED!!!".format(monitor.name)) self.server_state['aux_state'][monitor.name]['block_solve'] = int(time()) if monitor.send: self.logger.info("Submitting {} new block to celery".format(monitor.name)) try: hsh = aux_work['merged_proxy'].getblockhash(new_height) except Exception: self.logger.info("", exc_info=True) hsh = '' try: block = aux_work['merged_proxy'].getblock(hsh) except Exception: self.logger.info("", exc_info=True) try: trans = aux_work['merged_proxy'].gettransaction(block['tx'][0]) amount = trans['details'][0]['amount'] except Exception: self.logger.info("", exc_info=True) amount = -1 self.celery.send_task_pp( 'add_block', self.address, new_height, int(amount * 100000000), -1, "%0.6X" % bitcoin_data.FloatingInteger.from_target_upper_bound(aux_work['target']).bits, hsh, merged=monitor.celery_id) break # break retry loop if success else: self.logger.error( "{} Aux Block failed to submit to the server, " "server returned {}!".format(monitor.name, res), exc_info=True) sleep(1) for mm in job.mm_later: spawn(check_merged_block, mm) # valid network hash? if hash_int > job.bits_target: return self.VALID_SHARE, difficulty try: self.logger.log(35, "Valid network block identified!") self.logger.info("New block at height %i" % self.net_state['current_height']) self.logger.info("Block coinbase hash %s" % job.coinbase.lehexhash) block = hexlify(job.submit_serial(header)) self.logger.log(35, "New block hex dump:\n{}".format(block)) self.logger.log(35, "Coinbase: {}".format(str(job.coinbase.to_dict()))) for trans in job.transactions: self.logger.log(35, str(trans.to_dict())) except Exception: # because I'm paranoid... self.logger.error("Unexcpected exception in block logging!", exc_info=True) def submit_block(conn): retries = 0 while retries < 5: retries += 1 res = "failed" try: res = conn.getblocktemplate({'mode': 'submit', 'data': block}) except (CoinRPCException, socket.error, ValueError) as e: self.logger.info("Block failed to submit to the server {} with submitblock!" .format(conn.name)) if getattr(e, 'error', {}).get('code', 0) != -8: self.logger.error(getattr(e, 'error'), exc_info=True) try: res = conn.submitblock(block) except (CoinRPCException, socket.error, ValueError) as e: self.logger.error("Block failed to submit to the server {}!" .format(conn.name), exc_info=True) self.logger.error(getattr(e, 'error')) if res is None: hash_hex = hexlify(header_hash) self.celery.send_task_pp( 'add_block', self.address, self.net_state['current_height'] + 1, job.total_value, job.fee_total, hexlify(job.bits), hash_hex) self.logger.info("NEW BLOCK ACCEPTED by {}!!!" .format(conn.name)) self.server_state['block_solve'] = int(time()) break # break retry loop if success else: self.logger.error( "Block failed to submit to the server {}, " "server returned {}!".format(conn.name, res), exc_info=True) sleep(1) self.logger.info("Retry {} for connection {}".format(retries, conn.name)) for conn in self.net_state['live_connections']: # spawn a new greenlet for each submission to do them all async. # lower orphan chance spawn(submit_block, conn) return self.BLOCK_FOUND, difficulty