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
# ******************** 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

View File

@ -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:

View File

@ -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()

View File

@ -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'''

View File

@ -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