import util
import StringIO
+from twisted.internet import defer
+from lib.exceptions import SubmitException
+
import stratum.logger
log = stratum.logger.get_logger('template_registry')
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()
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
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.'''
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)
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)))
def diff_to_target(self, difficulty):
'''Converts difficulty to target'''
- diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000
+ diff1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000
return diff1 / difficulty
def get_job(self, job_id):
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.
# 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!
# ---------------------------
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 = self.diff_to_target(difficulty)
-
- if hash_int > target:
- return (False, "Share is above target", None, None)
+ 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:
+ 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:
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)
-
- # 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)
+ on_submit = self.bitcoin_rpc.submitblock(serialized)
- 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)