Typo fix
[stratum-mining.git] / lib / template_registry.py
index 5fc5124..25dad09 100644 (file)
@@ -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')
 
@@ -27,7 +30,7 @@ class TemplateRegistry(object):
     service and implements block validation and submits.'''
     
     def __init__(self, block_template_class, coinbaser, bitcoin_rpc, instance_id,
-                 on_block_callback):
+                 on_template_callback, on_block_callback):
         self.prevhashes = {}
         self.jobs = weakref.WeakValueDictionary()
         
@@ -39,6 +42,7 @@ class TemplateRegistry(object):
         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
@@ -57,7 +61,7 @@ class TemplateRegistry(object):
         from last known template.'''
         return self.last_block.broadcast_args
         
-    def add_template(self, block):
+    def add_template(self, block,block_height):
         '''Adds new template to the registry.
         It also clean up templates which should
         not be used anymore.'''
@@ -86,7 +90,16 @@ class TemplateRegistry(object):
                 del self.prevhashes[ph]
                 
         log.info("New template for %s" % prevhash)
-        self.on_block_callback(new_block)
+
+        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) 
               
@@ -114,7 +127,7 @@ class TemplateRegistry(object):
                 
         template = self.block_template_class(Interfaces.timestamper, self.coinbaser, JobIdGenerator.get_new_id())
         template.fill_from_rpc(data)
-        self.add_template(template)
+        self.add_template(template,data['height'])
 
         log.info("Update finished, %.03f sec, %d txes" % \
                     (Interfaces.timestamper.time() - start, len(template.vtx)))
@@ -124,7 +137,7 @@ class TemplateRegistry(object):
     
     def diff_to_target(self, difficulty):
         '''Converts difficulty to target'''
-        diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000 
+        diff1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000 
         return diff1 / difficulty
     
     def get_job(self, job_id):
@@ -133,23 +146,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):
+    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.
@@ -162,33 +175,34 @@ 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")
+
+        # normalize the case to prevent duplication of valid shares by the client
+        ntime = ntime.lower()
+        nonce = nonce.lower()
+        extranonce2 = extranonce2.lower()
+
         # 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!
         # ---------------------------
@@ -210,20 +224,25 @@ 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 = util.doublesha(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ]))
+        hash_bin = util.scrypt(''.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:
-            return (False, "Share is above target", None, None)
+        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:
             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! 
@@ -235,32 +254,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, share_diff, 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, share_diff, None)