Added YAC Support, Vardiff WorkAround (Power of 2), Worker Banning, Custom Difficulties

This commit is contained in:
Ahmed Bodiwala 2013-12-07 22:08:15 +00:00
parent beda421235
commit 1286b20c2a
5 changed files with 142 additions and 46 deletions

View File

@ -128,25 +128,32 @@ MERKLE_REFRESH_INTERVAL = 60 # How often check memorypool
INSTANCE_ID = 31 # Used for extranonce and needs to be 0-31 INSTANCE_ID = 31 # Used for extranonce and needs to be 0-31
# ******************** Pool Difficulty Settings ********************* # ******************** 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) # 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) POOL_TARGET = 32 # Pool-wide difficulty target int >= 1
# 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
# Variable Difficulty Enable # Variable Difficulty Enable
VARIABLE_DIFF = True # Master variable difficulty enable VARIABLE_DIFF = True # Master variable difficulty enable
# Variable diff tuning variables # 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) #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_COINDAEMON_DIFF = False # Set the maximum difficulty to the coin daemon's difficulty. USE_LITECOIN_DIFF = False # Set the maximum difficulty to the litecoin difficulty.
DIFF_UPDATE_FREQUENCY = 86400 # Update the COINDAEMON difficulty once a day for the VARDIFF maximum DIFF_UPDATE_FREQUENCY = 86400 # Update the litecoin difficulty once a day for the VARDIFF maximum
VDIFF_MIN_TARGET = 15 # Minimum Target difficulty VDIFF_MIN_TARGET = 16 # Minimum Target difficulty
VDIFF_MAX_TARGET = 1000 # Maximum Target difficulty VDIFF_MAX_TARGET = 1024 # Maximum Target difficulty
VDIFF_TARGET_TIME = 30 # Target time per share (i.e. try to get 1 share per this many seconds) 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_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 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 ##### #### Advanced Option #####
# For backwards compatibility, we send the scrypt hash to the solutions column in the shares table # 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 # 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_DISABLE_MIDSTATE = False # Disable midstate's (Faster but breaks some clients)
GW_SEND_REAL_TARGET = True # Propigate >1 difficulty to Clients (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 ********************* # ******************** E-Mail Notification Settings *********************
NOTIFY_EMAIL_TO = '' # Where to send Start/Found block notifications NOTIFY_EMAIL_TO = '' # Where to send Start/Found block notifications
NOTIFY_EMAIL_TO_DEADMINER = '' # Where to send dead miner notifications NOTIFY_EMAIL_TO_DEADMINER = '' # Where to send dead miner notifications

View File

@ -5,6 +5,8 @@ import StringIO
import settings import settings
if settings.COINDAEMON_ALGO == 'scrypt': if settings.COINDAEMON_ALGO == 'scrypt':
import ltc_scrypt import ltc_scrypt
elif settings.MAIN_COIN_ALGORITHM == 'scrypt-jane':
import yac_scrypt
else: pass else: pass
from twisted.internet import defer from twisted.internet import defer
from lib.exceptions import SubmitException from lib.exceptions import SubmitException
@ -170,7 +172,7 @@ class TemplateRegistry(object):
return j return j
def submit_share(self, job_id, worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, 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 '''Check parameters and finalize block template. If it leads
to valid block candidate, asynchronously submits the block to valid block candidate, asynchronously submits the block
back to the bitcoin network. back to the bitcoin network.
@ -229,11 +231,13 @@ class TemplateRegistry(object):
# 4. Reverse header and compare it with target of the user # 4. Reverse header and compare it with target of the user
if settings.COINDAEMON_ALGO == 'scrypt': 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) ])) 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) ])) 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) hash_int = util.uint256_from_str(hash_bin)
scrypt_hash_hex = "%064x" % hash_int scrypt_hash_hex = "%064x" % hash_int
header_hex = binascii.hexlify(header_bin) header_hex = binascii.hexlify(header_bin)
if settings.COINDAEMON_ALGO == 'scrypt': if not settings.COINDAEMON_ALGO == 'sha256d':
header_hex = header_hex+"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" header_hex = header_hex+"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000"
else: pass 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']) ): or 'prev_diff' not in session or hash_int > self.diff_to_target(session['prev_diff']) ):
raise SubmitException("Share is above target") 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 # Mostly for debugging purposes
target_info = self.diff_to_target(100000) target_info = self.diff_to_target(100000)
if hash_int <= target_info: if hash_int <= target_info:

View File

@ -87,7 +87,7 @@ class BasicShareLimiter(object):
ts = int(timestamp) ts = int(timestamp)
# Init the stats for this worker if it isn't set. # 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) } 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) dbi.update_worker_diff(worker_name, settings.POOL_TARGET)
return return
@ -111,33 +111,58 @@ class BasicShareLimiter(object):
avg = 1 avg = 1
# Figure out our Delta-Diff # 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): if (avg > self.tmax and current_difficulty > settings.VDIFF_MIN_TARGET):
# For fractional -0.1 ddiff's just drop by 1 # For fractional -0.1 ddiff's just drop by 1
if ddiff > -1: if settings.VDIFF_X2_TYPE:
ddiff = -1 ddiff = 0.5
# Don't drop below POOL_TARGET # Don't drop below POOL_TARGET
if (ddiff + current_difficulty) < settings.VDIFF_MIN_TARGET: if (ddiff * current_difficulty) < settings.VDIFF_MIN_TARGET:
ddiff = settings.VDIFF_MIN_TARGET - current_difficulty 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: elif avg < self.tmin:
# For fractional 0.1 ddiff's just up by 1 # For fractional 0.1 ddiff's just up by 1
if ddiff < 1: if settings.VDIFF_X2_TYPE:
ddiff = 1 ddiff = 2
# Don't go above LITECOIN or VDIFF_MAX_TARGET # Don't go above LITECOIN or VDIFF_MAX_TARGET
self.update_litecoin_difficulty() if settings.USE_LITECOIN_DIFF:
if settings.USE_COINDAEMON_DIFF: self.update_litecoin_difficulty()
diff_max = min([settings.VDIFF_MAX_TARGET, self.litecoin_diff]) diff_max = min([settings.VDIFF_MAX_TARGET, self.litecoin_diff])
else: else:
diff_max = settings.VDIFF_MAX_TARGET diff_max = settings.VDIFF_MAX_TARGET
if (ddiff + current_difficulty) > diff_max: if (ddiff * current_difficulty) > diff_max:
ddiff = diff_max - current_difficulty 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. else: # If we are here, then we should not be retargeting.
return return
# At this point we are retargeting this worker # 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)) log.info("Retarget for %s %i old: %i new: %i" % (worker_name, ddiff, current_difficulty, new_diff))
self.worker_stats[worker_name]['buffer'].clear() self.worker_stats[worker_name]['buffer'].clear()

View File

@ -17,12 +17,21 @@ dbi.init_main()
class WorkerManagerInterface(object): class WorkerManagerInterface(object):
def __init__(self): def __init__(self):
self.worker_log = {}
self.worker_log.setdefault('authorized', {})
return return
def authorize(self, worker_name, worker_password): def authorize(self, worker_name, worker_password):
# Important NOTE: This is called on EVERY submitted share. So you'll need caching!!! # Important NOTE: This is called on EVERY submitted share. So you'll need caching!!!
return dbi.check_password(worker_name, worker_password) 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): class ShareLimiterInterface(object):
'''Implement difficulty adjustments here''' '''Implement difficulty adjustments here'''

View File

@ -7,9 +7,6 @@ from stratum.pubsub import Pubsub
from interfaces import Interfaces from interfaces import Interfaces
from subscription import MiningSubscription from subscription import MiningSubscription
from lib.exceptions import SubmitException from lib.exceptions import SubmitException
import DBInterface
dbi = DBInterface.DBInterface()
dbi.init_main()
import lib.logger import lib.logger
log = lib.logger.get_logger('mining') log = lib.logger.get_logger('mining')
@ -32,8 +29,7 @@ class MiningService(GenericService):
See blocknotify.sh in /scripts/ for more info.''' See blocknotify.sh in /scripts/ for more info.'''
log.info("New block notification received") log.info("New block notification received")
dbi.do_import(self, dbi, True) Interfaces.template_registry.update_block()
Interfaces.template_registry.update_block()
return True return True
@admin @admin
@ -55,10 +51,20 @@ class MiningService(GenericService):
if Interfaces.worker_manager.authorize(worker_name, worker_password): if Interfaces.worker_manager.authorize(worker_name, worker_password):
session['authorized'][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 return True
else: else:
if worker_name in session['authorized']: if worker_name in session['authorized']:
del session['authorized'][worker_name] 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 return False
def subscribe(self, *args): def subscribe(self, *args):
@ -72,7 +78,6 @@ class MiningService(GenericService):
session = self.connection_ref().get_session() session = self.connection_ref().get_session()
session['extranonce1'] = extranonce1 session['extranonce1'] = extranonce1
session['difficulty'] = settings.POOL_TARGET # Following protocol specs, default diff is 1 session['difficulty'] = settings.POOL_TARGET # Following protocol specs, default diff is 1
return Pubsub.subscribe(self.connection_ref(), MiningSubscription()) + (extranonce1_hex, extranonce2_size) return Pubsub.subscribe(self.connection_ref(), MiningSubscription()) + (extranonce1_hex, extranonce2_size)
def submit(self, worker_name, job_id, extranonce2, ntime, nonce): 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") raise SubmitException("Connection is not subscribed for mining")
difficulty = session['difficulty'] difficulty = session['difficulty']
s_difficulty = difficulty
submit_time = Interfaces.timestamper.time() submit_time = Interfaces.timestamper.time()
ip = self.connection_ref()._get_ip() ip = self.connection_ref()._get_ip()
(valid, invalid, is_banned, diff, is_ext_diff, last_ts) = Interfaces.worker_manager.worker_log['authorized'][worker_name]
Interfaces.share_limiter.submit(self.connection_ref, job_id, difficulty, submit_time, 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 # This checks if submitted share meet all requirements
# and it is valid proof of work. # and it is valid proof of work.
try: try:
(block_header, block_hash, share_diff, on_submit) = Interfaces.template_registry.submit_share(job_id, (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: except SubmitException as e:
# block_header and block_hash are None when submitted data are corrupted # 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, 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 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, Interfaces.share_manager.on_submit_share(worker_name, block_header,
block_hash, difficulty, submit_time, True, ip, '', share_diff) block_hash, difficulty, submit_time, True, ip, '', share_diff)
if on_submit != None: if on_submit != None:
# Pool performs submitblock() to litecoind. Let's hook # Pool performs submitblock() to litecoind. Let's hook
# to result and report it to share manager # to result and report it to share manager