Merge pull request #206 from ahmedbodi/issue-92

Issue 92
This commit is contained in:
ahmedbodi 2014-01-30 15:04:55 -08:00
commit 1e8a69d60d
5 changed files with 14 additions and 301 deletions

View File

@ -4,7 +4,7 @@ Stratum-mining is a pooled mining protocol. It is a replacement for *getwork* ba
This is a implementation of stratum-mining for scrypt based coins. It is compatible with *MPOS* as it complies with the standards of *pushpool*. The end goal is to build on these standards to come up with a more stable solution. This is a implementation of stratum-mining for scrypt based coins. It is compatible with *MPOS* as it complies with the standards of *pushpool*. The end goal is to build on these standards to come up with a more stable solution.
The goal is to make a reliable stratum mining server for scrypt based coins. Over time I will develop this to be more feature rich and very stable. If you would like to see a feature please file a feature request. The goal is to make a reliable stratum mining server for a wide range of coins unlike other forks where the code is limited to specific algorithm's. Over time I will develop this to be more feature rich and very stable. If you would like to see a feature please file a feature request.
**NOTE:** This fork is still in development. Many features may be broken. Please report any broken features or issues. **NOTE:** This fork is still in development. Many features may be broken. Please report any broken features or issues.
@ -14,7 +14,6 @@ The goal is to make a reliable stratum mining server for scrypt based coins. Ove
* Solved Block Confirmation * Solved Block Confirmation
* Job Based Vardiff support * Job Based Vardiff support
* Solution Block Hash Support * Solution Block Hash Support
* *NEW* SHA256 and Scrypt Algo Support
* Log Rotation * Log Rotation
* Initial low difficulty share confirmation * Initial low difficulty share confirmation
* Multiple *coind* wallets * Multiple *coind* wallets
@ -34,7 +33,7 @@ The goal is to make a reliable stratum mining server for scrypt based coins. Ove
* Doge: DLtBRYtNCzfiZfcpUeEr8KPvy5k1aR7jca * Doge: DLtBRYtNCzfiZfcpUeEr8KPvy5k1aR7jca
* SRC: sMP2wHN5H2ik7FQDPjhSzFZUWux75BYZGe * SRC: sMP2wHN5H2ik7FQDPjhSzFZUWux75BYZGe
* ARG: AQvXPWVqGzcpH2j2XSRG7X5R9nA3y9D9aQ * ARG: AQvXPWVqGzcpH2j2XSRG7X5R9nA3y9D9aQ
* CryptsyTradeKey: ec13d183e304326ebd41258d6ae7188e303866fe * Cryptsy Trade Key: ec13d183e304326ebd41258d6ae7188e303866fe
#Requirements #Requirements
@ -76,10 +75,9 @@ Please research and attempt to debug first.
#Credits #Credits
* Original version by Slush0 (original stratum code) * Original version by Slush0 and ArtForz (original stratum code)
* More Features added by GeneralFault, Wadee Womersley and Moopless * More Features added by GeneralFault, Wadee Womersley, Viperaus, TheSeven and Moopless
* Scrypt conversion from work done by viperaus * Scrypt conversion from work done by viperaus
* PoS conversion done by TheSeven
* Multi Algo, Vardiff, DB and MPOS support done by Ahmed_Bodi and Obigal * Multi Algo, Vardiff, DB and MPOS support done by Ahmed_Bodi and Obigal
#License #License

View File

@ -23,6 +23,7 @@ class BitcoinRPC(object):
} }
client.HTTPClientFactory.noisy = False client.HTTPClientFactory.noisy = False
self.has_submitblock = False self.has_submitblock = False
def _call_raw(self, data): def _call_raw(self, data):
client.Headers client.Headers
return client.getPage( return client.getPage(
@ -45,7 +46,7 @@ class BitcoinRPC(object):
try: try:
log.info("Checking for submitblock") log.info("Checking for submitblock")
resp = (yield self._call('submitblock', [])) resp = (yield self._call('submitblock', []))
self.has_submitblock = Trie self.has_submitblock = True
except Exception as e: except Exception as e:
if (str(e) == "404 Not Found"): if (str(e) == "404 Not Found"):
log.debug("No submitblock detected.") log.debug("No submitblock detected.")
@ -61,7 +62,7 @@ class BitcoinRPC(object):
@defer.inlineCallbacks @defer.inlineCallbacks
def submitblock(self, block_hex, hash_hex): def submitblock(self, block_hex, hash_hex, scrypt_hex):
#try 5 times? 500 Internal Server Error could mean random error or that TX messages setting is wrong #try 5 times? 500 Internal Server Error could mean random error or that TX messages setting is wrong
attempts = 0 attempts = 0
while True: while True:
@ -116,7 +117,7 @@ class BitcoinRPC(object):
if json.loads(resp)['result'] == None: if json.loads(resp)['result'] == None:
# make sure the block was created. # make sure the block was created.
log.info("CHECKING FOR BLOCK AFTER SUBMITBLOCK") log.info("CHECKING FOR BLOCK AFTER SUBMITBLOCK")
defer.returnValue((yield self.blockexists(hash_hex, block_hex))) defer.returnValue((yield self.blockexists(hash_hex, scrypt_hex)))
else: else:
defer.returnValue(False) defer.returnValue(False)

View File

@ -42,6 +42,7 @@ class BitcoinRPCManager(object):
if len(self.conns) <= 1: if len(self.conns) <= 1:
log.error("Problem with Pool 0 -- NO ALTERNATE POOLS!!!") log.error("Problem with Pool 0 -- NO ALTERNATE POOLS!!!")
time.sleep(4) time.sleep(4)
self.curr_conn = 0
return return
log.error("Problem with Pool %i Switching to Next!" % (self.curr_conn) ) log.error("Problem with Pool %i Switching to Next!" % (self.curr_conn) )
self.curr_conn = self.curr_conn + 1 self.curr_conn = self.curr_conn + 1
@ -89,11 +90,11 @@ class BitcoinRPCManager(object):
return self.conns[self.curr_conn]._call(method,params) return self.conns[self.curr_conn]._call(method,params)
except: except:
self.next_connection() self.next_connection()
def check_submitblock(self): def check_submitblock(self):
while True: while True:
try: try:
return self.conns[self.curr_conn].check_submitblock() return self.conns[self.curr_conn].check_submitblock()
except: except:
self.next_connection() self.next_connection()
def submitblock(self, block_hex, hash_hex, scrypt_hex): def submitblock(self, block_hex, hash_hex, scrypt_hex):

View File

@ -1,288 +0,0 @@
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())
print("hit template registry")
template.fill_from_rpc(data) 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:
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)
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)

View File

@ -82,6 +82,7 @@ class ShareManagerInterface(object):
def on_submit_block(self, is_accepted, worker_name, block_header, block_hash, timestamp, ip, share_diff): def on_submit_block(self, is_accepted, worker_name, block_header, block_hash, timestamp, ip, share_diff):
log.info("Block %s %s" % (block_hash, 'ACCEPTED' if is_accepted else 'REJECTED')) log.info("Block %s %s" % (block_hash, 'ACCEPTED' if is_accepted else 'REJECTED'))
dbi.do_import(True)
dbi.found_block([worker_name, block_header, block_hash, -1, timestamp, is_accepted, ip, self.block_height, self.prev_hash, share_diff ]) dbi.found_block([worker_name, block_header, block_hash, -1, timestamp, is_accepted, ip, self.block_height, self.prev_hash, share_diff ])
class TimestamperInterface(object): class TimestamperInterface(object):