From 60bb044bc716de1e24e133c237866c457f296e9b Mon Sep 17 00:00:00 2001 From: slush Date: Mon, 10 Sep 2012 07:23:42 +0000 Subject: [PATCH] Improved exception handling of submit() share_manager.on_submit_block() is called after on_submit_share() now --- lib/template_registry.py | 62 +++++++++++++++------------------------------- mining/interfaces.py | 2 +- mining/service.py | 31 ++++++++++++++--------- 3 files changed, 40 insertions(+), 55 deletions(-) diff --git a/lib/template_registry.py b/lib/template_registry.py index 5fc5124..cab911f 100644 --- a/lib/template_registry.py +++ b/lib/template_registry.py @@ -3,6 +3,9 @@ import binascii import util import StringIO +from twisted.internet import defer +from lib.exceptions import SubmitException + import stratum.logger log = stratum.logger.get_logger('template_registry') @@ -133,23 +136,23 @@ class TemplateRegistry(object): j = self.jobs[job_id] except: log.info("Job id '%s' not found" % job_id) - return False + 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 False + return None if j not in self.prevhashes[j.prevhash_hex]: log.info("Job %s is unknown" % job_id) - return False + return None - return True + return j def submit_share(self, job_id, worker_name, extranonce1_bin, extranonce2, ntime, nonce, - difficulty, submitblock_callback): + difficulty): '''Check parameters and finalize block template. If it leads to valid block candidate, asynchronously submits the block back to the bitcoin network. @@ -162,33 +165,29 @@ class TemplateRegistry(object): # Check if extranonce2 looks correctly. extranonce2 is in hex form... if len(extranonce2) != self.extranonce2_size * 2: - return (False, "Incorrect size of extranonce2. Expected %d chars" % (self.extranonce2_size*2), None, None) + raise SubmitException("Incorrect size of extranonce2. Expected %d chars" % (self.extranonce2_size*2)) # Check for job - if not self.get_job(job_id): - return (False, "Job '%s' not found" % job_id, None, None) - - try: - job = self.jobs[job_id] - except KeyError: - return (False, "Job '%s' not found" % job_id, None, None) + 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: - return (False, "Incorrect size of ntime. Expected 8 chars", None, None) + raise SubmitException("Incorrect size of ntime. Expected 8 chars") if not job.check_ntime(int(ntime, 16)): - return (False, "Ntime out of range", None, None) + raise SubmitException("Ntime out of range") # Check nonce if len(nonce) != 8: - return (False, "Incorrect size of nonce. Expected 8 chars", None, None) + 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)) - return (False, "Duplicate share", None, None) + raise SubmitException("Duplicate share") # Now let's do the hard work! # --------------------------- @@ -217,7 +216,7 @@ class TemplateRegistry(object): target_user = self.diff_to_target(difficulty) if hash_int > target_user: - return (False, "Share is above target", None, None) + raise SubmitException("Share is above target") # Mostly for debugging purposes target_info = self.diff_to_target(100000) @@ -235,32 +234,11 @@ class TemplateRegistry(object): if not job.is_valid(): # Should not happen log.error("Final job validation failed!") - return (False, 'Job validation failed', header_hex, block_hash_hex) # 7. Submit block to the network - submit_time = Interfaces.timestamper.time() serialized = binascii.hexlify(job.serialize()) - d = self.bitcoin_rpc.submitblock(serialized) + on_submit = self.bitcoin_rpc.submitblock(serialized) - # Submit is lazy, we don't need to wait for the result - # Callback will just register success or failure to share manager - d.addCallback(self._on_submitblock, submitblock_callback, - worker_name, header_hex, block_hash_hex, submit_time) - d.addErrback(self._on_submitblock_failure, submitblock_callback, - worker_name, header_hex, block_hash_hex, submit_time) - - return (True, '', header_hex, block_hash_hex) + return (header_hex, block_hash_hex, on_submit) - return (True, '', header_hex, block_hash_hex) - - def _on_submitblock(self, is_accepted, callback, worker_name, block_header, block_hash, timestamp): - '''Helper method, bridges call from deferred to method reference given in submit()''' - # Forward submitblock result to share manager - callback(worker_name, block_header, block_hash, timestamp, is_accepted) - return is_accepted - - def _on_submitblock_failure(self, failure, callback, worker_name, block_header, block_hash, timestamp): - '''Helper method, bridges call from deferred to method reference given in submit()''' - # Forward submitblock failure to share manager - callback(worker_name, block_header, block_hash, timestamp, False) - log.exception(failure) \ No newline at end of file + return (header_hex, block_hash_hex, None) \ No newline at end of file diff --git a/mining/interfaces.py b/mining/interfaces.py index 88f5417..179c85e 100644 --- a/mining/interfaces.py +++ b/mining/interfaces.py @@ -28,7 +28,7 @@ class ShareManagerInterface(object): def on_submit_share(self, worker_name, block_header, block_hash, shares, timestamp, is_valid): log.info("%s %s %s" % (block_hash, 'valid' if is_valid else 'INVALID', worker_name)) - def on_submit_block(self, worker_name, block_header, block_hash, timestamp, is_accepted): + def on_submit_block(self, is_accepted, worker_name, block_header, block_hash, timestamp): log.info("Block %s %s" % (block_hash, 'ACCEPTED' if is_accepted else 'REJECTED')) class TimestamperInterface(object): diff --git a/mining/service.py b/mining/service.py index 5330662..d9efa95 100644 --- a/mining/service.py +++ b/mining/service.py @@ -2,16 +2,13 @@ import binascii from twisted.internet import defer from stratum.services import GenericService, admin -from stratum.custom_exceptions import ServiceException from stratum.pubsub import Pubsub from interfaces import Interfaces from subscription import MiningSubscription +from lib.exceptions import SubmitException import stratum.logger log = stratum.logger.get_logger('mining') - -class SubmitException(ServiceException): - pass class MiningService(GenericService): '''This service provides public API for Stratum mining proxy @@ -95,18 +92,28 @@ class MiningService(GenericService): difficulty = session['difficulty'] + submit_time = Interfaces.timestamper.time() + # This checks if submitted share meet all requirements # and it is valid proof of work. - (is_valid, reason, block_header, block_hash) = Interfaces.template_registry.submit_share(job_id, - worker_name, extranonce1_bin, extranonce2, ntime, nonce, difficulty, - Interfaces.share_manager.on_submit_block) - - # block_header and block_hash may be None when submitted data are corrupted + try: + (block_header, block_hash, on_submit) = Interfaces.template_registry.submit_share(job_id, + worker_name, extranonce1_bin, extranonce2, ntime, nonce, difficulty) + except SubmitException: + # block_header and block_hash are None when submitted data are corrupted + Interfaces.share_manager.on_submit_share(worker_name, None, None, difficulty, + submit_time, False) + raise + + Interfaces.share_manager.on_submit_share(worker_name, block_header, block_hash, difficulty, - Interfaces.timestamper.time(), is_valid) + submit_time, True) - if not is_valid: - raise SubmitException(reason) + if on_submit != None: + # Pool performs submitblock() to bitcoind. Let's hook + # to result and report it to share manager + on_submit.addCallback(Interfaces.share_manager.on_submit_block, + worker_name, block_header, block_hash, submit_time) return True -- 1.7.1