diff --git a/conf/config_sample.py b/conf/config_sample.py index fd52406..c3ba8bd 100644 --- a/conf/config_sample.py +++ b/conf/config_sample.py @@ -21,11 +21,8 @@ COINDAEMON_TRUSTED_PASSWORD = 'somepassword' # The available options are: # scrypt, sha256d, scrypt-jane and quark # If the option does not meet either of these criteria stratum defaults to scrypt -# Until AutoReward Selecting Code has been implemented the below options are used to select the type of coin -# For Reward type there is POW and POS. please ensure you choose the currect type. # For Coins which support TX Messages please enter yes in the TX selection COINDAEMON_ALGO = 'scrypt' -COINDAEMON_Reward = 'POW' COINDAEMON_TX = 'no' # ******************** BASIC SETTINGS *************** # Backup Coin Daemon address's (consider having at least 1 backup) @@ -191,3 +188,11 @@ NOTIFY_EMAIL_SERVER = 'localhost' # E-Mail Sender NOTIFY_EMAIL_USERNAME = '' # E-Mail server SMTP Logon NOTIFY_EMAIL_PASSWORD = '' NOTIFY_EMAIL_USETLS = True + +#### Memcache #### +# Memcahce is a requirement. Enter the settings below +MEMCACHE_HOST = "localhost" # hostname or IP that runs memcached +MEMCACHE_PORT = 11211 # Port +MEMCACHE_TIMEOUT = 900 # Key timeout +MEMCACHE_PREFIX = "stratum_" # Prefix for keys + diff --git a/lib/block_template.py b/lib/block_template.py index 4e00675..9a55f50 100644 --- a/lib/block_template.py +++ b/lib/block_template.py @@ -5,6 +5,8 @@ import struct import util import merkletree import halfnode +from coinbasetx import CoinbaseTransactionPOW +from coinbasetx import CoinbaseTransactionPOS from coinbasetx import CoinbaseTransaction import lib.logger log = lib.logger.get_logger('block_template') @@ -55,10 +57,10 @@ class BlockTemplate(halfnode.CBlock): txhashes = [None] + [ util.ser_uint256(int(t['hash'], 16)) for t in data['transactions'] ] mt = merkletree.MerkleTree(txhashes) if settings.COINDAEMON_Reward == 'POW': - coinbase = self.coinbase_transaction_class(self.timestamper, self.coinbaser, data['coinbasevalue'], data['coinbaseaux']['flags'], data['height'], + coinbase = CoinbaseTransactionPOW(self.timestamper, self.coinbaser, data['coinbasevalue'], data['coinbaseaux']['flags'], data['height'], settings.COINBASE_EXTRAS) else: - coinbase = self.coinbase_transaction_class(self.timestamper, self.coinbaser, data['coinbasevalue'], data['coinbaseaux']['flags'], data['height'], + coinbase = CoinbaseTransactionPOS(self.timestamper, self.coinbaser, data['coinbasevalue'], data['coinbaseaux']['flags'], data['height'], settings.COINBASE_EXTRAS, data['curtime']) self.height = data['height'] diff --git a/lib/coinbasetx.py b/lib/coinbasetx.py index 4f3c187..905582b 100644 --- a/lib/coinbasetx.py +++ b/lib/coinbasetx.py @@ -6,8 +6,8 @@ import settings import lib.logger log = lib.logger.get_logger('coinbasetx') -if settings.COINDAEMON_Reward == 'POW': - class CoinbaseTransaction(halfnode.CTransaction): +#if settings.COINDAEMON_Reward == 'POW': +class CoinbaseTransactionPOW(halfnode.CTransaction): '''Construct special transaction used for coinbase tx. It also implements quick serialization using pre-cached scriptSig template.''' @@ -17,8 +17,8 @@ if settings.COINDAEMON_Reward == 'POW': extranonce_size = struct.calcsize(extranonce_type) def __init__(self, timestamper, coinbaser, value, flags, height, data): - super(CoinbaseTransaction, self).__init__() - log.debug("Got to CoinBaseTX") + super(CoinbaseTransactionPOW, self).__init__() + log.debug("Got to CoinBaseTX") #self.extranonce = 0 if len(self.extranonce_placeholder) != self.extranonce_size: @@ -45,7 +45,7 @@ if settings.COINDAEMON_Reward == 'POW': self.vout.append(tx_out) # Two parts of serialized coinbase, just put part1 + extranonce + part2 to have final serialized tx - self._serialized = super(CoinbaseTransaction, self).serialize().split(self.extranonce_placeholder) + self._serialized = super(CoinbaseTransactionPOW, self).serialize().split(self.extranonce_placeholder) def set_extranonce(self, extranonce): if len(extranonce) != self.extranonce_size: @@ -53,8 +53,56 @@ if settings.COINDAEMON_Reward == 'POW': (part1, part2) = self.vin[0]._scriptSig_template self.vin[0].scriptSig = part1 + extranonce + part2 -elif settings.COINDAEMON_Reward == 'POS': - class CoinbaseTransaction(halfnode.CTransaction): +#elif settings.COINDAEMON_Reward == 'POS': +class CoinbaseTransactionPOS(halfnode.CTransaction): + '''Construct special transaction used for coinbase tx. + It also implements quick serialization using pre-cached + scriptSig template.''' + + extranonce_type = '>Q' + extranonce_placeholder = struct.pack(extranonce_type, int('f000000ff111111f', 16)) + extranonce_size = struct.calcsize(extranonce_type) + + def __init__(self, timestamper, coinbaser, value, flags, height, data, ntime): + super(CoinbaseTransactionPOS, self).__init__() + log.debug("Got to CoinBaseTX") + #self.extranonce = 0 + + if len(self.extranonce_placeholder) != self.extranonce_size: + raise Exception("Extranonce placeholder don't match expected length!") + + tx_in = halfnode.CTxIn() + tx_in.prevout.hash = 0L + tx_in.prevout.n = 2**32-1 + tx_in._scriptSig_template = ( + util.ser_number(height) + binascii.unhexlify(flags) + util.ser_number(int(timestamper.time())) + \ + chr(self.extranonce_size), + util.ser_string(coinbaser.get_coinbase_data() + data) + ) + + tx_in.scriptSig = tx_in._scriptSig_template[0] + self.extranonce_placeholder + tx_in._scriptSig_template[1] + + tx_out = halfnode.CTxOut() + tx_out.nValue = value + tx_out.scriptPubKey = coinbaser.get_script_pubkey() + + self.nTime = ntime + if settings.COINDAEMON_SHA256_TX == 'yes': + self.strTxComment = "http://github.com/ahmedbodi/stratum-mining" + self.vin.append(tx_in) + self.vout.append(tx_out) + + # Two parts of serialized coinbase, just put part1 + extranonce + part2 to have final serialized tx + self._serialized = super(CoinbaseTransactionPOS, self).serialize().split(self.extranonce_placeholder) + + def set_extranonce(self, extranonce): + if len(extranonce) != self.extranonce_size: + raise Exception("Incorrect extranonce size") + + (part1, part2) = self.vin[0]._scriptSig_template + self.vin[0].scriptSig = part1 + extranonce + part2 +#else: +class CoinbaseTransaction(halfnode.CTransaction): '''Construct special transaction used for coinbase tx. It also implements quick serialization using pre-cached scriptSig template.''' @@ -86,54 +134,6 @@ elif settings.COINDAEMON_Reward == 'POS': tx_out.nValue = value tx_out.scriptPubKey = coinbaser.get_script_pubkey() - self.nTime = ntime - if settings.COINDAEMON_SHA256_TX == 'yes': - self.strTxComment = "http://github.com/ahmedbodi/stratum-mining" - self.vin.append(tx_in) - self.vout.append(tx_out) - - # Two parts of serialized coinbase, just put part1 + extranonce + part2 to have final serialized tx - self._serialized = super(CoinbaseTransaction, self).serialize().split(self.extranonce_placeholder) - - def set_extranonce(self, extranonce): - if len(extranonce) != self.extranonce_size: - raise Exception("Incorrect extranonce size") - - (part1, part2) = self.vin[0]._scriptSig_template - self.vin[0].scriptSig = part1 + extranonce + part2 -else: - class CoinbaseTransaction(halfnode.CTransaction): - '''Construct special transaction used for coinbase tx. - It also implements quick serialization using pre-cached - scriptSig template.''' - - extranonce_type = '>Q' - extranonce_placeholder = struct.pack(extranonce_type, int('f000000ff111111f', 16)) - extranonce_size = struct.calcsize(extranonce_type) - - def __init__(self, timestamper, coinbaser, value, flags, height, data, ntime): - super(CoinbaseTransaction, self).__init__() - log.debug("Got to CoinBaseTX") - #self.extranonce = 0 - - if len(self.extranonce_placeholder) != self.extranonce_size: - raise Exception("Extranonce placeholder don't match expected length!") - - tx_in = halfnode.CTxIn() - tx_in.prevout.hash = 0L - tx_in.prevout.n = 2**32-1 - tx_in._scriptSig_template = ( - util.ser_number(height) + binascii.unhexlify(flags) + util.ser_number(int(timestamper.time())) + \ - chr(self.extranonce_size), - util.ser_string(coinbaser.get_coinbase_data() + data) - ) - - tx_in.scriptSig = tx_in._scriptSig_template[0] + self.extranonce_placeholder + tx_in._scriptSig_template[1] - - tx_out = halfnode.CTxOut() - tx_out.nValue = value - tx_out.scriptPubKey = coinbaser.get_script_pubkey() - self.nTime = ntime self.vin.append(tx_in) self.vout.append(tx_out) diff --git a/lib/halfnode.py b/lib/halfnode.py index 2642be1..9975e34 100644 --- a/lib/halfnode.py +++ b/lib/halfnode.py @@ -21,27 +21,27 @@ log = lib.logger.get_logger('halfnode') log.debug("Got to Halfnode") if settings.COINDAEMON_ALGO == 'scrypt': - log.debug("########################################### Loading LTC Scrypt #########################################################") - import ltc_scrypt + log.debug("########################################### Loading LTC Scrypt #########################################################") + import ltc_scrypt elif settings.COINDAEMON_ALGO == 'quark': - log.debug("########################################### Loading Quark Support #########################################################") - import quark_hash + log.debug("########################################### Loading Quark Support #########################################################") + import quark_hash else: - log.debug("########################################### Loading SHA256 Support ######################################################") + log.debug("########################################### Loading SHA256 Support ######################################################") -if settings.COINDAEMON_Reward == 'POS': - log.debug("########################################### Loading POS Support #########################################################") - pass -else: - log.debug("########################################### Loading POW Support ######################################################") - pass +#if settings.COINDAEMON_Reward == 'POS': +# log.debug("########################################### Loading POS Support #########################################################") +# pass +#else: +# log.debug("########################################### Loading POW Support ######################################################") +# pass if settings.COINDAEMON_TX == 'yes': - log.debug("########################################### Loading SHA256 Transaction Message Support #########################################################") - pass + log.debug("########################################### Loading SHA256 Transaction Message Support #########################################################") + pass else: - log.debug("########################################### NOT Loading SHA256 Transaction Message Support ######################################################") - pass + log.debug("########################################### NOT Loading SHA256 Transaction Message Support ######################################################") + pass MY_VERSION = 31402 diff --git a/lib/util.py b/lib/util.py index 4a823f7..4e93fec 100644 --- a/lib/util.py +++ b/lib/util.py @@ -211,15 +211,15 @@ def ser_number(n): s.append(n) return bytes(s) -if settings.COINDAEMON_Reward == 'POW': - def script_to_address(addr): - d = address_to_pubkeyhash(addr) - if not d: - raise ValueError('invalid address') - (ver, pubkeyhash) = d - return b'\x76\xa9\x14' + pubkeyhash + b'\x88\xac' -else: - def script_to_pubkey(key): - if len(key) == 66: key = binascii.unhexlify(key) - if len(key) != 33: raise Exception('Invalid Address') - return b'\x21' + key + b'\xac' +#if settings.COINDAEMON_Reward == 'POW': +def script_to_address(addr): + d = address_to_pubkeyhash(addr) + if not d: + raise ValueError('invalid address') + (ver, pubkeyhash) = d + return b'\x76\xa9\x14' + pubkeyhash + b'\x88\xac' +#else: +def script_to_pubkey(key): + if len(key) == 66: key = binascii.unhexlify(key) + if len(key) != 33: raise Exception('Invalid Address') + return b'\x21' + key + b'\xac' diff --git a/mining/Cache.py b/mining/Cache.py new file mode 100644 index 0000000..5f5219e --- /dev/null +++ b/mining/Cache.py @@ -0,0 +1,24 @@ +''' A simple wrapper for pylibmc. It can be overwritten with simple hashing if necessary ''' +import lib.settings as settings +import lib.logger +log = lib.logger.get_logger('Cache') + +import pylibmc + +class Cache(): + def __init__(self): + # Open a new connection + self.mc = pylibmc.Client([settings.MEMCACHE_HOST + ":" + str(settings.MEMCACHE_PORT)], binary=True) + log.info("Caching initialized") + + def set(self, key, value, time=settings.MEMCACHE_TIMEOUT): + return self.mc.set(settings.MEMCACHE_PREFIX + str(key), value, time) + + def get(self, key): + return self.mc.get(settings.MEMCACHE_PREFIX + str(key)) + + def delete(self, key): + return self.mc.delete(settings.MEMCACHE_PREFIX + str(key)) + + def exists(self, key): + return str(key) in self.mc.get(settings.MEMCACHE_PREFIX + str(key)) diff --git a/mining/DBInterface.py b/mining/DBInterface.py index f494713..3bf802d 100644 --- a/mining/DBInterface.py +++ b/mining/DBInterface.py @@ -3,6 +3,7 @@ import time from datetime import datetime import Queue import signal +import Cache import lib.settings as settings @@ -19,8 +20,7 @@ class DBInterface(): self.q = Queue.Queue() self.queueclock = None - self.usercache = {} - self.clearusercache() + self.cache = Cache.Cache() self.nextStatsUpdate = 0 @@ -67,11 +67,6 @@ class DBInterface(): return DB_None.DB_None() - def clearusercache(self): - log.debug("DBInterface.clearusercache called") - self.usercache = {} - self.usercacheclock = reactor.callLater(settings.DB_USERCACHE_TIME , self.clearusercache) - def scheduleImport(self): # This schedule's the Import if settings.DATABASE_DRIVER == "sqlite": @@ -163,19 +158,16 @@ class DBInterface(): # Force username and password to be strings username = str(username) password = str(password) - wid = username + ":-:" + password - - if wid in self.usercache: + if not settings.USERS_CHECK_PASSWORD and self.user_exists(username): return True - elif not settings.USERS_CHECK_PASSWORD and self.user_exists(username): - self.usercache[wid] = 1 + elif self.cache.get(username) == password: return True elif self.dbi.check_password(username, password): - self.usercache[wid] = 1 + self.cache.set(username, password) return True elif settings.USERS_AUTOADD == True: self.insert_user(username, password) - self.usercache[wid] = 1 + self.cache.set(username, password) return True log.info("Authentication for %s failed" % username) @@ -188,6 +180,8 @@ class DBInterface(): return self.dbi.get_user(id) def user_exists(self, username): + if self.cache.get(username) is not None: + return True user = self.dbi.get_user(username) return user is not None @@ -195,11 +189,13 @@ class DBInterface(): return self.dbi.insert_user(username, password) def delete_user(self, username): + self.mc.delete(username) self.usercache = {} return self.dbi.delete_user(username) def update_user(self, username, password): - self.usercache = {} + self.mc.delete(username) + self.mc.set(username, password) return self.dbi.update_user(username, password) def update_worker_diff(self, username, diff): diff --git a/mining/DB_Mysql_Vardiff.py b/mining/DB_Mysql_Vardiff.py index 36ad69e..be6f49f 100644 --- a/mining/DB_Mysql_Vardiff.py +++ b/mining/DB_Mysql_Vardiff.py @@ -59,6 +59,72 @@ class DB_Mysql_Vardiff(DB_Mysql.DB_Mysql): ) self.dbh.commit() + + def found_block(self, data): + # for database compatibility we are converting our_worker to Y/N format + if data[5]: + data[5] = 'Y' + else: + data[5] = 'N' + + # Check for the share in the database before updating it + # Note: We can't use DUPLICATE KEY because solution is not a key + + self.execute( + """ + Select `id` from `shares` + WHERE `solution` = %(solution)s + LIMIT 1 + """, + { + "solution": data[2] + } + ) + + shareid = self.dbc.fetchone() + + if shareid[0] > 0: + # Note: difficulty = -1 here + self.execute( + """ + UPDATE `shares` + SET `upstream_result` = %(result)s + WHERE `solution` = %(solution)s + AND `id` = %(id)s + LIMIT 1 + """, + { + "result": data[5], + "solution": data[2], + "id": shareid[0] + } + ) + + self.dbh.commit() + else: + self.execute( + """ + INSERT INTO `shares` + (time, rem_host, username, our_result, + upstream_result, reason, solution) + VALUES + (FROM_UNIXTIME(%(time)s), %(host)s, + %(uname)s, + %(lres)s, %(result)s, %(reason)s, %(solution)s) + """, + { + "time": v[4], + "host": v[6], + "uname": v[0], + "lres": v[5], + "result": v[5], + "reason": v[9], + "solution": v[2] + } + ) + + self.dbh.commit() + def update_worker_diff(self, username, diff): log.debug("Setting difficulty for %s to %s", username, diff) diff --git a/mining/__init__.py b/mining/__init__.py index 110af1b..abb49b9 100644 --- a/mining/__init__.py +++ b/mining/__init__.py @@ -44,17 +44,16 @@ def setup(on_startup): if isinstance(result, dict): # litecoind implements version 1 of getblocktemplate if result['version'] >= 1: - result = (yield bitcoin_rpc.getinfo()) - if isinstance(result,dict): - if 'stake' in result and settings.COINDAEMON_Reward == 'POS': - log.info("CoinD looks to be a POS Coin, Config for POS looks correct") - break - elif 'stake' not in result and settings.COINDAEMON_Reward == 'POW': - log.info("CoinD looks to be a POW Coin, Config looks to be correct") - break - else: - log.error("Wrong Algo Selected, Switch to appropriate POS/POW in config.py!") - reactor.stop() + result = (yield bitcoin_rpc.getdifficulty()) + if isinstance(result,dict): + if 'proof-of-stake' in result: + settings.COINDAEMON_Reward = 'POS' + log.info("Coin detected as POS") + break; + else: + settings.COINDAEMON_Reward = 'POW' + log.info("Coin detected as POW") + break; else: log.error("Block Version mismatch: %s" % result['version']) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..02bf93f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,48 @@ +# This File is used to create a list of requirements needed for testing stratum-mining or to create a clone install +BeautifulSoup==3.2.1 +#Brlapi==0.5.7 +#GnuPGInterface==0.3.2 +MySQL-python==1.2.3 +#PAM==0.4.2 +#Pyste==0.9.10 +#SOAPpy==0.12.0 +Twisted==12.0.0 +#Twisted-Conch==12.0.0 +#Twisted-Core==12.0.0 +#Twisted-Lore==12.0.0 +#Twisted-Mail==12.0.0 +#Twisted-Names==12.0.0 +#Twisted-News==12.0.0 +#Twisted-Runner==12.0.0 +#Twisted-Web==12.0.0 +#Twisted-Words==12.0.0 +#apt-xapian-index==0.45 +argparse==1.2.1 +autobahn==0.6.5 +#chardet==2.0.1 +defer==1.0.6 +distribute==0.6.28 +ecdsa==0.10 +feedparser==5.1.2 +fpconst==0.7.2 +httplib2==0.7.4 +#louis==2.4.1 +#ltc-scrypt==1.0 +#numpy==1.6.2 +pyOpenSSL==0.13 +pyasn1==0.1.3 +pycrypto==2.6 +#pycurl==7.19.0 +pyserial==2.5 +#python-apt==0.8.8.2 +#python-debian==0.1.21 +#python-debianbts==1.11 +python-memcached==1.48 +pyxdg==0.19 +#reportbug==6.4.4 +simplejson==2.5.2 +#stratum==0.2.13 +#uTidylib==0.2 +#unattended-upgrades==0.1 +#wsgiref==0.1.2 +zope.interface==3.6.1