stratum-mining/mining/basic_share_limiter.py
2014-01-24 22:05:12 +01:00

182 lines
7.2 KiB
Python

import lib.settings as settings
import lib.logger
log = lib.logger.get_logger('BasicShareLimiter')
import DBInterface
dbi = DBInterface.DBInterface()
dbi.clear_worker_diff()
from twisted.internet import defer
from mining.interfaces import Interfaces
import time
''' This is just a customized ring buffer '''
class SpeedBuffer:
def __init__(self, size_max):
self.max = size_max
self.data = []
self.cur = 0
def append(self, x):
self.data.append(x)
self.cur += 1
if len(self.data) == self.max:
self.cur = 0
self.__class__ = SpeedBufferFull
def avg(self):
return sum(self.data) / self.cur
def pos(self):
return self.cur
def clear(self):
self.data = []
self.cur = 0
def size(self):
return self.cur
class SpeedBufferFull:
def __init__(self, n):
raise "you should use SpeedBuffer"
def append(self, x):
self.data[self.cur] = x
self.cur = (self.cur + 1) % self.max
def avg(self):
return sum(self.data) / self.max
def pos(self):
return self.cur
def clear(self):
self.data = []
self.cur = 0
self.__class__ = SpeedBuffer
def size(self):
return self.max
class BasicShareLimiter(object):
def __init__(self):
self.worker_stats = {}
self.target = settings.VDIFF_TARGET_TIME
self.retarget = settings.VDIFF_RETARGET_TIME
self.variance = self.target * (float(settings.VDIFF_VARIANCE_PERCENT) / float(100))
self.tmin = self.target - self.variance
self.tmax = self.target + self.variance
self.buffersize = self.retarget / self.target * 4
self.litecoin = {}
self.litecoin_diff = 100000000 # TODO: Set this to VARDIFF_MAX
# TODO: trim the hash of inactive workers
@defer.inlineCallbacks
def update_litecoin_difficulty(self):
# Cache the litecoin difficulty so we do not have to query it on every submit
# Update the difficulty if it is out of date or not set
if 'timestamp' not in self.litecoin or self.litecoin['timestamp'] < int(time.time()) - settings.DIFF_UPDATE_FREQUENCY:
self.litecoin['timestamp'] = time.time()
self.litecoin['difficulty'] = (yield Interfaces.template_registry.bitcoin_rpc.getdifficulty())
log.debug("Updated litecoin difficulty to %s" % (self.litecoin['difficulty']))
self.litecoin_diff = self.litecoin['difficulty']
def submit(self, connection_ref, job_id, current_difficulty, timestamp, worker_name):
ts = int(timestamp)
# Init the stats for this worker if it isn't set.
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
# Standard share update of data
self.worker_stats[worker_name]['buffer'].append(ts - self.worker_stats[worker_name]['last_ts'])
self.worker_stats[worker_name]['last_ts'] = ts
# Do We retarget? If not, we're done.
if ts - self.worker_stats[worker_name]['last_rtc'] < self.retarget and self.worker_stats[worker_name]['buffer'].size() > 0:
return
# Set up and log our check
self.worker_stats[worker_name]['last_rtc'] = ts
avg = self.worker_stats[worker_name]['buffer'].avg()
log.debug("Checking Retarget for %s (%i) avg. %i target %i+-%i" % (worker_name, current_difficulty, avg,
self.target, self.variance))
if avg < 1:
log.warning("Reseting avg = 1 since it's SOOO low")
avg = 1
# Figure out our Delta-Diff
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 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 settings.VDIFF_X2_TYPE:
ddiff = 2
# Don't go above LITECOIN or VDIFF_MAX_TARGET
if settings.USE_COINDAEMON_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 ddiff < 1:
ddiff = 1
# Don't go above LITECOIN or VDIFF_MAX_TARGET
if settings.USE_COINDAEMON_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
if settings.VDIFF_X2_TYPE:
new_diff = current_difficulty * ddiff
else:
new_diff = current_difficulty + ddiff
log.debug("Retarget for %s %i old: %i new: %i" % (worker_name, ddiff, current_difficulty, new_diff))
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['difficulty'] = new_diff
connection_ref().rpc('mining.set_difficulty', [new_diff, ], is_notification=True)
log.debug("Notified of New Difficulty")
connection_ref().rpc('mining.notify', [work_id, prevhash, coinb1, coinb2, merkle_branch, version, nbits, ntime, False, ], is_notification=True)
log.debug("Sent new work")
dbi.update_worker_diff(worker_name, new_diff)