diff --git a/conf/config_sample.py b/conf/config_sample.py index 0754600..ee68d39 100644 --- a/conf/config_sample.py +++ b/conf/config_sample.py @@ -128,25 +128,32 @@ MERKLE_REFRESH_INTERVAL = 60 # How often check memorypool INSTANCE_ID = 31 # Used for extranonce and needs to be 0-31 # ******************** Pool Difficulty Settings ********************* -# Again, Don't change unless you know what this is for. +VDIFF_X2_TYPE = True # powers of 2 e.g. 2,4,8,16,32,64,128,256,512,1024 +VDIFF_FLOAT = False # Use float difficulty # Pool Target (Base Difficulty) -# In order to match the Pool Target with a frontend like MPOS the following formula is used: (stratum diff) ~= 2^((target bits in pushpool) - 16) -# E.G. a Pool Target of 16 would = a MPOS and PushPool Target bit's of 20 -POOL_TARGET = 16 # Pool-wide difficulty target int >= 1 +POOL_TARGET = 32 # Pool-wide difficulty target int >= 1 # Variable Difficulty Enable -VARIABLE_DIFF = True # Master variable difficulty enable +VARIABLE_DIFF = True # Master variable difficulty enable # Variable diff tuning variables -#VARDIFF will start at the POOL_TARGET. It can go as low as the VDIFF_MIN and as high as min(VDIFF_MAX or the coin daemon's difficulty) -USE_COINDAEMON_DIFF = False # Set the maximum difficulty to the coin daemon's difficulty. -DIFF_UPDATE_FREQUENCY = 86400 # Update the COINDAEMON difficulty once a day for the VARDIFF maximum -VDIFF_MIN_TARGET = 15 # Minimum Target difficulty -VDIFF_MAX_TARGET = 1000 # Maximum Target difficulty -VDIFF_TARGET_TIME = 30 # Target time per share (i.e. try to get 1 share per this many seconds) -VDIFF_RETARGET_TIME = 120 # Check to see if we should retarget this often -VDIFF_VARIANCE_PERCENT = 20 # Allow average time to very this % from target without retarget +#VARDIFF will start at the POOL_TARGET. It can go as low as the VDIFF_MIN and as high as min(VDIFF_MAX or Liteconin's difficulty) +USE_LITECOIN_DIFF = False # Set the maximum difficulty to the litecoin difficulty. +DIFF_UPDATE_FREQUENCY = 86400 # Update the litecoin difficulty once a day for the VARDIFF maximum +VDIFF_MIN_TARGET = 16 # Minimum Target difficulty +VDIFF_MAX_TARGET = 1024 # Maximum Target difficulty +VDIFF_TARGET_TIME = 15 # Target time per share (i.e. try to get 1 share per this many seconds) +VDIFF_RETARGET_TIME = 120 # Check to see if we should retarget this often +VDIFF_VARIANCE_PERCENT = 30 # Allow average time to very this % from target without retarget +VDIFF_RETARGET_DELAY = 25 # Wait this many seconds before applying new variable difficulty target +VDIFF_RETARGET_REJECT_TIME = 60 # Wait this many seconds before rejecting old difficulty shares + +# Allow external setting of worker difficulty, checks pool_worker table datarow[6] position for target difficulty +# if present or else defaults to pool target, over rides all other difficulty settings, no checks are made +#for min or max limits this sould be done by your front end software +ALLOW_EXTERNAL_DIFFICULTY = False + #### Advanced Option ##### # For backwards compatibility, we send the scrypt hash to the solutions column in the shares table # For block confirmation, we have an option to send the block hash in @@ -163,6 +170,12 @@ GW_PORT = 3333 # Getwork Proxy Port GW_DISABLE_MIDSTATE = False # Disable midstate's (Faster but breaks some clients) GW_SEND_REAL_TARGET = True # Propigate >1 difficulty to Clients (breaks some clients) +# ******************** Worker Ban Options ********************* +ENABLE_WORKER_BANNING = True # enable/disable temporary worker banning +WORKER_CACHE_TIME = 600 # How long the worker stats cache is good before we check and refresh +WORKER_BAN_TIME = 300 # How long we temporarily ban worker +INVALID_SHARES_PERCENT = 50 # Allow average invalid shares vary this % before we ban + # ******************** E-Mail Notification Settings ********************* NOTIFY_EMAIL_TO = '' # Where to send Start/Found block notifications NOTIFY_EMAIL_TO_DEADMINER = '' # Where to send dead miner notifications diff --git a/lib/template_registry.py b/lib/template_registry.py index 3d00660..75b3636 100644 --- a/lib/template_registry.py +++ b/lib/template_registry.py @@ -5,6 +5,8 @@ import StringIO import settings if settings.COINDAEMON_ALGO == 'scrypt': import ltc_scrypt +elif settings.MAIN_COIN_ALGORITHM == 'scrypt-jane': + import yac_scrypt else: pass from twisted.internet import defer from lib.exceptions import SubmitException @@ -170,7 +172,7 @@ class TemplateRegistry(object): return j def submit_share(self, job_id, worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, - difficulty): + difficulty, submit_time): '''Check parameters and finalize block template. If it leads to valid block candidate, asynchronously submits the block back to the bitcoin network. @@ -229,11 +231,13 @@ class TemplateRegistry(object): # 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) ])) + elif settings.MAIN_COIN_ALGORITHM == 'scrypt-jane': + hash_bin = yac_scrypt.getPoWHash(''.join([ header_bin[i*4:i*4+4][::-1] for i in range(0, 20) ]), int(ntime, 16)) 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': + if not settings.COINDAEMON_ALGO == 'sha256d': header_hex = header_hex+"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" else: pass @@ -243,6 +247,10 @@ class TemplateRegistry(object): or 'prev_diff' not in session or hash_int > self.diff_to_target(session['prev_diff']) ): raise SubmitException("Share is above target") + if hash_int > target_user and 'prev_ts' in session \ + and (submit_time - session['prev_ts']) > settings.VDIFF_RETARGET_REJECT_TIME: + raise SubmitException("Stale-share above target") + # Mostly for debugging purposes target_info = self.diff_to_target(100000) if hash_int <= target_info: diff --git a/mining/basic_share_limiter.py b/mining/basic_share_limiter.py index 73450c2..06b2fb6 100644 --- a/mining/basic_share_limiter.py +++ b/mining/basic_share_limiter.py @@ -87,7 +87,7 @@ class BasicShareLimiter(object): ts = int(timestamp) # Init the stats for this worker if it isn't set. - if worker_name not in self.worker_stats : + if worker_name not in self.worker_stats or self.worker_stats[worker_name]['last_ts'] < ts - settings.DB_USERCACHE_TIME : self.worker_stats[worker_name] = {'last_rtc': (ts - self.retarget / 2), 'last_ts': ts, 'buffer': SpeedBuffer(self.buffersize) } dbi.update_worker_diff(worker_name, settings.POOL_TARGET) return @@ -111,33 +111,58 @@ class BasicShareLimiter(object): avg = 1 # Figure out our Delta-Diff - ddiff = float((float(current_difficulty) * (float(self.target) / float(avg))) - current_difficulty) + if settings.VDIFF_FLOAT: + ddiff = float((float(current_difficulty) * (float(self.target) / float(avg))) - current_difficulty) + else: + ddiff = int((float(current_difficulty) * (float(self.target) / float(avg))) - current_difficulty) + if (avg > self.tmax and current_difficulty > settings.VDIFF_MIN_TARGET): # For fractional -0.1 ddiff's just drop by 1 - if ddiff > -1: - ddiff = -1 - # Don't drop below POOL_TARGET - if (ddiff + current_difficulty) < settings.VDIFF_MIN_TARGET: - ddiff = settings.VDIFF_MIN_TARGET - current_difficulty + if settings.VDIFF_X2_TYPE: + ddiff = 0.5 + # Don't drop below POOL_TARGET + if (ddiff * current_difficulty) < settings.VDIFF_MIN_TARGET: + ddiff = settings.VDIFF_MIN_TARGET / current_difficulty + else: + if ddiff > -1: + ddiff = -1 + # Don't drop below POOL_TARGET + if (ddiff + current_difficulty) < settings.POOL_TARGET: + ddiff = settings.VDIFF_MIN_TARGET - current_difficulty elif avg < self.tmin: # For fractional 0.1 ddiff's just up by 1 - if ddiff < 1: - ddiff = 1 - # Don't go above LITECOIN or VDIFF_MAX_TARGET - self.update_litecoin_difficulty() - if settings.USE_COINDAEMON_DIFF: - diff_max = min([settings.VDIFF_MAX_TARGET, self.litecoin_diff]) - else: - diff_max = settings.VDIFF_MAX_TARGET + if settings.VDIFF_X2_TYPE: + ddiff = 2 + # Don't go above LITECOIN or VDIFF_MAX_TARGET + if settings.USE_LITECOIN_DIFF: + self.update_litecoin_difficulty() + diff_max = min([settings.VDIFF_MAX_TARGET, self.litecoin_diff]) + else: + diff_max = settings.VDIFF_MAX_TARGET - if (ddiff + current_difficulty) > diff_max: - ddiff = diff_max - current_difficulty + if (ddiff * current_difficulty) > diff_max: + ddiff = diff_max / current_difficulty + else: + if ddiff < 1: + ddiff = 1 + # Don't go above LITECOIN or VDIFF_MAX_TARGET + if settings.USE_LITECOIN_DIFF: + self.update_litecoin_difficulty() + diff_max = min([settings.VDIFF_MAX_TARGET, self.litecoin_diff]) + else: + diff_max = settings.VDIFF_MAX_TARGET + + if (ddiff + current_difficulty) > diff_max: + ddiff = diff_max - current_difficulty else: # If we are here, then we should not be retargeting. return # At this point we are retargeting this worker - new_diff = current_difficulty + ddiff + if settings.VDIFF_X2_TYPE: + new_diff = current_difficulty * ddiff + else: + new_diff = current_difficulty + ddiff log.info("Retarget for %s %i old: %i new: %i" % (worker_name, ddiff, current_difficulty, new_diff)) self.worker_stats[worker_name]['buffer'].clear() diff --git a/mining/interfaces.py b/mining/interfaces.py index 707dfd9..0370d06 100644 --- a/mining/interfaces.py +++ b/mining/interfaces.py @@ -17,12 +17,21 @@ dbi.init_main() class WorkerManagerInterface(object): def __init__(self): + self.worker_log = {} + self.worker_log.setdefault('authorized', {}) return def authorize(self, worker_name, worker_password): # Important NOTE: This is called on EVERY submitted share. So you'll need caching!!! return dbi.check_password(worker_name, worker_password) + def get_user_difficulty(self, worker_name): + wd = dbi.get_user(worker_name) + if len(wd) > 6: + #dbi.update_worker_diff(worker_name, wd[6]) + return (True, wd[6]) + else: + return (False, settings.POOL_TARGET) class ShareLimiterInterface(object): '''Implement difficulty adjustments here''' diff --git a/mining/service.py b/mining/service.py index e56b06c..e1a11f6 100644 --- a/mining/service.py +++ b/mining/service.py @@ -7,9 +7,6 @@ from stratum.pubsub import Pubsub from interfaces import Interfaces from subscription import MiningSubscription from lib.exceptions import SubmitException -import DBInterface -dbi = DBInterface.DBInterface() -dbi.init_main() import lib.logger log = lib.logger.get_logger('mining') @@ -32,8 +29,7 @@ class MiningService(GenericService): See blocknotify.sh in /scripts/ for more info.''' log.info("New block notification received") - dbi.do_import(self, dbi, True) - Interfaces.template_registry.update_block() + Interfaces.template_registry.update_block() return True @admin @@ -55,10 +51,20 @@ class MiningService(GenericService): if Interfaces.worker_manager.authorize(worker_name, worker_password): session['authorized'][worker_name] = worker_password + is_ext_diff = False + if settings.ALLOW_EXTERNAL_DIFFICULTY: + (is_ext_diff, session['difficulty']) = Interfaces.worker_manager.get_user_difficulty(worker_name) + self.connection_ref().rpc('mining.set_difficulty', [session['difficulty'], ], is_notification=True) + else: + session['difficulty'] = settings.POOL_TARGET + # worker_log = (valid, invalid, is_banned, diff, is_ext_diff, timestamp) + Interfaces.worker_manager.worker_log['authorized'][worker_name] = (0, 0, False, session['difficulty'], is_ext_diff, Interfaces.timestamper.time()) return True else: if worker_name in session['authorized']: del session['authorized'][worker_name] + if worker_name in Interfaces.worker_manager.worker_log['authorized']: + del Interfaces.worker_manager.worker_log['authorized'][worker_name] return False def subscribe(self, *args): @@ -72,7 +78,6 @@ class MiningService(GenericService): session = self.connection_ref().get_session() session['extranonce1'] = extranonce1 session['difficulty'] = settings.POOL_TARGET # Following protocol specs, default diff is 1 - return Pubsub.subscribe(self.connection_ref(), MiningSubscription()) + (extranonce1_hex, extranonce2_size) def submit(self, worker_name, job_id, extranonce2, ntime, nonce): @@ -92,26 +97,62 @@ class MiningService(GenericService): raise SubmitException("Connection is not subscribed for mining") difficulty = session['difficulty'] + s_difficulty = difficulty submit_time = Interfaces.timestamper.time() ip = self.connection_ref()._get_ip() - - Interfaces.share_limiter.submit(self.connection_ref, job_id, difficulty, submit_time, worker_name) + (valid, invalid, is_banned, diff, is_ext_diff, last_ts) = Interfaces.worker_manager.worker_log['authorized'][worker_name] + percent = float(float(invalid) / (float(valid) if valid else 1) * 100) + + if is_banned and submit_time - last_ts > settings.WORKER_BAN_TIME: + if percent > settings.INVALID_SHARES_PERCENT: + log.debug("Worker invalid percent: %0.2f %s STILL BANNED!" % (percent, worker_name)) + else: + is_banned = False + log.debug("Clearing ban for worker: %s UNBANNED" % worker_name) + (valid, invalid, is_banned, last_ts) = (0, 0, is_banned, Interfaces.timestamper.time()) + + if submit_time - last_ts > settings.WORKER_CACHE_TIME and not is_banned: + if percent > settings.INVALID_SHARES_PERCENT and settings.ENABLE_WORKER_BANNING: + is_banned = True + log.debug("Worker invalid percent: %0.2f %s BANNED!" % (percent, worker_name)) + else: + log.debug("Clearing worker stats for: %s" % worker_name) + (valid, invalid, is_banned, last_ts) = (0, 0, is_banned, Interfaces.timestamper.time()) + + if 'prev_ts' in session and (submit_time - session['prev_ts']) < settings.VDIFF_RETARGET_DELAY \ + and not is_ext_diff: + difficulty = session['prev_diff'] or session['difficulty'] or settings.POOL_TARGET + diff = difficulty + log.debug("%s (%d, %d, %s, %s, %d) %0.2f%% diff(%f)" % (worker_name, valid, invalid, is_banned, is_ext_diff, last_ts, percent, diff)) + if not is_ext_diff: + Interfaces.share_limiter.submit(self.connection_ref, job_id, difficulty, submit_time, worker_name) # This checks if submitted share meet all requirements # and it is valid proof of work. try: (block_header, block_hash, share_diff, on_submit) = Interfaces.template_registry.submit_share(job_id, - worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, difficulty) + worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, s_difficulty, submit_time) except SubmitException as e: # block_header and block_hash are None when submitted data are corrupted + invalid += 1 + Interfaces.worker_manager.worker_log['authorized'][worker_name] = (valid, invalid, is_banned, diff, is_ext_diff, last_ts) + + if is_banned: + raise SubmitException("Worker is temporarily banned") + Interfaces.share_manager.on_submit_share(worker_name, False, False, difficulty, - submit_time, False, ip, e[0], 0) + submit_time, False, ip, e[0], 0) raise - - + + valid += 1 + Interfaces.worker_manager.worker_log['authorized'][worker_name] = (valid, invalid, is_banned, diff, is_ext_diff, last_ts) + + if is_banned: + raise SubmitException("Worker is temporarily banned") + Interfaces.share_manager.on_submit_share(worker_name, block_header, block_hash, difficulty, submit_time, True, ip, '', share_diff) - + if on_submit != None: # Pool performs submitblock() to litecoind. Let's hook # to result and report it to share manager