def __init__(self, block_template_class, coinbaser, bitcoin_rpc, aux_rpc, instance_id, on_template_callback, on_block_callback): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() self.coinbaser = coinbaser self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.update_in_progress = False self.last_update = 0 self.last_height = None self.aux_rpc = aux_rpc self.aux_update_in_progress = False self.aux_new_block = False self.aux_last_update = 0 self.aux_update_counter = 0 self.aux_data = [] # Create first block template on startup self.update_auxs()
def __init__(self, block_template_class, coinbaser, bitcoin_rpc, instance_id, on_template_callback, on_block_callback, rootstock_rpc=None): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() self.coinbaser = coinbaser self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.rootstock_rpc = rootstock_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.update_in_progress = False self.last_update = None self.rsk_last_update = 0 self.rsk_update_in_progress = False self.last_data = dict() self.last_rsk_hash = "" # Create first block template on startup self.update_block() if self.rootstock_rpc is not None: self.rsk_timeout_counter = 0
def __init__(self, block_template_class, coinbaser, bitcoin_rpc, mm_rpc, instance_id, on_template_callback, on_block_callback): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() log.debug("Got to Template Registry") self.coinbaser = coinbaser self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.mm_rpc = mm_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.update_in_progress = False self.update_mm_in_progress = False self.last_update = None self.last_update_mm = None self.mm_hash = "" self.mm_script = "" self.mm_target = None self.last_height = None if settings.COINDAEMON_ALGO == 'scrypt': self.algo = 1 else: self.algo = 0 # Create first block template on startup self.update_block() self.update_mm_block()
def __init__(self, block_template_class, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() self.coinbasers_value = [] for address, percent in settings.BITCOIN_ADDRESSES.iteritems(): coinbaser = SimpleCoinbaser(address, bitcoin_rpc) self.coinbasers_value.append((coinbaser, percent)) self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.last_update = None
def __init__(self, block_template_class, coinbaser, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() log.debug("Got to Template Registry") self.coinbaser = coinbaser self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.update_in_progress = False self.last_update = None # Create first block template on startup self.update_block()
def __init__(self, block_template_class, coinbaser, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): self.current_prevhash = '' self.jobs = weakref.WeakValueDictionary() self.minimal_job_id = 0 self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() self.coinbaser = coinbaser self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self._last_template = None self.update_in_progress = False self.last_update = None # Create first block template on startup self.update_block()
def __init__(self, template_generator, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): log.debug("Got to Template Registry") self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.template_generator = template_generator self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = template_generator.get_extranonce_size( ) - self.extranonce_counter.get_size() self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_template = None self.update_in_progress = False self.GBT_RPC_ATTEMPT = None self.last_block_update_start_time = None # Create first block template on startup self.update_block()
def __init__(self, template_generator, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): log.debug("Got to Template Registry") self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.template_generator = template_generator self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = template_generator.get_extranonce_size() - self.extranonce_counter.get_size() self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_template = None self.update_in_progress = False self.GBT_RPC_ATTEMPT = None self.last_block_update_start_time = None # Create first block template on startup self.update_block()
class TemplateRegistry(object): '''Implements the main logic of the pool. Keep track on valid block templates, provide internal interface for stratum service and implements block validation and submits.''' def __init__(self, block_template_class, coinbaser, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() log.debug("Got to Template Registry") self.coinbaser = coinbaser self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.update_in_progress = False self.last_update = None # Create first block template on startup self.update_block() def get_new_extranonce1(self): '''Generates unique extranonce1 (e.g. for newly subscribed connection.''' return self.extranonce_counter.get_new_bin() def get_last_broadcast_args(self): '''Returns arguments for mining.notify from last known template.''' return self.last_block.broadcast_args def add_template(self, block, block_height): '''Adds new template to the registry. It also clean up templates which should not be used anymore.''' prevhash = block.prevhash_hex if prevhash in self.prevhashes.keys(): new_block = False else: new_block = True self.prevhashes[prevhash] = [] # Blocks sorted by prevhash, so it's easy to drop # them on blockchain update self.prevhashes[prevhash].append(block) # Weak reference for fast lookup using job_id self.jobs[block.job_id] = block # Use this template for every new request self.last_block = block # Drop templates of obsolete blocks for ph in self.prevhashes.keys(): if ph != prevhash: del self.prevhashes[ph] if new_block: # Tell the system about new block # It is mostly important for share manager self.on_block_callback(prevhash, block_height) # Everything is ready, let's broadcast jobs! self.on_template_callback(new_block) def update_block(self): '''Registry calls the getblocktemplate() RPC and build new block template.''' if self.update_in_progress: # Block has been already detected return self.update_in_progress = True self.last_update = Interfaces.timestamper.time() d = self.bitcoin_rpc.getblocktemplate() d.addCallback(self._update_block) d.addErrback(self._update_block_failed) def _update_block_failed(self, failure): log.error(str(failure)) self.update_in_progress = False def _update_block(self, data): start = Interfaces.timestamper.time() template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id()) log.info(template.fill_from_rpc(data)) self.add_template(template, data['height']) log.info("[---- %s -----]Update finished, %.03f sec, %d txes" % \ (template.job_id, Interfaces.timestamper.time() - start, len(template.vtx))) self.update_in_progress = False return data def diff_to_target(self, difficulty): '''Converts difficulty to target''' diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000 if settings.CUSTOM_DIFF1 != None: diff1 = settings.CUSTOM_DIFF1 return diff1 / difficulty def bits_to_diff(self, nbits): diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000 return diff1 / util.uint256_from_compact(nbits) def get_job(self, job_id, worker_name, ip=False): '''For given job_id returns BlockTemplate instance or None''' try: j = self.jobs[job_id] except: log.info("Job id '%s' not found, worker_name: '%s'" % (job_id, worker_name)) if ip: log.info("Worker submited invalid Job id: IP %s", str(ip)) return None # Now we have to check if job is still valid. # Unfortunately weak references are not bulletproof and # old reference can be found until next run of garbage collector. if j.prevhash_hex not in self.prevhashes: log.info("Prevhash of job '%s' is unknown" % job_id) return None if j not in self.prevhashes[j.prevhash_hex]: log.info("Job %s is unknown" % job_id) return None return j def submit_share(self, job_id, worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, difficulty, ip, version_rolling): '''Check parameters and finalize block template. If it leads to valid block candidate, asynchronously submits the block back to the bitcoin network. - extranonce1_bin is binary. No checks performed, it should be from session data - job_id, extranonce2, ntime, nonce - in hex form sent by the client - difficulty - decimal number from session - submitblock_callback - reference to method which receive result of submitblock() - difficulty is checked to see if its lower than the vardiff minimum target or pool target from conf/config.py and if it is the share is rejected due to it not meeting the requirements for a share ''' if difficulty < settings.VDIFF_MIN_TARGET or difficulty < settings.POOL_TARGET: # Share Diff Should never be 0 raise SubmitException( "Diff is %s Share Rejected Reporting to Admin" % (difficulty)) # normalize the case to prevent duplication of valid shares by the client ntime = ntime.lower() nonce = nonce.lower() extranonce2 = extranonce2.lower() # Check for job job = self.get_job(job_id, worker_name, ip) if job == None: raise SubmitException("Job '%s' not found" % job_id) # Check if ntime looks correct if len(ntime) != 8: raise SubmitException("Incorrect size of ntime. Expected 8 chars") if not job.check_ntime(int(ntime, 16)): raise SubmitException("Ntime out of range") # Check nonce if len(nonce) != 8: raise SubmitException("Incorrect size of nonce. Expected 8 chars") # 0. Some sugar extranonce2_bin = binascii.unhexlify(extranonce2) ntime_bin = binascii.unhexlify(ntime) nonce_bin = binascii.unhexlify(nonce) # Check for duplicated submit if not job.register_submit(extranonce1_bin, extranonce2_bin, ntime_bin, nonce_bin): log.debug("Duplicate from %s, (%s %s %s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce)) raise SubmitException("Duplicate share") # Now let's do the hard work! # --------------------------- # 1. Build coinbase coinbase_bin = job.serialize_coinbase(extranonce1_bin, extranonce2_bin) coinbase_hash = util.doublesha(coinbase_bin) # 2. Calculate merkle root merkle_root_bin = job.merkletree.withFirst(coinbase_hash) merkle_root_int = util.uint256_from_str(merkle_root_bin) # 3. Serialize header with given merkle, ntime and nonce header_bin = job.serialize_header(merkle_root_int, ntime_bin, nonce_bin, version_rolling) # 4. Reverse header and compare it with target of the user hash_bin = util.doublesha(''.join( [header_bin[i * 4:i * 4 + 4][::-1] for i in range(0, 20)])) hash_int = util.uint256_from_str(hash_bin) scrypt_hash_hex = "%064x" % hash_int header_hex = binascii.hexlify(header_bin) if settings.CUSTOM_HEADER != None: header_hex = header_hex + settings.CUSTOM_HEADER target_user = self.diff_to_target(difficulty) if hash_int > target_user: raise SubmitException("Share is above target %064x > %064x" % (hash_int, target_user)) # Algebra tells us the diff_to_target is the same as hash_to_diff share_diff = int(self.diff_to_target(hash_int)) # 5. Compare hash with target of the network if hash_int <= job.target: # Yay! It is block candidate! log.info("We found a block candidate! %s" % scrypt_hash_hex) block_hash_bin = util.doublesha(''.join( [header_bin[i * 4:i * 4 + 4][::-1] for i in range(0, 20)])) block_hash_hex = block_hash_bin[::-1].encode('hex_codec') # 6. Finalize and serialize block object job.finalize(merkle_root_int, extranonce1_bin, extranonce2_bin, int(ntime, 16), int(nonce, 16)) if not job.is_valid(): # Should not happen log.exception( "FINAL JOB VALIDATION FAILED!(Try enabling/disabling tx messages)" ) # 7. Submit block to the network serialized = binascii.hexlify(job.serialize()) on_submit = self.bitcoin_rpc.submitblock(serialized, block_hash_hex, scrypt_hash_hex) if on_submit: self.update_block() if settings.SOLUTION_BLOCK_HASH: return (header_hex, block_hash_hex, share_diff, on_submit) else: return (header_hex, scrypt_hash_hex, share_diff, on_submit) if settings.SOLUTION_BLOCK_HASH: block_hash_bin = util.doublesha(''.join( [header_bin[i * 4:i * 4 + 4][::-1] for i in range(0, 20)])) block_hash_hex = block_hash_bin[::-1].encode('hex_codec') return (header_hex, block_hash_hex, share_diff, None) else: return (header_hex, scrypt_hash_hex, share_diff, None)
class TemplateRegistry(object): '''Implements the main logic of the pool. Keep track on valid block templates, provide internal interface for stratum service and implements block validation and submits.''' def __init__(self, block_template_class, coinbaser, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() log.debug("Got to Template Registry") self.coinbaser = coinbaser self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.update_in_progress = False self.last_update = None # Create first block template on startup self.update_block() def get_new_extranonce1(self): '''Generates unique extranonce1 (e.g. for newly subscribed connection.''' log.debug("Getting Unique Extronance") return self.extranonce_counter.get_new_bin() def get_last_broadcast_args(self): '''Returns arguments for mining.notify from last known template.''' log.debug("Getting Laat Template") return self.last_block.broadcast_args def add_template(self, block,block_height): '''Adds new template to the registry. It also clean up templates which should not be used anymore.''' prevhash = block.prevhash_hex if prevhash in self.prevhashes.keys(): new_block = False else: new_block = True self.prevhashes[prevhash] = [] # Blocks sorted by prevhash, so it's easy to drop # them on blockchain update self.prevhashes[prevhash].append(block) # Weak reference for fast lookup using job_id self.jobs[block.job_id] = block # Use this template for every new request self.last_block = block # Drop templates of obsolete blocks for ph in self.prevhashes.keys(): if ph != prevhash: del self.prevhashes[ph] log.info("New template for %s" % prevhash) if new_block: # Tell the system about new block # It is mostly important for share manager self.on_block_callback(prevhash, block_height) # Everything is ready, let's broadcast jobs! self.on_template_callback(new_block) #from twisted.internet import reactor #reactor.callLater(10, self.on_block_callback, new_block) def update_block(self): '''Registry calls the getblocktemplate() RPC and build new block template.''' if self.update_in_progress: # Block has been already detected return self.update_in_progress = True self.last_update = Interfaces.timestamper.time() d = self.bitcoin_rpc.getblocktemplate() d.addCallback(self._update_block) d.addErrback(self._update_block_failed) def _update_block_failed(self, failure): log.error(str(failure)) self.update_in_progress = False def _update_block(self, data): start = Interfaces.timestamper.time() template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id()) log.info(template.fill_from_rpc(data)) self.add_template(template,data['height']) log.info("Update finished, %.03f sec, %d txes" % \ (Interfaces.timestamper.time() - start, len(template.vtx))) self.update_in_progress = False return data def diff_to_target(self, difficulty): '''Converts difficulty to target''' if settings.COINDAEMON_ALGO == 'scrypt' or 'scrypt-jane': diff1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000 elif settings.COINDAEMON_ALGO == 'quark': diff1 = 0x000000ffff000000000000000000000000000000000000000000000000000000 else: diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000 return diff1 / difficulty def get_job(self, job_id): '''For given job_id returns BlockTemplate instance or None''' try: j = self.jobs[job_id] except: log.info("Job id '%s' not found" % job_id) return None # Now we have to check if job is still valid. # Unfortunately weak references are not bulletproof and # old reference can be found until next run of garbage collector. if j.prevhash_hex not in self.prevhashes: log.info("Prevhash of job '%s' is unknown" % job_id) return None if j not in self.prevhashes[j.prevhash_hex]: log.info("Job %s is unknown" % job_id) return None return j def submit_share(self, job_id, worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, difficulty): '''Check parameters and finalize block template. If it leads to valid block candidate, asynchronously submits the block back to the bitcoin network. - extranonce1_bin is binary. No checks performed, it should be from session data - job_id, extranonce2, ntime, nonce - in hex form sent by the client - difficulty - decimal number from session, again no checks performed - submitblock_callback - reference to method which receive result of submitblock() ''' # Check if extranonce2 looks correctly. extranonce2 is in hex form... if len(extranonce2) != self.extranonce2_size * 2: raise SubmitException("Incorrect size of extranonce2. Expected %d chars" % (self.extranonce2_size*2)) # Check for job job = self.get_job(job_id) if job == None: raise SubmitException("Job '%s' not found" % job_id) # Check if ntime looks correct if len(ntime) != 8: raise SubmitException("Incorrect size of ntime. Expected 8 chars") if not job.check_ntime(int(ntime, 16)): raise SubmitException("Ntime out of range") # Check nonce if len(nonce) != 8: raise SubmitException("Incorrect size of nonce. Expected 8 chars") # Check for duplicated submit if not job.register_submit(extranonce1_bin, extranonce2, ntime, nonce): log.info("Duplicate from %s, (%s %s %s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce)) raise SubmitException("Duplicate share") # Now let's do the hard work! # --------------------------- # 0. Some sugar extranonce2_bin = binascii.unhexlify(extranonce2) ntime_bin = binascii.unhexlify(ntime) nonce_bin = binascii.unhexlify(nonce) # 1. Build coinbase coinbase_bin = job.serialize_coinbase(extranonce1_bin, extranonce2_bin) coinbase_hash = util.doublesha(coinbase_bin) # 2. Calculate merkle root merkle_root_bin = job.merkletree.withFirst(coinbase_hash) merkle_root_int = util.uint256_from_str(merkle_root_bin) # 3. Serialize header with given merkle, ntime and nonce header_bin = job.serialize_header(merkle_root_int, ntime_bin, nonce_bin) # 4. Reverse header and compare it with target of the user if settings.COINDAEMON_ALGO == 'scrypt': hash_bin = ltc_scrypt.getPoWHash(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) elif settings.COINDAEMON_ALGO == 'scrypt-jane': hash_bin = yac_scrypt.getPoWHash(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ]), int(ntime, 16)) elif settings.COINDAEMON_ALGO == 'quark': hash_bin = quark_hash.getPoWHash(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) else: hash_bin = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) hash_int = util.uint256_from_str(hash_bin) scrypt_hash_hex = "%064x" % hash_int header_hex = binascii.hexlify(header_bin) if settings.COINDAEMON_ALGO == 'scrypt' or settings.COINDAEMON_ALGO == 'scrypt-jane': header_hex = header_hex+"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" elif settings.COINDAEMON_ALGO == 'quark': header_hex = header_hex+"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" else: pass target_user = self.diff_to_target(difficulty) if hash_int > target_user: raise SubmitException("Share is above target") # Mostly for debugging purposes target_info = self.diff_to_target(100000) if hash_int <= target_info: log.info("Yay, share with diff above 100000") # Algebra tells us the diff_to_target is the same as hash_to_diff share_diff = int(self.diff_to_target(hash_int)) # 5. Compare hash with target of the network if hash_int <= job.target: # Yay! It is block candidate! log.info("We found a block candidate! %s" % scrypt_hash_hex) # Reverse the header and get the potential block hash (for scrypt only) #if settings.COINDAEMON_ALGO == 'scrypt' or settings.COINDAEMON_ALGO == 'sha256d': # if settings.COINDAEMON_Reward == 'POW': block_hash_bin = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) block_hash_hex = block_hash_bin[::-1].encode('hex_codec') #else: block_hash_hex = hash_bin[::-1].encode('hex_codec') #else: block_hash_hex = hash_bin[::-1].encode('hex_codec') # 6. Finalize and serialize block object job.finalize(merkle_root_int, extranonce1_bin, extranonce2_bin, int(ntime, 16), int(nonce, 16)) if not job.is_valid(): # Should not happen log.exception("FINAL JOB VALIDATION FAILED!(Try enabling/disabling tx messages)") # 7. Submit block to the network serialized = binascii.hexlify(job.serialize()) if settings.BLOCK_CHECK_SCRYPT_HASH: on_submit = self.bitcoin_rpc.submitblock(serialized, scrypt_hash_hex) else: on_submit = self.bitcoin_rpc.submitblock(serialized, block_hash_hex) if on_submit: self.update_block() if settings.SOLUTION_BLOCK_HASH: return (header_hex, block_hash_hex, share_diff, on_submit) else: return (header_hex, scrypt_hash_hex, share_diff, on_submit) if settings.SOLUTION_BLOCK_HASH: # Reverse the header and get the potential block hash (for scrypt only) only do this if we want to send in the block hash to the shares table block_hash_bin = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) block_hash_hex = block_hash_bin[::-1].encode('hex_codec') return (header_hex, block_hash_hex, share_diff, None) else: return (header_hex, scrypt_hash_hex, share_diff, None)
class TemplateRegistry(object): '''Implements the main logic of the pool. Keep track on valid block templates, provide internal interface for stratum service and implements block validation and submits.''' def __init__(self, block_template_class, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() self.coinbasers_value = [] for address, percent in settings.BITCOIN_ADDRESSES.iteritems(): coinbaser = SimpleCoinbaser(address, bitcoin_rpc) self.coinbasers_value.append((coinbaser, percent)) self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.last_update = None # Create first block template on startup # self.update_block() def get_new_extranonce1(self): '''Generates unique extranonce1 (e.g. for newly subscribed connection.''' return self.extranonce_counter.get_new_bin() def get_last_broadcast_args(self): '''Returns arguments for mining.notify from last known template.''' return self.last_block.broadcast_args def add_template(self, block): '''Adds new template to the registry. It also clean up templates which should not be used anymore.''' prevhash = block.prevhash_hex if prevhash in self.prevhashes.keys(): new_block = False else: new_block = True self.prevhashes[prevhash] = [] # Blocks sorted by prevhash, so it's easy to drop # them on blockchain update self.prevhashes[prevhash].append(block) # Weak reference for fast lookup using job_id self.jobs[block.job_id] = block # Use this template for every new request self.last_block = block # Drop templates of obsolete blocks for ph in self.prevhashes.keys(): if ph != prevhash: del self.prevhashes[ph] logger.log('debug', "New template for %s" % prevhash) if new_block: # Tell the system about new block # It is mostly important for share manager self.on_block_callback(prevhash) else: self.on_template_callback(new_block) def update_block(self): '''Registry calls the getblocktemplate() RPC and build new block template.''' self.last_update = time.time() data = self.bitcoin_rpc.getblocktemplate() start = time.time() template = self.block_template_class(self.coinbasers_value, JobIdGenerator.get_new_id()) template.fill_from_rpc(data) self.add_template(template) logger.log('debug', "Update finished, %.03f sec, %d txes" % \ (time.time() - start, len(template.vtx))) return data def diff_to_target(self, difficulty): '''Converts difficulty to target''' diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000 return diff1 / difficulty def get_job(self, job_id): '''For given job_id returns BlockTemplate instance or None''' try: j = self.jobs[job_id] except: logger.log('bad_client', "Job id '%s' not found, %s" % (job_id, self.jobs.keys())) return None # Now we have to check if job is still valid. # Unfortunately weak references are not bulletproof and # old reference can be found until next run of garbage collector. if j.prevhash_hex not in self.prevhashes: logger.log('bad_client', "Prevhash of job '%s' is unknown" % job_id) return None if j not in self.prevhashes[j.prevhash_hex]: logger.log('bad_client', "Job %s is unknown" % job_id) return None return j def submit_share(self, job_id, worker_name, extranonce1_bin, extranonce2, ntime, nonce, difficulty, payment_type): '''Check parameters and finalize block template. If it leads to valid block candidate, asynchronously submits the block back to the bitcoin network. - extranonce1_bin is binary. No checks performed, it should be from session data - job_id, extranonce2, ntime, nonce - in hex form sent by the client - difficulty - decimal number from session, again no checks performed - submitblock_callback - reference to method which receive result of submitblock() ''' # Check if extranonce2 looks correctly. extranonce2 is in hex form... if len(extranonce2) != self.extranonce2_size * 2: raise SubmitException("Incorrect size of extranonce2. Expected %d chars" % (self.extranonce2_size*2)) # Check for job job = self.get_job(job_id) if job == None: raise SubmitException("Job '%s' not found" % job_id) # Check if ntime looks correct if len(ntime) != 8: raise SubmitException("Incorrect size of ntime. Expected 8 chars") if not job.check_ntime(int(ntime, 16)): raise SubmitException("Ntime out of range") # Check nonce if len(nonce) != 8: raise SubmitException("Incorrect size of nonce. Expected 8 chars") # Check for duplicated submit if not job.register_submit(extranonce1_bin, extranonce2, ntime, nonce): logger.log('bad_client', worker_name, "Duplicate from %s, (%s %s %s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce)) raise SubmitException("Duplicate share") # Now let's do the hard work! # --------------------------- # 0. Some sugar extranonce2_bin = binascii.unhexlify(extranonce2) ntime_bin = binascii.unhexlify(ntime) nonce_bin = binascii.unhexlify(nonce) # 1. Build coinbase coinbase_bin = job.serialize_coinbase(extranonce1_bin, extranonce2_bin) coinbase_hash = util.doublesha(coinbase_bin) # 2. Calculate merkle root merkle_root_bin = job.merkletree.withFirst(coinbase_hash) merkle_root_int = util.uint256_from_str(merkle_root_bin) # 3. Serialize header with given merkle, ntime and nonce header_bin = job.serialize_header(merkle_root_int, ntime_bin, nonce_bin) # 4. Reverse header and compare it with target of the user hash_bin = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) hash_int = util.uint256_from_str(hash_bin) block_hash_hex = "%064x" % hash_int header_hex = binascii.hexlify(header_bin) target_user = self.diff_to_target(difficulty) #logger.log('share', worker_name, 'HASH %064x' % hash_int, 'TARGET %064x' % job.target, 'DIFF %d' % difficulty) if hash_int > target_user: logger.log('bad_client', 'Share is above target') raise SubmitException("Share is above target") if payment_type == 'PPS': logger.log('share', worker_name, 'HASH %064x' % hash_int, 'TARGET %064x' % job.target, 'DIFF %d' % difficulty) logger.log('pplns', worker_name, 'HASH %064x' % hash_int, 'DIFF %d' % difficulty, 'PAY 0') if payment_type == 'PPLNS': logger.log('pplns', worker_name, 'HASH %064x' % hash_int, 'DIFF %d' % difficulty, 'PAY 1') # 5. Compare hash with target of the network if hash_int <= job.target: # Yay! It is block candidate! logger.log('block', worker_name, block_hash_hex) # 6. Finalize and serialize block object job.finalize(merkle_root_int, extranonce1_bin, extranonce2_bin, int(ntime, 16), int(nonce, 16)) if not job.is_valid(): # Should not happen logger.log('error', "Final job validation failed!") # 7. Submit block to the network serialized = binascii.hexlify(job.serialize()) on_submit = self.bitcoin_rpc.submitblock(serialized) logger.log('submitblock', serialized) return (True, worker_name, block_hash_hex) return (False, worker_name, block_hash_hex)
class TemplateRegistry(object): '''Implements the main logic of the pool. Keep track on valid block templates, provide internal interface for stratum service and implements block validation and submits.''' def __init__(self, block_template_class, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() self.coinbasers_value = [] for address, percent in settings.BITCOIN_ADDRESSES.iteritems(): coinbaser = SimpleCoinbaser(address, bitcoin_rpc) self.coinbasers_value.append((coinbaser, percent)) self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.last_update = None # Create first block template on startup # self.update_block() def get_new_extranonce1(self): '''Generates unique extranonce1 (e.g. for newly subscribed connection.''' return self.extranonce_counter.get_new_bin() def get_last_broadcast_args(self): '''Returns arguments for mining.notify from last known template.''' return self.last_block.broadcast_args def add_template(self, block): '''Adds new template to the registry. It also clean up templates which should not be used anymore.''' prevhash = block.prevhash_hex if prevhash in self.prevhashes.keys(): new_block = False else: new_block = True self.prevhashes[prevhash] = [] # Blocks sorted by prevhash, so it's easy to drop # them on blockchain update self.prevhashes[prevhash].append(block) # Weak reference for fast lookup using job_id self.jobs[block.job_id] = block # Use this template for every new request self.last_block = block # Drop templates of obsolete blocks for ph in self.prevhashes.keys(): if ph != prevhash: del self.prevhashes[ph] logger.log('debug', "New template for %s" % prevhash) if new_block: # Tell the system about new block # It is mostly important for share manager self.on_block_callback(prevhash) else: self.on_template_callback(new_block) def update_block(self): '''Registry calls the getblocktemplate() RPC and build new block template.''' self.last_update = time.time() data = self.bitcoin_rpc.getblocktemplate() start = time.time() template = self.block_template_class(self.coinbasers_value, JobIdGenerator.get_new_id()) template.fill_from_rpc(data) self.add_template(template) logger.log('debug', "Update finished, %.03f sec, %d txes" % \ (time.time() - start, len(template.vtx))) return data def diff_to_target(self, difficulty): '''Converts difficulty to target''' diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000 return diff1 / difficulty def get_job(self, job_id): '''For given job_id returns BlockTemplate instance or None''' try: j = self.jobs[job_id] except: logger.log( 'bad_client', "Job id '%s' not found, %s" % (job_id, self.jobs.keys())) return None # Now we have to check if job is still valid. # Unfortunately weak references are not bulletproof and # old reference can be found until next run of garbage collector. if j.prevhash_hex not in self.prevhashes: logger.log('bad_client', "Prevhash of job '%s' is unknown" % job_id) return None if j not in self.prevhashes[j.prevhash_hex]: logger.log('bad_client', "Job %s is unknown" % job_id) return None return j def submit_share(self, job_id, worker_name, extranonce1_bin, extranonce2, ntime, nonce, difficulty, payment_type): '''Check parameters and finalize block template. If it leads to valid block candidate, asynchronously submits the block back to the bitcoin network. - extranonce1_bin is binary. No checks performed, it should be from session data - job_id, extranonce2, ntime, nonce - in hex form sent by the client - difficulty - decimal number from session, again no checks performed - submitblock_callback - reference to method which receive result of submitblock() ''' # Check if extranonce2 looks correctly. extranonce2 is in hex form... if len(extranonce2) != self.extranonce2_size * 2: raise SubmitException( "Incorrect size of extranonce2. Expected %d chars" % (self.extranonce2_size * 2)) # Check for job job = self.get_job(job_id) if job == None: raise SubmitException("Job '%s' not found" % job_id) # Check if ntime looks correct if len(ntime) != 8: raise SubmitException("Incorrect size of ntime. Expected 8 chars") if not job.check_ntime(int(ntime, 16)): raise SubmitException("Ntime out of range") # Check nonce if len(nonce) != 8: raise SubmitException("Incorrect size of nonce. Expected 8 chars") # Check for duplicated submit if not job.register_submit(extranonce1_bin, extranonce2, ntime, nonce): logger.log('bad_client', worker_name, "Duplicate from %s, (%s %s %s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce)) raise SubmitException("Duplicate share") # Now let's do the hard work! # --------------------------- # 0. Some sugar extranonce2_bin = binascii.unhexlify(extranonce2) ntime_bin = binascii.unhexlify(ntime) nonce_bin = binascii.unhexlify(nonce) # 1. Build coinbase coinbase_bin = job.serialize_coinbase(extranonce1_bin, extranonce2_bin) coinbase_hash = util.doublesha(coinbase_bin) # 2. Calculate merkle root merkle_root_bin = job.merkletree.withFirst(coinbase_hash) merkle_root_int = util.uint256_from_str(merkle_root_bin) # 3. Serialize header with given merkle, ntime and nonce header_bin = job.serialize_header(merkle_root_int, ntime_bin, nonce_bin) # 4. Reverse header and compare it with target of the user hash_bin = util.doublesha(''.join( [header_bin[i * 4:i * 4 + 4][::-1] for i in range(0, 20)])) hash_int = util.uint256_from_str(hash_bin) block_hash_hex = "%064x" % hash_int header_hex = binascii.hexlify(header_bin) target_user = self.diff_to_target(difficulty) #logger.log('share', worker_name, 'HASH %064x' % hash_int, 'TARGET %064x' % job.target, 'DIFF %d' % difficulty) if hash_int > target_user: logger.log('bad_client', 'Share is above target') raise SubmitException("Share is above target") if payment_type == 'PPS': logger.log('share', worker_name, 'HASH %064x' % hash_int, 'TARGET %064x' % job.target, 'DIFF %d' % difficulty) logger.log('pplns', worker_name, 'HASH %064x' % hash_int, 'DIFF %d' % difficulty, 'PAY 0') if payment_type == 'PPLNS': logger.log('pplns', worker_name, 'HASH %064x' % hash_int, 'DIFF %d' % difficulty, 'PAY 1') # 5. Compare hash with target of the network if hash_int <= job.target: # Yay! It is block candidate! logger.log('block', worker_name, block_hash_hex) # 6. Finalize and serialize block object job.finalize(merkle_root_int, extranonce1_bin, extranonce2_bin, int(ntime, 16), int(nonce, 16)) if not job.is_valid(): # Should not happen logger.log('error', "Final job validation failed!") # 7. Submit block to the network serialized = binascii.hexlify(job.serialize()) on_submit = self.bitcoin_rpc.submitblock(serialized) logger.log('submitblock', serialized) return (True, worker_name, block_hash_hex) return (False, worker_name, block_hash_hex)
class TemplateRegistry(object): '''Implements the main logic of the pool. Keep track on valid block templates, provide internal interface for stratum service and implements block validation and submits.''' def __init__(self, template_generator, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): log.debug("Got to Template Registry") self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.template_generator = template_generator self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = template_generator.get_extranonce_size( ) - self.extranonce_counter.get_size() self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_template = None self.update_in_progress = False self.GBT_RPC_ATTEMPT = None self.last_block_update_start_time = None # Create first block template on startup self.update_block() def get_new_extranonce1(self): '''Generates unique extranonce1 (e.g. for newly subscribed connection.''' log.debug("Getting Unique Extronance") return self.extranonce_counter.get_new_bin() def get_last_broadcast_args(self): '''Returns arguments for mining.notify from last known template.''' log.debug("Getting arguments needed for mining.notify") return self.last_template.broadcast_args def add_template(self, template, block_height): '''Adds new template to the registry. It also clean up templates which should not be used anymore.''' prevhash = template.prevhash_hex if prevhash in self.prevhashes.keys(): new_block = False else: new_block = True self.prevhashes[prevhash] = [] # Blocks sorted by prevhash, so it's easy to drop # them on blockchain update self.prevhashes[prevhash].append(template) # Weak reference for fast lookup using job_id self.jobs[template.job_id] = template # Use this template for every new request self.last_template = template # Drop templates of obsolete blocks for ph in self.prevhashes.keys(): if ph != prevhash: del self.prevhashes[ph] log.info("New template for %s" % prevhash) if new_block: # Tell the system about new block # It is mostly important for share manager self.on_block_callback(prevhash, block_height) # Everything is ready, let's broadcast jobs! self.on_template_callback(new_block) #from twisted.internet import reactor #reactor.callLater(10, self.on_block_callback, new_block) def update_block(self, force=False): '''Registry calls the getblocktemplate() RPC and build new block template.''' log.info("A block update has been requested.") if self.update_in_progress and force and not self.GBT_RPC_ATTEMPT is None: # Cancel current block update request (if any) log.warning("Forcing block update.") self.GBT_RPC_ATTEMPT.cancel() self.update_in_progress = False if self.update_in_progress: # Block has been already detected log.warning("Block update already in progress. Started at: %s" % str(self.last_block_update_start_time)) # It's possible for this process to get 'hung', lets see how long running_time = max( Interfaces.timestamper.time() - self.last_block_update_start_time, 0) log.info("Block update running for %i seconds" % running_time) # If it's been more than 30 seconds, then cancel it # But we don't run in this instance. if running_time >= 30: log.error( "Block update appears to be hung. Running for more than %i seconds. Canceling..." % running_time) self.GBT_RPC_ATTEMPT.cancel() self.update_in_progress = False return log.debug("Block update started.") self.update_in_progress = True self.last_block_update_start_time = Interfaces.timestamper.time() # Polls the upstream network daemon for new block template # This is done asyncronisly self.GBT_RPC_ATTEMPT = self.bitcoin_rpc.getblocktemplate() log.debug("Block template request sent") self.GBT_RPC_ATTEMPT.addCallback(self._update_block) self.GBT_RPC_ATTEMPT.addErrback(self._update_block_failed) def _update_block_failed(self, failure): # Runs when upstream 'getblocktemplate()' RPC call has failed try: log.error("Could not load block template: %s" % str(failure)) except: log.error("Could not load block template.") finally: self.update_in_progress = False def _update_block(self, data): # Runs when upstream 'getblocktemplate()' RPC call has completed with no error log.debug("Block template data recived: Creating new template...") # Generate a new template template = self.template_generator.new_template( Interfaces.timestamper, JobIdGenerator.get_new_id()) # Apply newly obtained template 'data' as recived from upstream network log.debug("Filling RPC Data") template.fill_from_rpc(data) # Add it the template registry log.debug("Adding to Registry") self.add_template(template, data['height']) # All done log.info("Block update finished, %.03f sec, %d txes" % (Interfaces.timestamper.time() - self.last_block_update_start_time, len(template.block.vtx))) self.update_in_progress = False ''' TODO: Investigate I don't think it is nessesary to return the raw block template data or anything at all from the '_update_block' function since it's called asyncronosly. Leaving it for now. ''' return data def diff_to_target(self, difficulty): '''Converts difficulty to target''' return util.get_diff_target(difficulty) def get_job(self, job_id, worker_name, ip=False): '''For given job_id returns BlockTemplate instance or None''' try: j = self.jobs[job_id] except: log.info("Job id '%s' not found, worker_name: '%s'" % (job_id, worker_name)) if ip: log.debug("Worker submited invalid Job id: IP %s", str(ip)) return None # Now we have to check if job is still valid. # Unfortunately weak references are not bulletproof and # old reference can be found until next run of garbage collector. if j.prevhash_hex not in self.prevhashes: log.debug("Prevhash of job '%s' is unknown" % job_id) return None if j not in self.prevhashes[j.prevhash_hex]: log.debug("Job %s is unknown" % job_id) return None return j def submit_share(self, job_id, worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, difficulty, ip=False): '''Check parameters and finalize block template. If it leads to valid block candidate, asynchronously submits the block back to the bitcoin network. - extranonce1_bin is binary. No checks performed, it should be from session data - job_id, extranonce2, ntime, nonce - in hex form sent by the client - difficulty - decimal number from session - submitblock_callback - reference to method which receive result of submitblock() - difficulty is checked to see if its lower than the vardiff minimum target or pool target from conf/config.py and if it is the share is rejected due to it not meeting the requirements for a share ''' log.debug("Session: %s" % session) # Share Difficulty should never be 0 or below if difficulty <= 0: log.exception( "Worker %s @ IP: %s seems to be submitting Fake Shares" % (worker_name, ip)) raise SubmitException( "Diff is %s Share Rejected Reporting to Admin" % (difficulty)) # Check if extranonce2 looks correctly. extranonce2 is in hex form... if len(extranonce2) != self.extranonce2_size * 2: raise SubmitException( "Incorrect size of extranonce2. Expected %d chars" % (self.extranonce2_size * 2)) # Check for job job = self.get_job(job_id, worker_name, ip) if job == None: if settings.REJECT_STALE_SHARES: # Reject stale share raise SubmitException("Job '%s' not found" % job_id) else: # Accept stale share but do not continue checking, return a bunch of nothingness log.info("Accepted Stale Share from %s, (%s %s %s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce)) return (None, None, None, None, None, None) # Check if ntime looks correct check_result, error_message = util.check_ntime(ntime) if not check_result: raise SubmitException(error_message) if not job.check_ntime(int(ntime, 16), getattr(settings, 'NTIME_AGE')): raise SubmitException("Ntime out of range") # Check nonce check_result, error_message = util.check_nonce(nonce) if not check_result: raise SubmitException(error_message) # Check for duplicated submit if not job.register_submit(extranonce1_bin, extranonce2, ntime, nonce): log.info("Duplicate from %s, (%s %s %s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce)) raise SubmitException("Duplicate share") # Now let's do the hard work! # --------------------------- # 0. Some sugar extranonce2_bin = binascii.unhexlify(extranonce2) ntime_bin = util.get_ntime_bin(ntime) nonce_bin = util.get_nonce_bin(nonce) target_user = self.diff_to_target(difficulty) target_info = self.diff_to_target(100000) # 1. Build coinbase coinbase_bin = job.serialize_coinbase(extranonce1_bin, extranonce2_bin) coinbase_hash = util.get_coinbase_hash(coinbase_bin) # 2. Calculate merkle root merkle_root_bin = job.merkletree.withFirst(coinbase_hash) merkle_root_int = util.uint256_from_str(merkle_root_bin) # 3. Serialize header with given merkle, ntime and nonce header_bin = job.serialize_header(merkle_root_int, ntime_bin, nonce_bin) # 4. Convert header into hex according to hash algorythim block_hash = util.get_hash_hex(header_bin, ntime, nonce) # 5a Compare it with target of the user check_result, message = util.check_header_target( block_hash['int'], target_user) if not check_result: log.debug( "Oops, somthing is wrong: target_user=%s, difficulty=%s, share_diff=%s" % (target_user, difficulty, int(self.diff_to_target(block_hash['int'])))) raise SubmitException("%s. Hash: %s" % (message, block_hash['hex'])) # Mostly for debugging purposes, just a celebratory message that's being carried over from older versions check_result, message = util.check_above_yay_target( block_hash['int'], target_info) if check_result: log.info(message) # Algebra tells us the diff_to_target is the same as hash_to_diff if settings.VDIFF_FLOAT: share_diff = float(self.diff_to_target(block_hash['int'])) else: share_diff = int(self.diff_to_target(block_hash['int'])) log.debug("share_diff: %s" % share_diff) log.debug("job.target: %s" % job.target) log.debug("block_hash_int: %s" % block_hash['int']) # 6. Compare hash with target of the network if util.is_block_candidate(block_hash['int'], job.target): # Yay! It is block candidate! log.info("We found a block candidate! for %i: %s | %s" % (job.height, block_hash['hex'], block_hash['check_hex'])) # 7. Finalize and serialize block object job.finalize(merkle_root_int, extranonce1_bin, extranonce2_bin, int(ntime, 16), int(nonce, 16)) if not job.is_valid(difficulty): # Should not happen log.exception( "FINAL JOB VALIDATION FAILED!(Try enabling/disabling tx messages)" ) # 8. Submit block to the network serialized = binascii.hexlify(job.serialize()) on_submit = self.bitcoin_rpc.submitblock(serialized, block_hash['check_hex'], block_hash['hex']) if on_submit: self.update_block() return (block_hash['header_hex'], block_hash['solution_hex'], share_diff, job.prevhash_hex, job.height, on_submit) # Not a potential Block return (block_hash['header_hex'], block_hash['solution_hex'], share_diff, job.prevhash_hex, job.height, None)
class TemplateRegistry(object): '''Implements the main logic of the pool. Keep track on valid block templates, provide internal interface for stratum service and implements block validation and submits.''' def __init__(self, block_template_class, coinbaser, bitcoin_rpc, instance_id, on_template_callback, on_block_callback, rootstock_rpc=None): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() self.coinbaser = coinbaser self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.rootstock_rpc = rootstock_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.update_in_progress = False self.last_update = None self.rsk_last_update = 0 self.rsk_update_in_progress = False self.last_data = dict() self.last_rsk_hash = "" # Create first block template on startup self.update_block() if self.rootstock_rpc is not None: self.rsk_timeout_counter = 0 def get_new_extranonce1(self): '''Generates unique extranonce1 (e.g. for newly subscribed connection.''' return self.extranonce_counter.get_new_bin() def get_last_broadcast_args(self): '''Returns arguments for mining.notify from last known template.''' return self.last_block.broadcast_args def add_template(self, block): '''Adds new template to the registry. It also clean up templates which should not be used anymore.''' prevhash = block.prevhash_hex if hasattr(block, 'rsk_flag'): rsk_is_old_block = self.rootstock_rpc.rsk_parent_hash == self.rootstock_rpc.rsk_last_parent_hash call = False if hasattr(settings, 'RSK_NOTIFY_POLICY') and hasattr( block, 'rsk_flag') and settings.RSK_NOTIFY_POLICY is not 0: if settings.RSK_NOTIFY_POLICY == 1: if self.rootstock_rpc.rsk_notify: call = True if settings.RSK_NOTIFY_POLICY == 2: if not rsk_is_old_block: call = True else: # Everything is ready, let's broadcast jobs! call = True if not call: return if prevhash in self.prevhashes.keys(): new_block = False else: new_block = True self.prevhashes[prevhash] = [] # Blocks sorted by prevhash, so it's easy to drop # them on blockchain update self.prevhashes[prevhash].append(block) # Weak reference for fast lookup using job_id self.jobs[block.job_id] = block # Use this template for every new request self.last_block = block # Drop templates of obsolete blocks for ph in self.prevhashes.keys(): if ph != prevhash: del self.prevhashes[ph] log.info("New template for %s" % prevhash) if new_block: # Tell the system about new block # It is mostly important for share manager self.on_block_callback(prevhash) self.on_template_callback(new_block) #from twisted.internet import reactor #reactor.callLater(10, self.on_block_callback, new_block) def _rsk_genheader(self, bhfmm): ''' Helper function for generating the rsk header in the expected format ''' return "RSKBLOCK:" + binascii.unhexlify(bhfmm[2:]) def _is_rsk_tag_in_coinbase(self, coinbase): rsk_tag_header = self._rsk_genheader( self.rootstock_rpc.rsk_blockhashformergedmining) return binascii.hexlify(rsk_tag_header) in binascii.hexlify(coinbase) def _rsk_fill_data(self, data): ''' Helper function for filling out the Bitcoin RPCs RSK data ''' start = Interfaces.timestamper.time() logid = util.id_generator() self.rootstock_rpc.rsk_notify = data['notify'] self.rootstock_rpc.rsk_blockhashformergedmining = data[ 'blockHashForMergedMining'] self.rootstock_rpc.rsk_last_header = self.rootstock_rpc.rsk_header self.rootstock_rpc.rsk_miner_fees = data['feesPaidToMiner'] self.rootstock_rpc.rsk_last_parent_hash = self.rootstock_rpc.rsk_parent_hash self.rootstock_rpc.rsk_parent_hash = data['parentBlockHash'] self.rootstock_rpc.rsk_header = self._rsk_genheader( self.rootstock_rpc.rsk_blockhashformergedmining) if settings.RSK_DEV_MODE: self.rootstock_rpc.rsk_target = int(settings.RSK_DEV_TARGET) else: self.rootstock_rpc.rsk_target = int(data['target'], 16) def update_block(self): '''Registry calls the getblocktemplate() RPC and build new block template.''' if self.update_in_progress: # Block has been already detected return self.update_in_progress = True self.last_update = Interfaces.timestamper.time() btc_block_received_start = Interfaces.timestamper.time() btc_block_received_id = util.id_generator() log.info( json.dumps({ "rsk": "[RSKLOG]", "tag": "[BTC_BLOCK_RECEIVED_START]", "start": btc_block_received_start, "elapsed": 0, "uuid": btc_block_received_id })) d = self.bitcoin_rpc.getblocktemplate() d.addCallback(self._update_block, btc_block_received_id) d.addErrback(self._update_block_failed) def _update_block_failed(self, failure): log.error(str(failure)) self.update_in_progress = False def _update_block(self, data, id): start = Interfaces.timestamper.time() self.last_data = data template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id()) data[ 'rsk_header'] = None if self.rootstock_rpc is None else self.rootstock_rpc.rsk_header template.fill_from_rpc(data) self.add_template(template) log.info("Update finished, %.03f sec, %d txes" % \ (Interfaces.timestamper.time() - start, len(template.vtx))) log.info( json.dumps({ "uuid": id, "rsk": "[RSKLOG]", "tag": "[BTC_BLOCK_RECEIVED_TEMPLATE]", "start": start, "elapsed": 0, "data": self.last_block.__dict__['broadcast_args'] })) self.update_in_progress = False return data def rsk_update_block(self): try: currentTime = Interfaces.timestamper.time() if self.rsk_update_in_progress and not ( currentTime - self.rsk_last_update > 3): return self.rsk_last_update = currentTime self.rsk_update_in_progress = True rsk_block_received_id = util.id_generator() log.info( json.dumps({ "rsk": "[RSKLOG]", "tag": "[RSK_BLOCK_RECEIVED_START]", "start": Interfaces.timestamper.time(), "elapsed": 0, "uuid": rsk_block_received_id })) rsk = self.rootstock_rpc.getwork() rsk.addCallback(self._rsk_getwork, rsk_block_received_id) rsk.addErrback(self._rsk_getwork_err) except AttributeError as e: if "'NoneType' object has no attribute 'getwork'" in str(e): pass #RSK dropped recently so we're letting this pass def _rsk_getwork(self, result, id): try: self._rsk_fill_data(result) template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id(), True) self.last_data['rsk_flag'] = True if settings.RSK_DEV_MODE: self.rootstock_rpc.rsk_target = int(settings.RSK_DEV_TARGET) else: self.rootstock_rpc.rsk_target = int(result['target'], 16) self.last_data['rsk_target'] = self.rootstock_rpc.rsk_target self.last_data['rsk_header'] = self.rootstock_rpc.rsk_header self.last_data['rsk_notify'] = self.rootstock_rpc.rsk_notify template.fill_from_rpc(self.last_data) self.add_template(template) start = Interfaces.timestamper.time() log.info( json.dumps({ "uuid": id, "rsk": "[RSKLOG]", "tag": "[RSK_BLOCK_RECEIVED_TEMPLATE]", "start": start, "elapsed": 0, "data": self.last_block.__dict__['broadcast_args'] })) #job_id return self.last_data finally: self.rsk_update_in_progress = False def _rsk_getwork_err(self, err): self.rsk_update_in_progress = False log.error("_RSK_GETWORK_ERR: " + str(err)) if "111: Connection refused" in str(err): log.info("RSKD Connection refused...") if self.rsk_timeout_counter < 3: log.info("RSKD Connection refused... trying %d more times", 3 - self.rsk_timeout_counter) self.rsk_update_block() self.rsk_timeout_counter += 1 else: log.info("RSKD Connection refused trying again in %s seconds", settings.RSK_POLL_PERIOD) self.rsk_timeout_counter = 0 def diff_to_target(self, difficulty): '''Converts difficulty to target''' diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000 return diff1 / difficulty def get_job(self, job_id): '''For given job_id returns BlockTemplate instance or None''' try: j = self.jobs[job_id] except: log.info("Job id '%s' not found" % job_id) return None # Now we have to check if job is still valid. # Unfortunately weak references are not bulletproof and # old reference can be found until next run of garbage collector. if j.prevhash_hex not in self.prevhashes: log.info("Prevhash of job '%s' is unknown" % job_id) return None if j not in self.prevhashes[j.prevhash_hex]: log.info("Job %s is unknown" % job_id) return None return j def submit_share(self, job_id, worker_name, extranonce1_bin, extranonce2, ntime, nonce, difficulty): '''Check parameters and finalize block template. If it leads to valid block candidate, asynchronously submits the block back to the bitcoin network. - extranonce1_bin is binary. No checks performed, it should be from session data - job_id, extranonce2, ntime, nonce - in hex form sent by the client - difficulty - decimal number from session, again no checks performed - submitblock_callback - reference to method which receive result of submitblock() ''' global rsk_last_received_share_time global rsk_submitted_shares start = Interfaces.timestamper.time() logid = util.id_generator() log.info( json.dumps({ "rsk": "[RSKLOG]", "tag": "[SHARE_RECEIVED_START]", "uuid": logid, "start": start, "elapsed": 0 })) # Check if extranonce2 looks correctly. extranonce2 is in hex form... if len(extranonce2) != self.extranonce2_size * 2: raise SubmitException( "Incorrect size of extranonce2. Expected %d chars" % (self.extranonce2_size * 2)) # Check for job job = self.get_job(job_id) if job == None: raise SubmitException("Job '%s' not found" % job_id) # Check if ntime looks correct if len(ntime) != 8: raise SubmitException("Incorrect size of ntime. Expected 8 chars") if not job.check_ntime(int(ntime, 16)): raise SubmitException("Ntime out of range") # Check nonce if len(nonce) != 8: raise SubmitException("Incorrect size of nonce. Expected 8 chars") # Convert from hex to binary extranonce2_bin = binascii.unhexlify(extranonce2) ntime_bin = binascii.unhexlify(ntime) nonce_bin = binascii.unhexlify(nonce) # Check for duplicated submit if not job.register_submit(extranonce1_bin, extranonce2_bin, ntime_bin, nonce_bin): log.info("Duplicate from %s, (%s %s %s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce)) raise SubmitException("Duplicate share") # Now let's do the hard work! # --------------------------- # 1. Build coinbase coinbase_bin = job.serialize_coinbase(extranonce1_bin, extranonce2_bin) coinbase_hash = util.doublesha(coinbase_bin) # 2. Calculate merkle root merkle_root_bin = job.merkletree.withFirst(coinbase_hash) merkle_root_int = util.uint256_from_str(merkle_root_bin) # 3. Serialize header with given merkle, ntime and nonce header_bin = job.serialize_header(merkle_root_int, ntime_bin, nonce_bin) # 4. Reverse header and compare it with target of the user # header 80-bytes (19*4 + 4) header_le = ''.join( [header_bin[i * 4:i * 4 + 4][::-1] for i in range(0, 20)]) hash_bin = util.doublesha(header_le) hash_int = util.uint256_from_str(hash_bin) block_hash_hex = "%064x" % hash_int header_hex = binascii.hexlify(header_bin) log.info( json.dumps({ "rsk": "[RSKLOG]", "tag": "[SHARE_RECEIVED_HEX]", "uuid": logid, "start": Interfaces.timestamper.time(), "elapsed": 0, "data": block_hash_hex })) if not settings.RSK_DEV_MODE: target_user = self.diff_to_target(difficulty) if hash_int > target_user: raise SubmitException("Share is above target") # Mostly for debugging purposes target_info = self.diff_to_target(100000) if hash_int <= target_info: log.info("Yay, share with diff above 100000") # 5. Compare hash with target of the network log.info("Hash_Int: %s, Job.Target %s" % (hash_int, job.target)) btc_solution = hash_int <= job.target rsk_solution = False if self.rootstock_rpc is not None: rsk_solution = hash_int <= self.rootstock_rpc.rsk_target and self._is_rsk_tag_in_coinbase( coinbase_bin) on_submit_rsk = None on_submit = None if btc_solution or rsk_solution: log.info("We found a block candidate! %s" % block_hash_hex) job.finalize(merkle_root_int, extranonce1_bin, extranonce2_bin, int(ntime, 16), int(nonce, 16)) if btc_solution: serialized = binascii.hexlify(job.serialize()) on_submit = self.bitcoin_rpc.submitblock(serialized) log.info( json.dumps({ "rsk": "[RSKLOG]", "tag": "[BTC_SUBMITBLOCK]", "uuid": util.id_generator(), "start": start, "elapsed": Interfaces.timestamper.time(), "data": block_hash_hex })) if rsk_solution: if rsk_last_received_share_time is None: rsk_last_received_share_time = int(round(time() * 1000)) rsk_submitted_shares = 0 last_received_share_time_now = int(round(time() * 1000)) if last_received_share_time_now - rsk_last_received_share_time >= 1000: rsk_submitted_shares = 0 rsk_last_received_share_time = last_received_share_time_now if last_received_share_time_now - rsk_last_received_share_time < 1000 and rsk_submitted_shares < 3: rsk_submitted_shares += 1 else: return (header_hex, block_hash_hex, on_submit, on_submit_rsk) serialized = binascii.hexlify(job.serialize()) block_header_hex = binascii.hexlify(header_le) coinbase_hex = binascii.hexlify(coinbase_bin) coinbase_hash_hex = binascii.hexlify(coinbase_hash) merkle_hashes_array = [ binascii.hexlify(x) for x in job.merkletree._steps ] merkle_hashes_array.insert(0, coinbase_hash_hex) merkle_hashes = ' '.join(merkle_hashes_array) txn_count = hex(len(merkle_hashes_array))[2:] on_submit_rsk = self.rootstock_rpc.submitBitcoinBlockPartialMerkle( block_hash_hex, block_header_hex, coinbase_hex, merkle_hashes, txn_count) log.info( json.dumps({ "rsk": "[RSKLOG]", "tag": "[RSK_SUBMITBLOCK]", "uuid": util.id_generator(), "start": start, "elapsed": Interfaces.timestamper.time(), "data": block_hash_hex })) return (header_hex, block_hash_hex, on_submit, on_submit_rsk)
class TemplateRegistry(object): '''Implements the main logic of the pool. Keep track on valid block templates, provide internal interface for stratum service and implements block validation and submits.''' def __init__(self, block_template_class, coinbaser, bitcoin_rpc, aux_rpc, instance_id, on_template_callback, on_block_callback): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() self.coinbaser = coinbaser self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.update_in_progress = False self.last_update = 0 self.last_height = None self.aux_rpc = aux_rpc self.aux_update_in_progress = False self.aux_new_block = False self.aux_last_update = 0 self.aux_update_counter = 0 self.aux_data = [] # Create first block template on startup self.update_auxs() #self.update_block() def get_new_extranonce1(self): '''Generates unique extranonce1 (e.g. for newly subscribed connection.''' return self.extranonce_counter.get_new_bin() def get_last_broadcast_args(self): '''Returns arguments for mining.notify from last known template.''' return self.last_block.broadcast_args def add_template(self, block, block_height): '''Adds new template to the registry. It also clean up templates which should not be used anymore.''' prevhash = block.prevhash_hex if prevhash in self.prevhashes.keys() and not self.aux_new_block: new_block = False else: new_block = True self.prevhashes[prevhash] = [] self.aux_new_block = False # Blocks sorted by prevhash, so it's easy to drop # them on blockchain update self.prevhashes[prevhash].append(block) # Weak reference for fast lookup using job_id self.jobs[block.job_id] = block # Use this template for every new request self.last_block = block # Drop templates of obsolete blocks for ph in self.prevhashes.keys(): if ph != prevhash: del self.prevhashes[ph] log.info("New template for %s" % prevhash) if new_block: # Tell the system about new block # It is mostly important for share manager self.on_block_callback(block_height) # Everything is ready, let's broadcast jobs! self.on_template_callback(new_block) def update_block(self): '''Registry calls the getblocktemplate() RPC and build new block template.''' if self.update_in_progress: # Block has been already detected return self.update_in_progress = True self.last_update = Interfaces.timestamper.time() d = self.bitcoin_rpc.getblocktemplate() d.addCallback(self._update_block) d.addErrback(self._update_block_failed) def _update_block_failed(self, failure): log.error(str(failure)) self.update_in_progress = False def _update_block(self, data): if self.aux_data is None: return start = Interfaces.timestamper.time() template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id()) template.fill_from_rpc(data, self.aux_data) self.last_height = data['height'] self.add_template(template, data['height']) log.info("Update finished, %.03f sec, %d txes" % \ (Interfaces.timestamper.time() - start, len(template.vtx))) self.update_in_progress = False return data def update_auxs(self): if self.aux_update_in_progress: return self.aux_update_in_progress = True self.aux_last_update = Interfaces.timestamper.time() self.aux_data = [] self.aux_update_counter = 0 for chain in range(len(self.aux_rpc.conns)): aux_block = self.aux_rpc.conns[chain].getauxblock() aux_block.addCallback(self._update_auxs) aux_block.addErrback(self._update_auxs_failed) def _update_auxs(self, data): self.aux_data.append(data) self.aux_update_counter += 1 if self.aux_update_counter == len(self.aux_rpc.conns): self.aux_update_counter = 0 self.aux_update_in_progress = False self.update_block() return data def _update_auxs_failed(self, failure): log.error(str(failure)) self.aux_update_in_progress = False self.aux_update_counter = 0 def diff_to_target(self, difficulty): '''Converts difficulty to target''' if settings.DAEMON_ALGO == 'scrypt': diff1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000 elif settings.DAEMON_ALGO == 'yescrypt': diff1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000 elif settings.DAEMON_ALGO == 'qubit': diff1 = 0x000000ffff000000000000000000000000000000000000000000000000000000 else: diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000 return float(diff1) / float(difficulty) def get_job(self, job_id): '''For given job_id returns BlockTemplate instance or None''' try: j = self.jobs[job_id] except: log.info("Job id '%s' not found" % job_id) return None # Now we have to check if job is still valid. # Unfortunately weak references are not bulletproof and # old reference can be found until next run of garbage collector. if j.prevhash_hex not in self.prevhashes: log.info("Prevhash of job '%s' is unknown" % job_id) return None if j not in self.prevhashes[j.prevhash_hex]: log.info("Job %s is unknown" % job_id) return None return j def submit_share(self, job_id, worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, difficulty, ip, submit_time): '''Check parameters and finalize block template. If it leads to valid block candidate, asynchronously submits the block back to the bitcoin network. - extranonce1_bin is binary. No checks performed, it should be from session data - job_id, extranonce2, ntime, nonce - in hex form sent by the client - difficulty - decimal number from session, again no checks performed - submitblock_callback - reference to method which receive result of submitblock() ''' # Check if extranonce2 looks correctly. extranonce2 is in hex form... if len(extranonce2) != self.extranonce2_size * 2: raise SubmitException( "Incorrect size of extranonce2. Expected %d chars" % (self.extranonce2_size * 2)) # Check for job job = self.get_job(job_id) if job == None: raise SubmitException("Job '%s' not found" % job_id) # Check if ntime looks correct if len(ntime) != 8: raise SubmitException("Incorrect size of ntime. Expected 8 chars") if not job.check_ntime(int(ntime, 16)): raise SubmitException("Ntime out of range") # Check nonce if len(nonce) != 8: raise SubmitException("Incorrect size of nonce. Expected 8 chars") # Check for duplicated submit if not job.register_submit(extranonce1_bin, extranonce2, ntime, nonce): log.info("Duplicate from %s, (%s %s %s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce)) raise SubmitException("Duplicate share") # Now let's do the hard work! # --------------------------- # 0. Some sugar extranonce2_bin = binascii.unhexlify(extranonce2) ntime_bin = binascii.unhexlify(ntime) nonce_bin = binascii.unhexlify(nonce) # 1. Build coinbase coinbase_bin = job.serialize_coinbase(extranonce1_bin, extranonce2_bin) coinbase_hash = util.doublesha(coinbase_bin) # 2. Calculate merkle root merkle_root_bin = job.merkletree.withFirst(coinbase_hash) merkle_root_int = util.uint256_from_str(merkle_root_bin) # 3. Serialize header with given merkle, ntime and nonce header_bin = job.serialize_header(merkle_root_int, ntime_bin, nonce_bin) # 4. Reverse header and compare it with target of the user if settings.DAEMON_ALGO == 'scrypt': hash_bin = ltc_scrypt.getPoWHash(header_bin) elif settings.DAEMON_ALGO == 'yescrypt': hash_bin = yescrypt_hash.getPoWHash(header_bin) elif settings.DAEMON_ALGO == 'qubit': hash_bin = qubit_hash.getPoWHash(header_bin) else: hash_bin = util.doublesha(header_bin) hash_int = util.uint256_from_str(hash_bin) pow_hash_hex = "%064x" % hash_int header_hex = binascii.hexlify(header_bin) target_user = self.diff_to_target(difficulty) if hash_int > target_user: raise SubmitException("Share is above target") # Mostly for debugging purposes target_info = self.diff_to_target(1000) if hash_int <= target_info: log.info("Yay, share with diff above 1000") # Algebra tells us the diff_to_target is the same as hash_to_diff share_diff = float(self.diff_to_target(hash_int)) on_submit = None aux_submit = None block_hash_bin = util.doublesha(header_bin) block_hash_hex = block_hash_bin[::-1].encode('hex_codec') if hash_int <= job.target: log.info( "MAINNET BLOCK CANDIDATE! %s diff(%f/%f)" % (block_hash_hex, share_diff, self.diff_to_target(job.target))) job.finalize(merkle_root_int, extranonce1_bin, extranonce2_bin, int(ntime, 16), int(nonce, 16)) if not job.is_valid(): log.exception("FINAL JOB VALIDATION FAILED!") serialized = binascii.hexlify(job.serialize()) if settings.SOLUTION_BLOCK_HASH: on_submit = self.bitcoin_rpc.submitblock( serialized, block_hash_hex) else: on_submit = self.bitcoin_rpc.submitblock( serialized, pow_hash_hex) '''if on_submit: self.update_block()''' # Check auxiliary merged chains for chain in range(len(job.auxs)): if hash_int <= job.aux_targets[chain]: log.info("FOUND MERGED BLOCK! %s diff(%f/%f)" % (job.auxs[chain]['hash'], share_diff, self.diff_to_target(job.aux_targets[chain]))) coinbase_hex = binascii.hexlify(coinbase_bin) branch_count = job.merkletree.branchCount() branch_hex = job.merkletree.branchHex() merkle_link = util.calculate_merkle_link( job.merkle_hashes, job.tree[job.auxs[chain]['chainid']]) submission = coinbase_hex + block_hash_hex + branch_count + branch_hex + '00000000' + merkle_link + header_hex aux_submit = self.aux_rpc.conns[chain].getauxblock( job.auxs[chain]['hash'], submission) aux_submit.addCallback( Interfaces.share_manager.on_submit_aux_block, worker_name, header_hex, job.auxs[chain]['hash'], submit_time, ip, share_diff) '''if aux_submit: self.update_auxs()''' if settings.SOLUTION_BLOCK_HASH: return (header_hex, block_hash_hex, share_diff, on_submit) else: return (header_hex, pow_hash_hex, share_diff, on_submit)
class TemplateRegistry(object): '''Implements the main logic of the pool. Keep track on valid block templates, provide internal interface for stratum service and implements block validation and submits.''' def __init__(self, block_template_class, coinbaser, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() self.coinbaser = coinbaser self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.update_in_progress = False self.last_update = None # Create first block template on startup self.update_block() def get_new_extranonce1(self): '''Generates unique extranonce1 (e.g. for newly subscribed connection.''' return self.extranonce_counter.get_new_bin() def get_last_broadcast_args(self): '''Returns arguments for mining.notify from last known template.''' return self.last_block.broadcast_args def add_template(self, block): '''Adds new template to the registry. It also clean up templates which should not be used anymore.''' prevhash = block.prevhash_hex if prevhash in self.prevhashes.keys(): new_block = False else: new_block = True self.prevhashes[prevhash] = [] # Blocks sorted by prevhash, so it's easy to drop # them on blockchain update self.prevhashes[prevhash].append(block) # Weak reference for fast lookup using job_id self.jobs[block.job_id] = block # Use this template for every new request self.last_block = block # Drop templates of obsolete blocks for ph in self.prevhashes.keys(): if ph != prevhash: del self.prevhashes[ph] log.info("New template for %s" % prevhash) if new_block: # Tell the system about new block # It is mostly important for share manager self.on_block_callback(prevhash) # Everything is ready, let's broadcast jobs! self.on_template_callback(new_block) #from twisted.internet import reactor #reactor.callLater(10, self.on_block_callback, new_block) def update_block(self): '''Registry calls the getblocktemplate() RPC and build new block template.''' if self.update_in_progress: # Block has been already detected return self.update_in_progress = True self.last_update = Interfaces.timestamper.time() d = self.bitcoin_rpc.getblocktemplate() d.addCallback(self._update_block) d.addErrback(self._update_block_failed) def _update_block_failed(self, failure): log.error(str(failure)) self.update_in_progress = False def _update_block(self, data): start = Interfaces.timestamper.time() template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id()) template.fill_from_rpc(data) self.add_template(template) log.info("Update finished, %.03f sec, %d txes" % \ (Interfaces.timestamper.time() - start, len(template.vtx))) self.update_in_progress = False return data def diff_to_target(self, difficulty): '''Converts difficulty to target''' diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000 return diff1 / difficulty def get_job(self, job_id): '''For given job_id returns BlockTemplate instance or None''' try: j = self.jobs[job_id] except: log.info("Job id '%s' not found" % job_id) return None # Now we have to check if job is still valid. # Unfortunately weak references are not bulletproof and # old reference can be found until next run of garbage collector. if j.prevhash_hex not in self.prevhashes: log.info("Prevhash of job '%s' is unknown" % job_id) return None if j not in self.prevhashes[j.prevhash_hex]: log.info("Job %s is unknown" % job_id) return None return j def submit_share(self, job_id, worker_name, extranonce1_bin, extranonce2, ntime, nonce, difficulty, address=None): '''Check parameters and finalize block template. If it leads to valid block candidate, asynchronously submits the block back to the bitcoin network. - extranonce1_bin is binary. No checks performed, it should be from session data - job_id, extranonce2, ntime, nonce - in hex form sent by the client - difficulty - decimal number from session, again no checks performed - submitblock_callback - reference to method which receive result of submitblock() ''' # Check if extranonce2 looks correctly. extranonce2 is in hex form... if len(extranonce2) != self.extranonce2_size * 2: raise SubmitException("Incorrect size of extranonce2. Expected %d chars" % (self.extranonce2_size*2)) # Check for job job = self.get_job(job_id) if job == None: raise SubmitException("Job '%s' not found" % job_id) # Check if ntime looks correct if len(ntime) != 8: raise SubmitException("Incorrect size of ntime. Expected 8 chars") if not job.check_ntime(int(ntime, 16)): raise SubmitException("Ntime out of range") # Check nonce if len(nonce) != 8: raise SubmitException("Incorrect size of nonce. Expected 8 chars") # Convert from hex to binary extranonce2_bin = binascii.unhexlify(extranonce2) ntime_bin = binascii.unhexlify(ntime) nonce_bin = binascii.unhexlify(nonce) # Check for duplicated submit if not job.register_submit(extranonce1_bin, extranonce2_bin, ntime_bin, nonce_bin): log.info("Duplicate from %s, (%s %s %s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce)) raise SubmitException("Duplicate share") # Now let's do the hard work! # --------------------------- # 0. Make a copy of the job if this is a submitfull() # and change the coinbase address. We make a copy first # because we don't want to ruin this job for regular # submit() users if address: oldjob = job job = copy.deepcopy(job) job.vtx[0].address = address # we know it's not our own address, and we don't care job.vtx[0].is_valid = True # 1. Build coinbase coinbase_bin = job.serialize_coinbase(extranonce1_bin, extranonce2_bin) coinbase_hash = util.doublesha(coinbase_bin) # 2. Calculate merkle root merkle_root_bin = job.merkletree.withFirst(coinbase_hash) merkle_root_int = util.uint256_from_str(merkle_root_bin) # 3. Serialize header with given merkle, ntime and nonce header_bin = job.serialize_header(merkle_root_int, ntime_bin, nonce_bin) # 4. Reverse header and compare it with target of the user hash_bin = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) hash_int = util.uint256_from_str(hash_bin) block_hash_hex = "%064x" % hash_int header_hex = binascii.hexlify(header_bin) target_user = self.diff_to_target(difficulty) if hash_int > target_user: raise SubmitException("Share is above target") # Mostly for debugging purposes target_info = self.diff_to_target(100000) if hash_int <= target_info: log.info("Yay, share with diff above 100000") # 5. Compare hash with target of the network if hash_int <= job.target: # Yay! It is block candidate! log.info("We found a block candidate! %s" % block_hash_hex) # 6. Finalize and serialize block object job.finalize(merkle_root_int, extranonce1_bin, extranonce2_bin, int(ntime, 16), int(nonce, 16)) if not job.is_valid(): # Should not happen log.error("Final job validation failed!") # 7. Submit block to the network serialized = binascii.hexlify(job.serialize()) on_submit = self.bitcoin_rpc.submitblock(serialized) return (header_hex, block_hash_hex, on_submit) return (header_hex, block_hash_hex, None)
class TemplateRegistry(object): '''Implements the main logic of the pool. Keep track on valid block templates, provide internal interface for stratum service and implements block validation and submits.''' def __init__(self, template_generator, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): log.debug("Got to Template Registry") self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.template_generator = template_generator self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = template_generator.get_extranonce_size() - self.extranonce_counter.get_size() self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_template = None self.update_in_progress = False self.GBT_RPC_ATTEMPT = None self.last_block_update_start_time = None # Create first block template on startup self.update_block() def get_new_extranonce1(self): '''Generates unique extranonce1 (e.g. for newly subscribed connection.''' log.debug("Getting Unique Extronance") return self.extranonce_counter.get_new_bin() def get_last_broadcast_args(self): '''Returns arguments for mining.notify from last known template.''' log.debug("Getting arguments needed for mining.notify") return self.last_template.broadcast_args def add_template(self, template, block_height): '''Adds new template to the registry. It also clean up templates which should not be used anymore.''' prevhash = template.prevhash_hex if prevhash in self.prevhashes.keys(): new_block = False else: new_block = True self.prevhashes[prevhash] = [] # Blocks sorted by prevhash, so it's easy to drop # them on blockchain update self.prevhashes[prevhash].append(template) # Weak reference for fast lookup using job_id self.jobs[template.job_id] = template # Use this template for every new request self.last_template = template # Drop templates of obsolete blocks for ph in self.prevhashes.keys(): if ph != prevhash: del self.prevhashes[ph] log.info("New template for %s" % prevhash) if new_block: # Tell the system about new block # It is mostly important for share manager self.on_block_callback(prevhash, block_height) # Everything is ready, let's broadcast jobs! self.on_template_callback(new_block) #from twisted.internet import reactor #reactor.callLater(10, self.on_block_callback, new_block) def update_block(self, force = False): '''Registry calls the getblocktemplate() RPC and build new block template.''' log.info("A block update has been requested.") if self.update_in_progress and force and not self.GBT_RPC_ATTEMPT is None: # Cancel current block update request (if any) log.warning("Forcing block update.") self.GBT_RPC_ATTEMPT.cancel() self.update_in_progress = False if self.update_in_progress: # Block has been already detected log.warning("Block update already in progress. Started at: %s" % str(self.last_block_update_start_time)) # It's possible for this process to get 'hung', lets see how long running_time = max(Interfaces.timestamper.time() - self.last_block_update_start_time , 0) log.info("Block update running for %i seconds" % running_time) # If it's been more than 30 seconds, then cancel it # But we don't run in this instance. if running_time >= 30: log.error("Block update appears to be hung. Running for more than %i seconds. Canceling..." % running_time) self.GBT_RPC_ATTEMPT.cancel() self.update_in_progress = False return log.debug("Block update started.") self.update_in_progress = True self.last_block_update_start_time = Interfaces.timestamper.time() # Polls the upstream network daemon for new block template # This is done asyncronisly self.GBT_RPC_ATTEMPT = self.bitcoin_rpc.getblocktemplate() log.debug("Block template request sent") self.GBT_RPC_ATTEMPT.addCallback(self._update_block) self.GBT_RPC_ATTEMPT.addErrback(self._update_block_failed) def _update_block_failed(self, failure): # Runs when upstream 'getblocktemplate()' RPC call has failed try: log.error("Could not load block template: %s" % str(failure)) except: log.error("Could not load block template.") finally: self.update_in_progress = False def _update_block(self, data): # Runs when upstream 'getblocktemplate()' RPC call has completed with no error log.debug("Block template data recived: Creating new template...") # Generate a new template template = self.template_generator.new_template(Interfaces.timestamper, JobIdGenerator.get_new_id()) # Apply newly obtained template 'data' as recived from upstream network log.debug("Filling RPC Data") template.fill_from_rpc(data) # Add it the template registry log.debug("Adding to Registry") self.add_template(template, data['height']) # All done log.info("Block update finished, %.03f sec, %d txes" % (Interfaces.timestamper.time() - self.last_block_update_start_time, len(template.block.vtx))) self.update_in_progress = False ''' TODO: Investigate I don't think it is nessesary to return the raw block template data or anything at all from the '_update_block' function since it's called asyncronosly. Leaving it for now. ''' return data def diff_to_target(self, difficulty): '''Converts difficulty to target''' return util.get_diff_target(difficulty); def get_job(self, job_id, worker_name, ip=False): '''For given job_id returns BlockTemplate instance or None''' try: j = self.jobs[job_id] except: log.info("Job id '%s' not found, worker_name: '%s'" % (job_id, worker_name)) if ip: log.debug("Worker submited invalid Job id: IP %s", str(ip)) return None # Now we have to check if job is still valid. # Unfortunately weak references are not bulletproof and # old reference can be found until next run of garbage collector. if j.prevhash_hex not in self.prevhashes: log.debug("Prevhash of job '%s' is unknown" % job_id) return None if j not in self.prevhashes[j.prevhash_hex]: log.debug("Job %s is unknown" % job_id) return None return j def submit_share(self, job_id, worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, difficulty, ip=False): '''Check parameters and finalize block template. If it leads to valid block candidate, asynchronously submits the block back to the bitcoin network. - extranonce1_bin is binary. No checks performed, it should be from session data - job_id, extranonce2, ntime, nonce - in hex form sent by the client - difficulty - decimal number from session - submitblock_callback - reference to method which receive result of submitblock() - difficulty is checked to see if its lower than the vardiff minimum target or pool target from conf/config.py and if it is the share is rejected due to it not meeting the requirements for a share ''' log.debug("Session: %s" % session) # Share Difficulty should never be 0 or below if difficulty <= 0 : log.exception("Worker %s @ IP: %s seems to be submitting Fake Shares"%(worker_name,ip)) raise SubmitException("Diff is %s Share Rejected Reporting to Admin"%(difficulty)) # Check if extranonce2 looks correctly. extranonce2 is in hex form... if len(extranonce2) != self.extranonce2_size * 2: raise SubmitException("Incorrect size of extranonce2. Expected %d chars" % (self.extranonce2_size*2)) # Check for job job = self.get_job(job_id, worker_name, ip) if job == None: if settings.REJECT_STALE_SHARES: # Reject stale share raise SubmitException("Job '%s' not found" % job_id) else: # Accept stale share but do not continue checking, return a bunch of nothingness log.info("Accepted Stale Share from %s, (%s %s %s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce)) return (None, None, None, None, None, None) # Check if ntime looks correct check_result, error_message = util.check_ntime(ntime) if not check_result: raise SubmitException(error_message) if not job.check_ntime(int(ntime, 16), getattr(settings, 'NTIME_AGE')): raise SubmitException("Ntime out of range") # Check nonce check_result, error_message = util.check_nonce(nonce) if not check_result: raise SubmitException(error_message) # Check for duplicated submit if not job.register_submit(extranonce1_bin, extranonce2, ntime, nonce): log.info("Duplicate from %s, (%s %s %s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce)) raise SubmitException("Duplicate share") # Now let's do the hard work! # --------------------------- # 0. Some sugar extranonce2_bin = binascii.unhexlify(extranonce2) ntime_bin = util.get_ntime_bin(ntime) nonce_bin = util.get_nonce_bin(nonce) target_user = self.diff_to_target(difficulty) target_info = self.diff_to_target(100000) # 1. Build coinbase coinbase_bin = job.serialize_coinbase(extranonce1_bin, extranonce2_bin) coinbase_hash = util.get_coinbase_hash(coinbase_bin) # 2. Calculate merkle root merkle_root_bin = job.merkletree.withFirst(coinbase_hash) merkle_root_int = util.uint256_from_str(merkle_root_bin) # 3. Serialize header with given merkle, ntime and nonce header_bin = job.serialize_header(merkle_root_int, ntime_bin, nonce_bin) # 4. Convert header into hex according to hash algorythim block_hash = util.get_hash_hex(header_bin, ntime, nonce) # 5a Compare it with target of the user check_result, message = util.check_header_target(block_hash['int'], target_user) if not check_result: log.debug("Oops, somthing is wrong: target_user=%s, difficulty=%s, share_diff=%s" % (target_user, difficulty, int(self.diff_to_target(block_hash['int'])))) raise SubmitException("%s. Hash: %s" % (message, block_hash['hex'])) # Mostly for debugging purposes, just a celebratory message that's being carried over from older versions check_result, message = util.check_above_yay_target(block_hash['int'], target_info) if check_result: log.info(message) # Algebra tells us the diff_to_target is the same as hash_to_diff if settings.VDIFF_FLOAT: share_diff = float(self.diff_to_target(block_hash['int'])) else: share_diff = int(self.diff_to_target(block_hash['int'])) log.debug("share_diff: %s" % share_diff) log.debug("job.target: %s" % job.target) log.debug("block_hash_int: %s" % block_hash['int']) # 6. Compare hash with target of the network if util.is_block_candidate(block_hash['int'], job.target): # Yay! It is block candidate! log.info("We found a block candidate! for %i: %s | %s" % (job.height, block_hash['hex'], block_hash['check_hex'])) # 7. Finalize and serialize block object job.finalize(merkle_root_int, extranonce1_bin, extranonce2_bin, int(ntime, 16), int(nonce, 16)) if not job.is_valid(difficulty): # Should not happen log.exception("FINAL JOB VALIDATION FAILED!(Try enabling/disabling tx messages)") # 8. Submit block to the network serialized = binascii.hexlify(job.serialize()) on_submit = self.bitcoin_rpc.submitblock(serialized, block_hash['check_hex'], block_hash['hex']) if on_submit: self.update_block() return (block_hash['header_hex'], block_hash['solution_hex'], share_diff, job.prevhash_hex, job.height, on_submit) # Not a potential Block return (block_hash['header_hex'], block_hash['solution_hex'], share_diff, job.prevhash_hex, job.height, None)
class TemplateRegistry(object): '''Implements the main logic of the pool. Keep track on valid block templates, provide internal interface for stratum service and implements block validation and submits.''' def __init__(self, block_template_class, coinbaser, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): self.current_prevhash = '' self.jobs = weakref.WeakValueDictionary() self.minimal_job_id = 0 self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() self.coinbaser = coinbaser self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self._last_template = None self.update_in_progress = False self.last_update = None # Create first block template on startup self.update_block() def get_block_min_job_id(self): return self.minimal_job_id def get_new_extranonce1(self): '''Generates unique extranonce1 (e.g. for newly subscribed connection.''' return self.extranonce_counter.get_new_bin() def get_last_template_broadcast_args(self): '''Returns arguments for mining.notify from last known template.''' return self._last_template.broadcast_args def get_last_template(self): ''' Returns the last known template. ''' return self._last_template def _add_template(self, template): '''Adds new template to the registry. It also clean up templates which should not be used anymore.''' prevhash = template.prevhash_hex # Did we just detect a new block? new_block = (prevhash != self.current_prevhash) if new_block: # Update the current prevhash and throw away all previous jobs (templates) self.current_prevhash = prevhash self.jobs = {template.job_id: template} # Remember the first job's id for the new block. All next templates must have # higher job_id in order to be valid. self.minimal_job_id = template.job_id # Tell the system about new template # It is mostly important for share manager self.on_block_callback(prevhash) else: # Check if the new job (template) has valid job_id (related to the first job for the block) if template.job_id < self.minimal_job_id: log.error("New template has invalid job_id (%s) - minimal %s" % \ (template.job_id, self.minimal_job_id)) return # Remember the job for the current block (prevhash) self.jobs[template.job_id] = template # Use this template for every new request self._last_template = template log.info("New template %x (cnt %d) for %s" % \ (template.job_id, len(self.jobs), prevhash)) # Everything is ready, let's broadcast jobs! self.on_template_callback(new_block) def update_blank_block(self, prevhash): '''Pick current block, replaces it's prevhash and broadcast it as a new template to client. This is work-around for slow processing of blocks in bitcoind.''' start = posix_time() template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id()) template.fill_from_another(self._last_template, prevhash) self._add_template(template) log.info("Blank block update finished, %.03f sec, %d txes" % \ (posix_time() - start, len(template.vtx))) # Now let's do standard update (with transactions) self.update_block() def update_block(self): '''Registry calls the getblocktemplate() RPC and build new block template.''' if self.update_in_progress: # Block has been already detected return self.update_in_progress = True self.last_update = posix_time() d = self.bitcoin_rpc.getblocktemplate() d.addCallback(self._update_block) d.addErrback(self._update_block_failed) def _update_block_failed(self, failure): self.update_in_progress = False log.error(str(failure)) def _update_block(self, bitcoind_rpc_template): try: if isinstance(bitcoind_rpc_template, dict): start = posix_time() template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id()) template.fill_from_rpc(bitcoind_rpc_template) self._add_template(template) log.info("Update finished, %.03f sec, %d txes, %s BTC" % \ (posix_time() - start, len(template.vtx), Decimal(template.get_value())/10**8)) #@UndefinedVariable else: log.error("Invalid data for block update: %s" % (bitcoind_rpc_template, )) finally: self.update_in_progress = False return bitcoind_rpc_template def diff_to_target(self, difficulty): '''Converts difficulty to target''' diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000 return diff1 / difficulty def get_job(self, job_id): '''For given job_id returns BlockTemplate instance or None''' try: return self.jobs[job_id] except: log.error("Job id '%s' not found" % job_id) return None def submit_share(self, job_id, worker_name, extranonce1_bin, extranonce2, ntime, nonce, difficulty): '''Check parameters and finalize block template. If it leads to valid block candidate, asynchronously submits the block back to the bitcoin network. - extranonce1_bin is binary. No checks performed, it should be from session data - job_id, extranonce2, ntime, nonce - in hex form sent by the client - difficulty - decimal number from session, again no checks performed - submitblock_callback - reference to method which receive result of submitblock() ''' # Check if extranonce2 looks correctly. extranonce2 is in hex form... if len(extranonce2) != self.extranonce2_size * 2: raise SubmitException("Incorrect size of extranonce2. Expected %d chars" % (self.extranonce2_size*2)) # Check for job job = self.get_job(job_id) if job == None: raise SubmitException("Job '%s' not found" % job_id) # Check if ntime looks correct if len(ntime) != 8: raise SubmitException("Incorrect size of ntime. Expected 8 chars") if not job.check_ntime(int(ntime, 16)): raise SubmitException("Ntime out of range") # Check nonce if len(nonce) != 8: raise SubmitException("Incorrect size of nonce. Expected 8 chars") # Check for duplicated submit if not job.register_submit(extranonce1_bin, extranonce2, ntime, nonce): log.info("Duplicate from %s, (%s %s %s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce)) raise SubmitException("Duplicate share") # Now let's do the hard work! # --------------------------- # 0. Some sugar extranonce2_bin = binascii.unhexlify(extranonce2) ntime_bin = binascii.unhexlify(ntime) nonce_bin = binascii.unhexlify(nonce) # 1. Build coinbase coinbase_bin = job.serialize_coinbase(extranonce1_bin, extranonce2_bin) coinbase_hash = util.doublesha(coinbase_bin) # 2. Calculate merkle root merkle_root_bin = job.merkletree.withFirst(coinbase_hash) merkle_root_int = util.uint256_from_str(merkle_root_bin) # 3. Serialize header with given merkle, ntime and nonce header_bin = job.serialize_header(merkle_root_int, ntime_bin, nonce_bin) # 4. Reverse header and compare it with target of the user hash_bin = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) hash_int = util.uint256_from_str(hash_bin) block_hash_hex = "%064x" % hash_int header_hex = binascii.hexlify(header_bin) target_user = self.diff_to_target(difficulty) if hash_int > target_user: # For testing purposes ONLY if not config.ACCEPT_SHARES_ABOVE_TARGET: raise SubmitException("Share is above target") # 5. Compare hash with target of the network if hash_int <= job.target or \ (nonce == config.ACCEPT_INVALID_BLOCK__NONCE): # Yay! It is block candidate! log.info("We found a block candidate! %s" % block_hash_hex) # 6. Finalize and serialize block object job.finalize(merkle_root_int, extranonce1_bin, extranonce2_bin, int(ntime, 16), int(nonce, 16)) if not job.is_valid(): # Should not happen log.error("Final job validation failed!") # 7. Get block value for statistical purposes block_value = job.get_value() # 8. Submit block to the network serialized = binascii.hexlify(job.serialize()) on_submit = self.bitcoin_rpc.submitblock(serialized) return (header_hex, block_hash_hex, block_value, on_submit) return (header_hex, block_hash_hex, None, None)
class TemplateRegistry(object): '''Implements the main logic of the pool. Keep track on valid block templates, provide internal interface for stratum service and implements block validation and submits.''' def __init__(self, block_template_class, coinbaser, bitcoin_rpc, aux_rpc, instance_id, on_template_callback, on_block_callback): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() self.coinbaser = coinbaser self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.update_in_progress = False self.last_update = 0 self.last_height = None self.aux_rpc = aux_rpc self.aux_update_in_progress = False self.aux_new_block = False self.aux_last_update = 0 self.aux_update_counter = 0 self.aux_data = [] # Create first block template on startup self.update_auxs() #self.update_block() def get_new_extranonce1(self): '''Generates unique extranonce1 (e.g. for newly subscribed connection.''' return self.extranonce_counter.get_new_bin() def get_last_broadcast_args(self): '''Returns arguments for mining.notify from last known template.''' return self.last_block.broadcast_args def add_template(self, block,block_height): '''Adds new template to the registry. It also clean up templates which should not be used anymore.''' prevhash = block.prevhash_hex if prevhash in self.prevhashes.keys() and not self.aux_new_block: new_block = False else: new_block = True self.prevhashes[prevhash] = [] self.aux_new_block = False # Blocks sorted by prevhash, so it's easy to drop # them on blockchain update self.prevhashes[prevhash].append(block) # Weak reference for fast lookup using job_id self.jobs[block.job_id] = block # Use this template for every new request self.last_block = block # Drop templates of obsolete blocks for ph in self.prevhashes.keys(): if ph != prevhash: del self.prevhashes[ph] log.info("New template for %s" % prevhash) if new_block: # Tell the system about new block # It is mostly important for share manager self.on_block_callback(block_height) # Everything is ready, let's broadcast jobs! self.on_template_callback(new_block) def update_block(self): '''Registry calls the getblocktemplate() RPC and build new block template.''' if self.update_in_progress: # Block has been already detected return self.update_in_progress = True self.last_update = Interfaces.timestamper.time() d = self.bitcoin_rpc.getblocktemplate() d.addCallback(self._update_block) d.addErrback(self._update_block_failed) def _update_block_failed(self, failure): log.error(str(failure)) self.update_in_progress = False def _update_block(self, data): if self.aux_data is None: return start = Interfaces.timestamper.time() template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id()) template.fill_from_rpc(data, self.aux_data) self.last_height = data['height'] self.add_template(template, data['height']) log.info("Update finished, %.03f sec, %d txes" % \ (Interfaces.timestamper.time() - start, len(template.vtx))) self.update_in_progress = False return data def update_auxs(self): if self.aux_update_in_progress: return self.aux_update_in_progress = True self.aux_last_update = Interfaces.timestamper.time() self.aux_data = [] self.aux_update_counter = 0 for chain in range(len(self.aux_rpc.conns)): aux_block = self.aux_rpc.conns[chain].getauxblock() aux_block.addCallback(self._update_auxs) aux_block.addErrback(self._update_auxs_failed) def _update_auxs(self, data): self.aux_data.append(data) self.aux_update_counter += 1 if self.aux_update_counter == len(self.aux_rpc.conns): self.aux_update_counter = 0 self.aux_update_in_progress = False self.update_block() return data def _update_auxs_failed(self,failure): log.error(str(failure)) self.aux_update_in_progress = False self.aux_update_counter = 0 def diff_to_target(self, difficulty): '''Converts difficulty to target''' if settings.DAEMON_ALGO == 'scrypt': diff1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000 elif settings.DAEMON_ALGO == 'yescrypt': diff1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000 elif settings.DAEMON_ALGO == 'qubit': diff1 = 0x000000ffff000000000000000000000000000000000000000000000000000000 else: diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000 return float(diff1) / float(difficulty) def get_job(self, job_id): '''For given job_id returns BlockTemplate instance or None''' try: j = self.jobs[job_id] except: log.info("Job id '%s' not found" % job_id) return None # Now we have to check if job is still valid. # Unfortunately weak references are not bulletproof and # old reference can be found until next run of garbage collector. if j.prevhash_hex not in self.prevhashes: log.info("Prevhash of job '%s' is unknown" % job_id) return None if j not in self.prevhashes[j.prevhash_hex]: log.info("Job %s is unknown" % job_id) return None return j def submit_share(self, job_id, worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, difficulty, ip, submit_time): '''Check parameters and finalize block template. If it leads to valid block candidate, asynchronously submits the block back to the bitcoin network. - extranonce1_bin is binary. No checks performed, it should be from session data - job_id, extranonce2, ntime, nonce - in hex form sent by the client - difficulty - decimal number from session, again no checks performed - submitblock_callback - reference to method which receive result of submitblock() ''' # Check if extranonce2 looks correctly. extranonce2 is in hex form... if len(extranonce2) != self.extranonce2_size * 2: raise SubmitException("Incorrect size of extranonce2. Expected %d chars" % (self.extranonce2_size*2)) # Check for job job = self.get_job(job_id) if job == None: raise SubmitException("Job '%s' not found" % job_id) # Check if ntime looks correct if len(ntime) != 8: raise SubmitException("Incorrect size of ntime. Expected 8 chars") if not job.check_ntime(int(ntime, 16)): raise SubmitException("Ntime out of range") # Check nonce if len(nonce) != 8: raise SubmitException("Incorrect size of nonce. Expected 8 chars") # Check for duplicated submit if not job.register_submit(extranonce1_bin, extranonce2, ntime, nonce): log.info("Duplicate from %s, (%s %s %s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce)) raise SubmitException("Duplicate share") # Now let's do the hard work! # --------------------------- # 0. Some sugar extranonce2_bin = binascii.unhexlify(extranonce2) ntime_bin = binascii.unhexlify(ntime) nonce_bin = binascii.unhexlify(nonce) # 1. Build coinbase coinbase_bin = job.serialize_coinbase(extranonce1_bin, extranonce2_bin) coinbase_hash = util.doublesha(coinbase_bin) # 2. Calculate merkle root merkle_root_bin = job.merkletree.withFirst(coinbase_hash) merkle_root_int = util.uint256_from_str(merkle_root_bin) # 3. Serialize header with given merkle, ntime and nonce header_bin = job.serialize_header(merkle_root_int, ntime_bin, nonce_bin) # 4. Reverse header and compare it with target of the user if settings.DAEMON_ALGO == 'scrypt': hash_bin = ltc_scrypt.getPoWHash(header_bin) elif settings.DAEMON_ALGO == 'yescrypt': hash_bin = yescrypt_hash.getPoWHash(header_bin) elif settings.DAEMON_ALGO == 'qubit': hash_bin = qubit_hash.getPoWHash(header_bin) else: hash_bin = util.doublesha(header_bin) hash_int = util.uint256_from_str(hash_bin) pow_hash_hex = "%064x" % hash_int header_hex = binascii.hexlify(header_bin) target_user = self.diff_to_target(difficulty) if hash_int > target_user: raise SubmitException("Share is above target") # Mostly for debugging purposes target_info = self.diff_to_target(1000) if hash_int <= target_info: log.info("Yay, share with diff above 1000") # Algebra tells us the diff_to_target is the same as hash_to_diff share_diff = float(self.diff_to_target(hash_int)) on_submit = None aux_submit = None block_hash_bin = util.doublesha(header_bin) block_hash_hex = block_hash_bin[::-1].encode('hex_codec') if hash_int <= job.target: log.info("MAINNET BLOCK CANDIDATE! %s diff(%f/%f)" % (block_hash_hex, share_diff, self.diff_to_target(job.target))) job.finalize(merkle_root_int, extranonce1_bin, extranonce2_bin, int(ntime, 16), int(nonce, 16)) if not job.is_valid(): log.exception("FINAL JOB VALIDATION FAILED!") serialized = binascii.hexlify(job.serialize()) if settings.SOLUTION_BLOCK_HASH: on_submit = self.bitcoin_rpc.submitblock(serialized, block_hash_hex) else: on_submit = self.bitcoin_rpc.submitblock(serialized, pow_hash_hex) '''if on_submit: self.update_block()''' # Check auxiliary merged chains for chain in range(len(job.auxs)): if hash_int <= job.aux_targets[chain]: log.info("FOUND MERGED BLOCK! %s diff(%f/%f)" % (job.auxs[chain]['hash'], share_diff, self.diff_to_target(job.aux_targets[chain]))) coinbase_hex = binascii.hexlify(coinbase_bin) branch_count = job.merkletree.branchCount() branch_hex = job.merkletree.branchHex() merkle_link = util.calculate_merkle_link(job.merkle_hashes, job.tree[job.auxs[chain]['chainid']]) submission = coinbase_hex + block_hash_hex + branch_count + branch_hex + '00000000' + merkle_link + header_hex; aux_submit = self.aux_rpc.conns[chain].getauxblock(job.auxs[chain]['hash'], submission) aux_submit.addCallback(Interfaces.share_manager.on_submit_aux_block, worker_name, header_hex, job.auxs[chain]['hash'], submit_time, ip, share_diff) '''if aux_submit: self.update_auxs()''' if settings.SOLUTION_BLOCK_HASH: return (header_hex, block_hash_hex, share_diff, on_submit) else: return (header_hex, pow_hash_hex, share_diff, on_submit)
class TemplateRegistry(object): '''Implements the main logic of the pool. Keep track on valid block templates, provide internal interface for stratum service and implements block validation and submits.''' def __init__(self, block_template_class, coinbaser, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() log.debug("Got to Template Registry") self.coinbaser = coinbaser self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.update_in_progress = False self.last_update = None # Create first block template on startup self.update_block() def get_new_extranonce1(self): '''Generates unique extranonce1 (e.g. for newly subscribed connection.''' log.debug("Getting Unique Extronance") return self.extranonce_counter.get_new_bin() def get_last_broadcast_args(self): '''Returns arguments for mining.notify from last known template.''' log.debug("Getting Laat Template") return self.last_block.broadcast_args def add_template(self, block,block_height): '''Adds new template to the registry. It also clean up templates which should not be used anymore.''' prevhash = block.prevhash_hex if prevhash in self.prevhashes.keys(): new_block = False else: new_block = True self.prevhashes[prevhash] = [] # Blocks sorted by prevhash, so it's easy to drop # them on blockchain update self.prevhashes[prevhash].append(block) # Weak reference for fast lookup using job_id self.jobs[block.job_id] = block # Use this template for every new request self.last_block = block # Drop templates of obsolete blocks for ph in self.prevhashes.keys(): if ph != prevhash: del self.prevhashes[ph] log.info("New template for %s" % prevhash) if new_block: # Tell the system about new block # It is mostly important for share manager self.on_block_callback(prevhash, block_height) # Everything is ready, let's broadcast jobs! self.on_template_callback(new_block) #from twisted.internet import reactor #reactor.callLater(10, self.on_block_callback, new_block) def update_block(self): '''Registry calls the getblocktemplate() RPC and build new block template.''' if self.update_in_progress: # Block has been already detected return self.update_in_progress = True self.last_update = Interfaces.timestamper.time() d = self.bitcoin_rpc.getblocktemplate() d.addCallback(self._update_block) d.addErrback(self._update_block_failed) def _update_block_failed(self, failure): log.error(str(failure)) self.update_in_progress = False def _update_block(self, data): start = Interfaces.timestamper.time() template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id()) log.info(template.fill_from_rpc(data)) self.add_template(template,data['height']) log.info("Update finished, %.03f sec, %d txes" % \ (Interfaces.timestamper.time() - start, len(template.vtx))) self.update_in_progress = False return data def diff_to_target(self, difficulty): '''Converts difficulty to target''' if settings.COINDAEMON_ALGO == 'scrypt' or 'scrypt-jane': diff1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000 elif settings.COINDAEMON_ALGO == 'quark': diff1 = 0x000000ffff000000000000000000000000000000000000000000000000000000 else: diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000 return diff1 / difficulty def get_job(self, job_id): '''For given job_id returns BlockTemplate instance or None''' try: j = self.jobs[job_id] except: log.info("Job id '%s' not found" % job_id) return None # Now we have to check if job is still valid. # Unfortunately weak references are not bulletproof and # old reference can be found until next run of garbage collector. if j.prevhash_hex not in self.prevhashes: log.info("Prevhash of job '%s' is unknown" % job_id) return None if j not in self.prevhashes[j.prevhash_hex]: log.info("Job %s is unknown" % job_id) return None return j def submit_share(self, job_id, worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, difficulty): '''Check parameters and finalize block template. If it leads to valid block candidate, asynchronously submits the block back to the bitcoin network. - extranonce1_bin is binary. No checks performed, it should be from session data - job_id, extranonce2, ntime, nonce - in hex form sent by the client - difficulty - decimal number from session, again no checks performed - submitblock_callback - reference to method which receive result of submitblock() ''' # Check if extranonce2 looks correctly. extranonce2 is in hex form... if len(extranonce2) != self.extranonce2_size * 2: raise SubmitException("Incorrect size of extranonce2. Expected %d chars" % (self.extranonce2_size*2)) # Check for job job = self.get_job(job_id) if job == None: raise SubmitException("Job '%s' not found" % job_id) # Check if ntime looks correct if len(ntime) != 8: raise SubmitException("Incorrect size of ntime. Expected 8 chars") if not job.check_ntime(int(ntime, 16)): raise SubmitException("Ntime out of range") # Check nonce if len(nonce) != 8: raise SubmitException("Incorrect size of nonce. Expected 8 chars") # Check for duplicated submit if not job.register_submit(extranonce1_bin, extranonce2, ntime, nonce): log.info("Duplicate from %s, (%s %s %s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce)) raise SubmitException("Duplicate share") # Now let's do the hard work! # --------------------------- # 0. Some sugar extranonce2_bin = binascii.unhexlify(extranonce2) ntime_bin = binascii.unhexlify(ntime) nonce_bin = binascii.unhexlify(nonce) # 1. Build coinbase coinbase_bin = job.serialize_coinbase(extranonce1_bin, extranonce2_bin) coinbase_hash = util.doublesha(coinbase_bin) # 2. Calculate merkle root merkle_root_bin = job.merkletree.withFirst(coinbase_hash) merkle_root_int = util.uint256_from_str(merkle_root_bin) # 3. Serialize header with given merkle, ntime and nonce header_bin = job.serialize_header(merkle_root_int, ntime_bin, nonce_bin) # 4. Reverse header and compare it with target of the user if settings.COINDAEMON_ALGO == 'scrypt': hash_bin = ltc_scrypt.getPoWHash(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) elif settings.COINDAEMON_ALGO == 'scrypt-jane': hash_bin = yac_scrypt.getPoWHash(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ]), int(ntime, 16)) elif settings.COINDAEMON_ALGO == 'quark': hash_bin = quark_hash.getPoWHash(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) else: hash_bin = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) hash_int = util.uint256_from_str(hash_bin) scrypt_hash_hex = "%064x" % hash_int header_hex = binascii.hexlify(header_bin) if settings.COINDAEMON_ALGO == 'scrypt' or settings.COINDAEMON_ALGO == 'scrypt-jane': header_hex = header_hex+"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" elif settings.COINDAEMON_ALGO == 'quark': header_hex = header_hex+"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" else: pass target_user = self.diff_to_target(difficulty) if hash_int > target_user: raise SubmitException("Share is above target") # Mostly for debugging purposes target_info = self.diff_to_target(100000) if hash_int <= target_info: log.info("Yay, share with diff above 100000") # Algebra tells us the diff_to_target is the same as hash_to_diff share_diff = int(self.diff_to_target(hash_int)) # 5. Compare hash with target of the network if hash_int <= job.target: # Yay! It is block candidate! log.info("We found a block candidate! %s" % scrypt_hash_hex) # Reverse the header and get the potential block hash (for scrypt only) #if settings.COINDAEMON_ALGO == 'scrypt' or settings.COINDAEMON_ALGO == 'sha256d': # if settings.COINDAEMON_Reward == 'POW': block_hash_bin = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) block_hash_hex = block_hash_bin[::-1].encode('hex_codec') #else: block_hash_hex = hash_bin[::-1].encode('hex_codec') #else: block_hash_hex = hash_bin[::-1].encode('hex_codec') # 6. Finalize and serialize block object job.finalize(merkle_root_int, extranonce1_bin, extranonce2_bin, int(ntime, 16), int(nonce, 16)) if not job.is_valid(): # Should not happen log.exception("FINAL JOB VALIDATION FAILED!(Try enabling/disabling tx messages)") # 7. Submit block to the network serialized = binascii.hexlify(job.serialize()) #just try both block hash and scrypt hash when checking for block creation on_submit = self.bitcoin_rpc.submitblock(serialized, block_hash_hex, scrypt_hash_hex) if on_submit: self.update_block() if settings.SOLUTION_BLOCK_HASH: return (header_hex, block_hash_hex, share_diff, on_submit) else: return (header_hex, scrypt_hash_hex, share_diff, on_submit) if settings.SOLUTION_BLOCK_HASH: # Reverse the header and get the potential block hash (for scrypt only) only do this if we want to send in the block hash to the shares table block_hash_bin = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) block_hash_hex = block_hash_bin[::-1].encode('hex_codec') return (header_hex, block_hash_hex, share_diff, None) else: return (header_hex, scrypt_hash_hex, share_diff, None)
class TemplateRegistry(object): '''Implements the main logic of the pool. Keep track on valid block templates, provide internal interface for stratum service and implements block validation and submits.''' def __init__(self, block_template_class, coinbaser, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() self.coinbaser = coinbaser self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.update_in_progress = False self.last_update = None # Create first block template on startup self.update_block() def get_new_extranonce1(self): '''Generates unique extranonce1 (e.g. for newly subscribed connection.''' log.debug("Getting Unique Extranonce") return self.extranonce_counter.get_new_bin() def get_last_broadcast_args(self): '''Returns arguments for mining.notify from last known template.''' #log.debug("Getting Laat Template") return self.last_block.broadcast_args def add_template(self, block, block_height): '''Adds new template to the registry. It also clean up templates which should not be used anymore.''' prevhash = block.prevhash_hex if prevhash in self.prevhashes.keys(): new_block = False else: new_block = True self.prevhashes[prevhash] = [] # Blocks sorted by prevhash, so it's easy to drop # them on blockchain update self.prevhashes[prevhash].append(block) # Weak reference for fast lookup using job_id self.jobs[block.job_id] = block # Use this template for every new request self.last_block = block # Drop templates of obsolete blocks for ph in self.prevhashes.keys(): if ph != prevhash: del self.prevhashes[ph] log.info("New template for %s" % prevhash) if new_block: # Tell the system about new block # It is mostly important for share manager self.on_block_callback(block_height) # Everything is ready, let's broadcast jobs! self.on_template_callback(new_block) #from twisted.internet import reactor #reactor.callLater(10, self.on_block_callback, new_block) def update_block(self): '''Registry calls the getblocktemplate() RPC and build new block template.''' if self.update_in_progress: # Block has been already detected return self.update_in_progress = True self.last_update = Interfaces.timestamper.time() d = self.bitcoin_rpc.getblocktemplate() d.addCallback(self._update_block) d.addErrback(self._update_block_failed) def _update_block_failed(self, failure): log.error(str(failure)) self.update_in_progress = False def _update_block(self, data): start = Interfaces.timestamper.time() template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id()) template.fill_from_rpc(data) self.add_template(template, data['height']) log.info("Update finished, %.03f sec, %d txes" % \ (Interfaces.timestamper.time() - start, len(template.vtx))) self.update_in_progress = False return data def get_job(self, job_id): '''For given job_id returns BlockTemplate instance or None''' try: j = self.jobs[job_id] except: log.info("Job id '%s' not found" % job_id) return None # Now we have to check if job is still valid. # Unfortunately weak references are not bulletproof and # old reference can be found until next run of garbage collector. if j.prevhash_hex not in self.prevhashes: log.info("Prevhash of job '%s' is unknown" % job_id) return None if j not in self.prevhashes[j.prevhash_hex]: log.info("Job %s is unknown" % job_id) return None return j def submit_share(self, job_id, worker_name, session, extranonce1_bin, data, difficulty): job = self.get_job(job_id) if job == None: raise SubmitException("Job '%s' not found" % job_id) ntime = util.flip(data[136:144]) if not job.check_ntime(int(ntime, 16)): raise SubmitException("Ntime out of range") if not job.register_submit(data): log.info("Duplicate from %s, (%s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), data)) raise SubmitException("Duplicate share") hash_int = gapcoin_hash.getpowdiff(str(data)) block_hash_bin = util.doublesha(binascii.unhexlify(data[0:168])) block_hash_hex = util.rev(binascii.hexlify(block_hash_bin)) '''log.info("block_hash_hex %s" % block_hash_hex) log.info("shrint %s" % hash_int) log.info("jobint %s" % job.target)%f log.info("target %s" % difficulty)''' if hash_int < difficulty: raise SubmitException("Share less than target") share_diff = float(float(hash_int) / float(pow(2, 48))) if hash_int >= job.target: log.info("BLOCK CANDIDATE! %s" % block_hash_hex) extranonce2_bin = struct.pack('>L', 0) #self.last_block.vtx[0].set_extranonce(extranonce1_bin + extranonce2_bin) #txs = binascii.hexlify(util.ser_vector(self.last_block.vtx)) job.vtx[0].set_extranonce(extranonce1_bin + extranonce2_bin) txs = binascii.hexlify(util.ser_vector(job.vtx)) serialized = str(data) + str(txs) on_submit = self.bitcoin_rpc.submitblock(str(data), str(txs), block_hash_hex) if on_submit: self.update_block() return (block_hash_hex, share_diff, on_submit) return (block_hash_hex, share_diff, None)
class TemplateRegistry(object): '''Implements the main logic of the pool. Keep track on valid block templates, provide internal interface for stratum service and implements block validation and submits.''' def __init__(self, block_template_class, coinbaser, bitcoin_rpc, instance_id, on_template_callback, on_block_callback): self.prevhashes = {} self.jobs = weakref.WeakValueDictionary() self.extranonce_counter = ExtranonceCounter(instance_id) self.extranonce2_size = block_template_class.coinbase_transaction_class.extranonce_size \ - self.extranonce_counter.get_size() self.coinbaser = coinbaser self.block_template_class = block_template_class self.bitcoin_rpc = bitcoin_rpc self.on_block_callback = on_block_callback self.on_template_callback = on_template_callback self.last_block = None self.update_in_progress = False self.last_update = None self.last_update_force = None # Create first block template on startup self.update_block() def get_new_extranonce1(self): '''Generates unique extranonce1 (e.g. for newly subscribed connection.''' log.debug("Getting Unique Extranonce") return self.extranonce_counter.get_new_bin() def get_last_broadcast_args(self): '''Returns arguments for mining.notify from last known template.''' #log.debug("Getting Laat Template") return self.last_block.broadcast_args def add_template(self, block): '''Adds new template to the registry. It also clean up templates which should not be used anymore.''' if self.last_update_force == None: self.last_update_force = Interfaces.timestamper.time() prevhash = block.prevhash_hex if Interfaces.timestamper.time( ) - self.last_update_force >= settings.FORCE_REFRESH_INTERVAL: log.info("FORCED UPDATE!") new_block = True self.prevhashes[prevhash] = [] self.last_update_force = Interfaces.timestamper.time() elif prevhash in self.prevhashes.keys(): new_block = False else: new_block = True self.prevhashes[prevhash] = [] self.last_update_force = Interfaces.timestamper.time() # Blocks sorted by prevhash, so it's easy to drop # them on blockchain update self.prevhashes[prevhash].append(block) # Weak reference for fast lookup using job_id self.jobs[block.job_id] = block # Use this template for every new request self.last_block = block # Drop templates of obsolete blocks for ph in self.prevhashes.keys(): if ph != prevhash: del self.prevhashes[ph] log.info("New template for %s" % prevhash) if new_block: # Tell the system about new block # It is mostly important for share manager self.on_block_callback() # Everything is ready, let's broadcast jobs! self.on_template_callback(new_block) def update_block(self): '''Registry calls the getblocktemplate() RPC and build new block template.''' if self.update_in_progress: # Block has been already detected return self.update_in_progress = True self.last_update = Interfaces.timestamper.time() d = self.bitcoin_rpc.getblocktemplate() d.addCallback(self._update_block) d.addErrback(self._update_block_failed) def _update_block_failed(self, failure): log.error(str(failure)) self.update_in_progress = False def _update_block(self, data): start = Interfaces.timestamper.time() template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id()) template.fill_from_rpc(data) #log.info("%s\n", repr(template)) self.add_template(template) log.info("Update finished, %.03f sec, %d txes" % \ (Interfaces.timestamper.time() - start, len(template.vtx))) self.update_in_progress = False return data def get_job(self, job_id): '''For given job_id returns BlockTemplate instance or None''' if job_id == '00': log.info("Job %s is invalid" % job_id) return None try: j = self.jobs[job_id] except: log.info("Job id '%s' not found" % job_id) return None # Now we have to check if job is still valid. # Unfortunately weak references are not bulletproof and # old reference can be found until next run of garbage collector. if j.prevhash_hex not in self.prevhashes: log.info("Prevhash of job '%s' is unknown" % job_id) return None if j not in self.prevhashes[j.prevhash_hex]: log.info("Job %s is unknown" % job_id) return None return j def diff_to_target(self, difficulty): '''Converts difficulty to target''' diff1 = 0x000000ffff000000000000000000000000000000000000000000000000000000000000000000000 return float(diff1 * 16) / float(difficulty) def submit_share(self, job_id, worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, difficulty): # Check for job job = self.get_job(job_id) if job == None: raise SubmitException("Job '%s' not found" % job_id) nonce = util.rev(nonce) ntime = util.rev(ntime) extranonce2_bin = binascii.unhexlify(extranonce2) ntime_bin = binascii.unhexlify(ntime) nonce_bin = binascii.unhexlify(nonce) # Check if extranonce2 looks correctly. extranonce2 is in hex form... if len(extranonce2) != self.extranonce2_size * 2: raise SubmitException( "Incorrect size of extranonce2. Expected %d chars" % (self.extranonce2_size * 2)) # Check if ntime looks correct if len(ntime) != 8: raise SubmitException("Incorrect size of ntime. Expected 8 chars") if not job.check_ntime(int(ntime, 16)): raise SubmitException("Ntime out of range") # Check nonce if len(nonce) != 8: raise SubmitException("Incorrect size of nonce. Expected 8 chars") # Check for duplicated submit if not job.register_submit(extranonce1_bin, extranonce2, ntime, nonce): log.info("Duplicate from %s, (%s %s %s %s)" % \ (worker_name, binascii.hexlify(extranonce1_bin), extranonce2, ntime, nonce)) raise SubmitException("Duplicate share") # 1. Build coinbase coinbase_bin = job.serialize_coinbase(extranonce1_bin, extranonce2_bin) coinbase_hash = kshake320_hash.getHash320(coinbase_bin) # 2. Calculate merkle root merkle_root_bin = job.merkletree.withFirst(coinbase_hash) merkle_root_int = util.uint320_from_str(merkle_root_bin) # 3. Serialize header with given merkle, ntime and nonce header_bin = job.serialize_header(merkle_root_bin, int(ntime, 16), int(nonce, 16)) header_hex = binascii.hexlify(header_bin) header_hex = header_hex + "0000000000000000" # 4. Reverse header and compare it with target of the user hash_bin = kshake320_hash.getPoWHash(header_bin) hash_int = util.uint320_from_str(hash_bin) hash_hex = "%080x" % hash_int block_hash_hex = hash_bin[::-1].encode('hex_codec') target_user = float(self.diff_to_target(difficulty)) if hash_int > target_user: log.info("ABOVE TARGET!") raise SubmitException("Share above target") target_info = self.diff_to_target(50) if hash_int <= target_info: log.info("YAY, share with diff above 50") # Algebra tells us the diff_to_target is the same as hash_to_diff share_diff = float(self.diff_to_target(hash_int)) if hash_int <= job.target: # Yay! It is block candidate! log.info("BLOCK CANDIDATE! %s" % block_hash_hex) # Finalize and serialize block object job.finalize(merkle_root_int, extranonce1_bin, extranonce2_bin, int(ntime, 16), int(nonce, 16)) if not job.is_valid(): # Should not happen log.info("FINAL JOB VALIDATION FAILED!") # Submit block to the network '''serialized = binascii.hexlify(job.serialize()) on_submit = self.bitcoin_rpc.submitblock(str(serialized), block_hash_hex)''' job.vtx[0].set_extranonce(extranonce1_bin + extranonce2_bin) txs = binascii.hexlify(util.ser_vector(job.vtx)) on_submit = self.bitcoin_rpc.submitblock_wtxs( str(header_hex), str(txs), block_hash_hex) '''if on_submit: self.update_block()''' return (block_hash_hex, share_diff, on_submit) return (block_hash_hex, share_diff, None)