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

View File

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

View File

@ -5,6 +5,8 @@ from twisted.internet.error import ConnectionRefusedError
import time
import simplejson as json
from twisted.internet import reactor
import threading
from mining.work_log_pruner import WorkLogPruner
@defer.inlineCallbacks
def setup(on_startup):
@ -86,6 +88,10 @@ def setup(on_startup):
# This is just failsafe solution when -blocknotify
# mechanism is not working properly
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")
on_startup.callback(True)

View File

@ -167,10 +167,13 @@ class BasicShareLimiter(object):
self.worker_stats[worker_name]['buffer'].clear()
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
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)

View File

@ -19,6 +19,8 @@ class WorkerManagerInterface(object):
def __init__(self):
self.worker_log = {}
self.worker_log.setdefault('authorized', {})
self.job_log = {}
self.job_log.setdefault('None', {})
return
def authorize(self, worker_name, worker_password):
@ -33,6 +35,22 @@ class WorkerManagerInterface(object):
else:
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):
'''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
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.'''
session = self.connection_ref().get_session()
@ -96,8 +96,16 @@ class MiningService(GenericService):
if not extranonce1_bin:
raise SubmitException("Connection is not subscribed for mining")
# Get current block job_id
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()
ip = self.connection_ref()._get_ip()
(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)
(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))
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))
if not is_ext_diff:
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.
try:
(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:
# 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)
Interfaces.worker_manager.worker_log['authorized'][worker_name] = (valid, invalid, is_banned, difficulty, is_ext_diff, last_ts)
if is_banned:
raise SubmitException("Worker is temporarily banned")
@ -145,7 +149,7 @@ class MiningService(GenericService):
raise
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:
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, _) = \
Interfaces.template_registry.get_last_broadcast_args()
# 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)
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())