Permalink
Browse files

Add rollntime support to RPC and MMP backends. (Not supported by the …

…miner core yet, planned for a future release)

Fixed a race condition in RPC that could result in disconnects.
Removed fetchRange() limitation: Work requests are no longer required to be multiples of 256.
Bump version to 1.7.2

Signed-off-by: jedi95 <jedi95@gmail.com>
  • Loading branch information...
1 parent 3fb7c12 commit af1f8af2b0dc1ec2c6345bedc173997a46799af2 @jedi95 committed Dec 31, 2011
Showing with 82 additions and 33 deletions.
  1. +1 −1 Miner.py
  2. +1 −5 WorkQueue.py
  3. +7 −0 minerutil/ClientBase.py
  4. +7 −1 minerutil/MMPProtocol.py
  5. +66 −26 minerutil/RPCProtocol.py
View
@@ -29,7 +29,7 @@
class Miner(object):
# This must be manually set for Git
- VER = (1, 7, 1)
+ VER = (1, 7, 2)
REVISION = reduce(lambda x,y: x*100+y, VER)
VERSION = 'v%s' % '.'.join(str(x) for x in VER)
View
@@ -36,8 +36,7 @@ class WorkUnit(object):
"""A NonceRange is a range of nonces from a WorkUnit, to be dispatched in a
single execution of a mining kernel. The size of the NonceRange can be
-adjusted to tune the performance of the kernel, but will always
-be a multiple of 256.
+adjusted to tune the performance of the kernel.
This class doesn't actually do anything, it's just a well-defined container
that kernels can pull information out of.
@@ -167,9 +166,6 @@ def fetchRange(self, size=0x10000):
#make sure size is not too large
size = min(size, 0x100000000)
- #size must be a multiple of 256
- size = 256 * int(size / 256) #rounded up
-
#make sure size is not too small
size = max(size, 256)
View
@@ -19,10 +19,17 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
+import struct
+
class AssignedWork(object):
data = None
mask = None
target = None
+ maxtime = None
+ time = None
+ def setMaxTimeIncrement(self, n):
+ self.time = n
+ self.maxtime = struct.unpack('>I', self.data[68:72])[0] + n
class ClientBase(object):
callbacksActive = True
View
@@ -83,6 +83,7 @@ class MMPClientProtocol(MMPProtocolBase, ClientBase):
# A suitable default, but the server really should set this itself.
target = ('\xff'*28) + ('\x00'*4)
+ time = 0
metaSent = False
@@ -93,6 +94,7 @@ class MMPClientProtocol(MMPProtocolBase, ClientBase):
'BLOCK': (int,),
'ACCEPTED': (str,),
'REJECTED': (str,),
+ 'TIME': (int,),
}
def connectionMade(self):
@@ -126,6 +128,9 @@ def cmd_TARGET(self, target):
if len(t) == 32:
self.target = t
+ def cmd_TIME(self, time):
+ self.time = time
+
def cmd_WORK(self, work, mask):
try:
data = work.decode('hex')
@@ -137,6 +142,7 @@ def cmd_WORK(self, work, mask):
wu.data = data
wu.mask = mask
wu.target = self.target
+ wu.setMaxTimeIncrement(self.time)
self.runCallback('work', wu)
# Since the server is giving work, we know it has accepted our
# login details, so we can reset the factory's reconnect delay.
@@ -163,7 +169,7 @@ class MMPClient(ReconnectingClientFactory, ClientBase):
username = None
password = None
- meta = {'version': 'MMPClient v0.8 by CFSworks'}
+ meta = {'version': 'MMPClient v1.0 by CFSworks'}
deferreds = {}
connection = None
View
@@ -92,7 +92,10 @@ def _doRequest(self, url, *args):
socket.SO_KEEPALIVE, 1)
try:
self.connection.request(*args)
- return self.connection.getresponse()
+ response = self.connection.getresponse()
+ headers = response.getheaders()
+ data = response.read()
+ return (headers, data)
except (httplib.HTTPException, socket.error):
self.closeConnection()
raise
@@ -143,9 +146,6 @@ def errback(failure):
try:
if failure.check(ServerMessage):
self.root.runCallback('msg', failure.getErrorMessage())
- else:
- self.root.runCallback('debug', failure.getErrorMessage())
-
self.root._failure()
finally:
self._startCall()
@@ -158,7 +158,7 @@ def callback(x):
(headers, result) = x
except TypeError:
return
- self.root.handleWork(result)
+ self.root.handleWork(result, headers)
self.root.handleHeaders(headers)
finally:
self._startCall()
@@ -183,9 +183,9 @@ def call(self, method, params=[]):
'Content-Type': 'application/json'
})
- data = response.read()
+ (headers, data) = response
result = self.parse(data)
- defer.returnValue((dict(response.getheaders()), result))
+ defer.returnValue((dict(headers), result))
@classmethod
def parse(cls, data):
@@ -252,7 +252,13 @@ def _requestComplete(self, response):
if isinstance(response, failure.Failure):
return
- data = response.read()
+ try:
+ (headers, data) = response
+ except TypeError:
+ #handle case where response doesn't contain valid data
+ self.root.runCallback('debug', 'TypeError in LP response:')
+ self.root.runCallback('debug', str(response))
+ return
try:
result = RPCPoller.parse(data)
@@ -266,7 +272,7 @@ def _requestComplete(self, response):
finally:
self._request()
- self.root.handleWork(result, True)
+ self.root.handleWork(result, headers, True)
class RPCClient(ClientBase):
"""The actual root of the whole RPC client system."""
@@ -281,22 +287,23 @@ def __init__(self, handler, url):
self.params[s[0]] = s[1]
self.auth = 'Basic ' + ('%s:%s' % (
url.username, url.password)).encode('base64').strip()
- self.version = 'RPCClient/1.8'
+ self.version = 'RPCClient/2.0'
self.poller = RPCPoller(self)
self.longPoller = None # Gets created later...
self.disconnected = False
self.saidConnected = False
self.block = None
+ self.setupMaxtime()
def connect(self):
"""Begin communicating with the server..."""
self.poller.ask()
def disconnect(self):
- """Cease server communications immediately. The client might be
- reusable, but it's probably best not to try.
+ """Cease server communications immediately. The client is probably not
+ reusable, so it's probably best not to try.
"""
self._deactivateCallbacks()
@@ -307,6 +314,16 @@ def disconnect(self):
self.longPoller.stop()
self.longPoller = None
+ def setupMaxtime(self):
+ try:
+ self.maxtime = int(self.params['maxtime'])
+ if self.maxtime < 0:
+ self.maxtime = 0
+ elif self.maxtime > 3600:
+ self.maxtime = 3600
+ except (KeyError, ValueError):
+ self.maxtime = 60
+
def setMeta(self, var, value):
"""RPC clients do not support meta. Ignore."""
@@ -339,7 +356,7 @@ def callback(x):
(headers, accepted) = x
except TypeError:
self.runCallback('debug',
- "TypeError in RPC sendResult callback")
+ 'TypeError in RPC sendResult callback')
return False
if (not accepted):
@@ -355,7 +372,7 @@ def callback(x):
def handleRejectReason(self, headers):
reason = headers.get('x-reject-reason')
if reason is not None:
- self.runCallback('debug', "Reject reason: " + str(reason))
+ self.runCallback('debug', 'Reject reason: ' + str(reason))
def useAskrate(self, variable):
defaults = {'askrate': 10, 'retryrate': 15, 'lpaskrate': 0}
@@ -365,29 +382,49 @@ def useAskrate(self, variable):
askrate = defaults.get(variable, 10)
self.poller.setInterval(askrate)
- def handleWork(self, work, pushed=False):
+ def handleWork(self, work, headers, pushed=False):
if work is None:
return;
+ try:
+ rollntime = headers.get('x-roll-ntime')
+ except:
+ rollntime = None
+
+ if rollntime:
+ if rollntime.lower().startswith('expires='):
@luke-jr

luke-jr Jan 27, 2012

This is wrong. It's expire=, not expires=

+ try:
+ maxtime = int(rollntime[8:])
+ except:
+ #if the server supports rollntime but doesn't format the
+ #request properly, then use a sensible default
+ maxtime = self.maxtime
+ else:
+ if rollntime.lower() in ('t', 'true', 'on', '1', 'y', 'yes'):
+ maxtime = self.maxtime
+ elif rollntime.lower() in ('f', 'false', 'off', '0', 'n', 'no'):
+ maxtime = 0
+ else:
+ try:
+ maxtime = int(rollntime)
+ except:
+ maxtime = self.maxtime
+ else:
+ maxtime = 0
+
+ if self.maxtime < maxtime:
+ maxtime = self.maxtime
+
if not self.saidConnected:
self.saidConnected = True
self.runCallback('connect')
self.useAskrate('askrate')
- if 'block' in work:
- try:
- block = int(work['block'])
- except (TypeError, ValueError):
- pass
- else:
- if self.block != block:
- self.block = block
- self.runCallback('block', block)
-
aw = AssignedWork()
aw.data = work['data'].decode('hex')[:80]
aw.target = work['target'].decode('hex')
aw.mask = work.get('mask', 32)
+ aw.setMaxTimeIncrement(maxtime)
if pushed:
self.runCallback('push', aw)
self.runCallback('work', aw)
@@ -401,8 +438,11 @@ def handleHeaders(self, headers):
if self.block != block:
self.block = block
self.runCallback('block', block)
+ try:
+ longpoll = headers.get('x-long-polling')
+ except:
+ longpoll = None
- longpoll = headers.get('x-long-polling')
if longpoll:
lpParsed = urlparse.urlparse(longpoll)
lpURL = urlparse.ParseResult(

0 comments on commit af1f8af

Please sign in to comment.