diff --git a/lib/bitcoin_rpc.py b/lib/bitcoin_rpc.py index 63be393..3d65ea1 100644 --- a/lib/bitcoin_rpc.py +++ b/lib/bitcoin_rpc.py @@ -43,12 +43,14 @@ class BitcoinRPC(object): def submitblock(self, block_hex, block_hash_hex): # Try submitblock if that fails, go to getblocktemplate try: + print("Submitting Block with Submit Block ") resp = (yield self._call('submitblock', [block_hex,])) except Exception: try: + print("Submit Block call failed, trying GetBlockTemplate") resp = (yield self._call('getblocktemplate', [{'mode': 'submit', 'data': block_hex}])) except Exception as e: - log.exception("Problem Submitting block %s" % str(e)) + log.exception("Both SubmitBlock and GetBlockTemplate failed. Problem Submitting block %s" % str(e)) raise if json.loads(resp)['result'] == None: diff --git a/lib/config_default.py b/lib/config_default.py index e156491..5f96e58 100755 --- a/lib/config_default.py +++ b/lib/config_default.py @@ -111,6 +111,12 @@ COINDAEMON_TRUSTED_PORT = 8332 # RPC port COINDAEMON_TRUSTED_USER = 'stratum' COINDAEMON_TRUSTED_PASSWORD = '***somepassword***' +# Coin Algorithm is the option used to determine the algortithm used by stratum +# This currently only works with POW SHA256 and Scrypt Coins +# The available options are scrypt and sha256d. +# If the option does not meet either of these criteria stratum defaults to scrypt +COINDAEMON_ALGO = 'scrypt' + # ******************** OTHER CORE SETTINGS ********************* # Use "echo -n '' | sha256sum | cut -f1 -d' ' " # for calculating SHA256 of your preferred password diff --git a/lib/halfnode.py b/lib/halfnode.py index d64b4f8..576ea8b 100644 --- a/lib/halfnode.py +++ b/lib/halfnode.py @@ -15,7 +15,14 @@ from Crypto.Hash import SHA256 from twisted.internet.protocol import Protocol from util import * -import ltc_scrypt +import settings +if settings.COINDAEMON_ALGO == 'scrypt': + print("########################################### Loading LTC Scrypt Module #########################################################") + import ltc_scrypt +else: + print("########################################### NOT Loading LTC Scrypt Module ######################################################") + pass + import lib.logger log = lib.logger.get_logger('halfnode') @@ -174,7 +181,9 @@ class CBlock(object): self.nNonce = 0 self.vtx = [] self.sha256 = None - self.scrypt = None + if settings.COINDAEMON_ALGO == 'scrypt': + self.scrypt = None + else: pass def deserialize(self, f): self.nVersion = struct.unpack(" target: - if self.scrypt > target: - return False + if settings.COINDAEMON_ALGO == 'scrypt': + if self.scrypt > target: + return false + else: + if self.sha256 > target: + return False hashes = [] for tx in self.vtx: tx.sha256 = None diff --git a/lib/template_registry.py b/lib/template_registry.py index 434939a..34f6d7c 100644 --- a/lib/template_registry.py +++ b/lib/template_registry.py @@ -2,8 +2,10 @@ import weakref import binascii import util import StringIO -import ltc_scrypt - +import settings +if settings.COINDAEMON_ALGO == 'scrypt': + import ltc_scrypt +else: pass from twisted.internet import defer from lib.exceptions import SubmitException @@ -140,9 +142,10 @@ class TemplateRegistry(object): def diff_to_target(self, difficulty): '''Converts difficulty to target''' - #diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000 - diff1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000 - return diff1 / difficulty + if settings.COINDAEMON_ALGO == 'scrypt': + diff1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000 + else: diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000 + return diff1 / difficulty def get_job(self, job_id): '''For given job_id returns BlockTemplate instance or None''' @@ -223,12 +226,15 @@ class TemplateRegistry(object): 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 = ltc_scrypt.getPoWHash(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ])) + 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) ])) + 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) - header_hex = header_hex+"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" - + if settings.COINDAEMON_ALGO == 'scrypt': + header_hex = header_hex+"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" + else: pass target_user = self.diff_to_target(difficulty) if hash_int > target_user and \ @@ -245,16 +251,16 @@ class TemplateRegistry(object): share_diff = int(self.diff_to_target(hash_int)) - # 5. Compare hash with target of the network + # 5. Compare hash with target of the network if hash_int <= job.target: - # Yay! It is block candidate! + # 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) + # Reverse the header and get the potential block hash (for scrypt only) 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 + # 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(): diff --git a/lib/template_registry.py.save b/lib/template_registry.py.save new file mode 100644 index 0000000..da1dfe9 --- /dev/null +++ b/lib/template_registry.py.save @@ -0,0 +1,287 @@ +import weakref +import binascii +import util +import StringIO +import settings +if settings.COINDAEMON_ALGO == 'scrypt': + import ltc_scrypt +else: pass +from twisted.internet import defer +from lib.exceptions import SubmitException + +import lib.logger +log = lib.logger.get_logger('template_registry') + +from mining.interfaces import Interfaces +from extranonce_counter import ExtranonceCounter +import lib.settings as settings + + +class JobIdGenerator(object): + '''Generate pseudo-unique job_id. It does not need to be absolutely unique, + because pool sends "clean_jobs" flag to clients and they should drop all previous jobs.''' + counter = 0 + + @classmethod + def get_new_id(cls): + cls.counter += 1 + if cls.counter % 0xffff == 0: + cls.counter = 1 + return "%x" % cls.counter + +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,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()) + 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': + diff1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000 + 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) ])) + 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': + header_hex = header_hex+"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" + else: pass + + target_user = self.diff_to_target(difficulty) + if hash_int > target_user and \ + ( 'prev_jobid' not in session or session['prev_jobid'] < job_id \ + or 'prev_diff' not in session or hash_int > self.diff_to_target(session['prev_diff']) ): + raise SubmitException("Share is above target") + + # Mostly for debugging purposes + target_info = self.diff_to_target(100000) + if hash_int <= target_info: + print("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! + print("We found a block candidate! %s" % scrypt_hash_hex) + + # Reverse the header and get the potential block hash (for scrypt only) + 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.error("Final job validation failed!") + + # 7. Submit block to the network + serialized = binascii.hexlify(job.serialize()) + 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)