Merge pull request #20 from obigal/vardiff-bug-patch

Vardiff bug patch
This commit is contained in:
ahmedbodi 2013-12-15 13:56:04 -08:00
commit ce145159a4
8 changed files with 80 additions and 26 deletions

View File

@ -158,8 +158,6 @@ 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_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 = 30 # 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 # 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 # if present or else defaults to pool target, over rides all other difficulty settings, no checks are made

View File

@ -176,7 +176,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, submit_time): difficulty):
'''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.
@ -249,16 +249,10 @@ class TemplateRegistry(object):
header_hex = header_hex+"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000" header_hex = header_hex+"000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000"
else: pass else: pass
target_user = self.diff_to_target(difficulty) target_user = self.diff_to_target(difficulty)
if hash_int > target_user and \ if hash_int > target_user:
( 'prev_jobid' not in session or session['prev_jobid'] < job_id \
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:
@ -267,7 +261,6 @@ class TemplateRegistry(object):
# Algebra tells us the diff_to_target is the same as hash_to_diff # Algebra tells us the diff_to_target is the same as hash_to_diff
share_diff = int(self.diff_to_target(hash_int)) share_diff = int(self.diff_to_target(hash_int))
# 5. Compare hash with target of the network # 5. Compare hash with target of the network
if hash_int <= job.target: if hash_int <= job.target:
# Yay! It is block candidate! # Yay! It is block candidate!

View File

@ -5,6 +5,8 @@ from twisted.internet.error import ConnectionRefusedError
import time import time
import simplejson as json import simplejson as json
from twisted.internet import reactor from twisted.internet import reactor
import threading
from mining.work_log_pruner import WorkLogPruner
@defer.inlineCallbacks @defer.inlineCallbacks
def setup(on_startup): def setup(on_startup):
@ -86,6 +88,10 @@ def setup(on_startup):
# This is just failsafe solution when -blocknotify # This is just failsafe solution when -blocknotify
# mechanism is not working properly # mechanism is not working properly
BlockUpdater(registry, bitcoin_rpc) BlockUpdater(registry, bitcoin_rpc)
prune_thr = threading.Thread(target=WorkLogPruner, args=(Interfaces.worker_manager.job_log,))
prune_thr.daemon = True
prune_thr.start()
log.info("MINING SERVICE IS READY") log.info("MINING SERVICE IS READY")
on_startup.callback(True) on_startup.callback(True)

View File

@ -167,10 +167,13 @@ class BasicShareLimiter(object):
self.worker_stats[worker_name]['buffer'].clear() self.worker_stats[worker_name]['buffer'].clear()
session = connection_ref().get_session() session = connection_ref().get_session()
(job_id, prevhash, coinb1, coinb2, merkle_branch, version, nbits, ntime, _) = \
Interfaces.template_registry.get_last_broadcast_args()
work_id = Interfaces.worker_manager.register_work(worker_name, job_id, new_diff)
session['prev_diff'] = session['difficulty']
session['prev_jobid'] = job_id
session['difficulty'] = new_diff session['difficulty'] = new_diff
connection_ref().rpc('mining.set_difficulty', [new_diff, ], is_notification=True) connection_ref().rpc('mining.set_difficulty', [new_diff, ], is_notification=True)
connection_ref().rpc('mining.notify', [work_id, prevhash, coinb1, coinb2, merkle_branch, version, nbits, ntime, False, ], is_notification=True)
dbi.update_worker_diff(worker_name, new_diff) dbi.update_worker_diff(worker_name, new_diff)

View File

@ -19,6 +19,8 @@ class WorkerManagerInterface(object):
def __init__(self): def __init__(self):
self.worker_log = {} self.worker_log = {}
self.worker_log.setdefault('authorized', {}) self.worker_log.setdefault('authorized', {})
self.job_log = {}
self.job_log.setdefault('None', {})
return return
def authorize(self, worker_name, worker_password): def authorize(self, worker_name, worker_password):
@ -33,6 +35,22 @@ class WorkerManagerInterface(object):
else: else:
return (False, settings.POOL_TARGET) return (False, settings.POOL_TARGET)
def register_work(self, worker_name, job_id, difficulty):
now = Interfaces.timestamper.time()
work_id = WorkIdGenerator.get_new_id()
self.job_log.setdefault(worker_name, {})[work_id] = (job_id, difficulty, now)
return work_id
class WorkIdGenerator(object):
counter = 1000
@classmethod
def get_new_id(cls):
cls.counter += 1
if cls.counter % 0xffff == 0:
cls.counter = 1
return "%x" % cls.counter
class ShareLimiterInterface(object): class ShareLimiterInterface(object):
'''Implement difficulty adjustments here''' '''Implement difficulty adjustments here'''

View File

@ -80,7 +80,7 @@ class MiningService(GenericService):
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, work_id, extranonce2, ntime, nonce):
'''Try to solve block candidate using given parameters.''' '''Try to solve block candidate using given parameters.'''
session = self.connection_ref().get_session() session = self.connection_ref().get_session()
@ -96,8 +96,16 @@ class MiningService(GenericService):
if not extranonce1_bin: if not extranonce1_bin:
raise SubmitException("Connection is not subscribed for mining") raise SubmitException("Connection is not subscribed for mining")
# Get current block job_id
difficulty = session['difficulty'] difficulty = session['difficulty']
s_difficulty = difficulty if worker_name in Interfaces.worker_manager.job_log and work_id in Interfaces.worker_manager.job_log[worker_name]:
(job_id, difficulty, job_ts) = Interfaces.worker_manager.job_log[worker_name][work_id]
else:
job_ts = Interfaces.timestamper.time()
Interfaces.worker_manager.job_log.setdefault(worker_name, {})[work_id] = (work_id, difficulty, job_ts)
job_id = work_id
#log.debug("worker_job_log: %s" % repr(Interfaces.worker_manager.job_log))
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] (valid, invalid, is_banned, diff, is_ext_diff, last_ts) = Interfaces.worker_manager.worker_log['authorized'][worker_name]
@ -119,11 +127,7 @@ class MiningService(GenericService):
log.debug("Clearing worker stats for: %s" % worker_name) log.debug("Clearing worker stats for: %s" % worker_name)
(valid, invalid, is_banned, last_ts) = (0, 0, is_banned, Interfaces.timestamper.time()) (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 \ log.debug("%s (%d, %d, %s, %s, %d) %0.2f%% work_id(%s) job_id(%s) diff(%f)" % (worker_name, valid, invalid, is_banned, is_ext_diff, last_ts, percent, work_id, job_id, difficulty))
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: if not is_ext_diff:
Interfaces.share_limiter.submit(self.connection_ref, job_id, difficulty, submit_time, worker_name) Interfaces.share_limiter.submit(self.connection_ref, job_id, difficulty, submit_time, worker_name)
@ -131,11 +135,11 @@ class MiningService(GenericService):
# 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, s_difficulty, submit_time) worker_name, session, extranonce1_bin, extranonce2, ntime, nonce, difficulty)
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 invalid += 1
Interfaces.worker_manager.worker_log['authorized'][worker_name] = (valid, invalid, is_banned, diff, is_ext_diff, last_ts) Interfaces.worker_manager.worker_log['authorized'][worker_name] = (valid, invalid, is_banned, difficulty, is_ext_diff, last_ts)
if is_banned: if is_banned:
raise SubmitException("Worker is temporarily banned") raise SubmitException("Worker is temporarily banned")
@ -145,7 +149,7 @@ class MiningService(GenericService):
raise raise
valid += 1 valid += 1
Interfaces.worker_manager.worker_log['authorized'][worker_name] = (valid, invalid, is_banned, diff, is_ext_diff, last_ts) Interfaces.worker_manager.worker_log['authorized'][worker_name] = (valid, invalid, is_banned, difficulty, is_ext_diff, last_ts)
if is_banned: if is_banned:
raise SubmitException("Worker is temporarily banned") raise SubmitException("Worker is temporarily banned")

View File

@ -21,9 +21,18 @@ class MiningSubscription(Subscription):
(job_id, prevhash, coinb1, coinb2, merkle_branch, version, nbits, ntime, _) = \ (job_id, prevhash, coinb1, coinb2, merkle_branch, version, nbits, ntime, _) = \
Interfaces.template_registry.get_last_broadcast_args() Interfaces.template_registry.get_last_broadcast_args()
# Push new job to subscribed clients # Push new job to subscribed clients
cls.emit(job_id, prevhash, coinb1, coinb2, merkle_branch, version, nbits, ntime, clean_jobs) for subscription in Pubsub.iterate_subscribers(cls.event):
session = subscription.connection_ref().get_session()
session.setdefault('authorized', {})
if session['authorized'].keys():
worker_name = session['authorized'].keys()[0]
difficulty = session['difficulty']
work_id = Interfaces.worker_manager.register_work(worker_name, job_id, difficulty)
subscription.emit_single(work_id, prevhash, coinb1, coinb2, merkle_branch, version, nbits, ntime, clean_jobs)
else:
subscription.emit_single(job_id, prevhash, coinb1, coinb2, merkle_branch, version, nbits, ntime, clean_jobs)
cnt = Pubsub.get_subscription_count(cls.event) cnt = Pubsub.get_subscription_count(cls.event)
log.info("BROADCASTED to %d connections in %.03f sec" % (cnt, (Interfaces.timestamper.time() - start))) log.info("BROADCASTED to %d connections in %.03f sec" % (cnt, (Interfaces.timestamper.time() - start)))

23
mining/work_log_pruner.py Normal file
View File

@ -0,0 +1,23 @@
from time import sleep, time
import traceback
import lib.logger
log = lib.logger.get_logger('work_log_pruner')
def _WorkLogPruner_I(wl):
now = time()
pruned = 0
for username in wl:
userwork = wl[username]
for wli in tuple(userwork.keys()):
if now > userwork[wli][2] + 120:
del userwork[wli]
pruned += 1
log.debug('Pruned %d jobs' % (pruned,))
def WorkLogPruner(wl):
while True:
try:
sleep(60)
_WorkLogPruner_I(wl)
except:
log.debug(traceback.format_exc())