Added YAC Support, Vardiff WorkAround (Power of 2), Worker Banning, Custom Difficulties
This commit is contained in:
parent
beda421235
commit
1286b20c2a
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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'''
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user