from Source import Source from base64 import b64encode from httplib import HTTPException from json import dumps, loads from log import say_exception, say_line from struct import pack from threading import Thread from time import sleep, time from urlparse import urlsplit from util import if_else import httplib import socket import socks class NotAuthorized(Exception): pass class RPCError(Exception): pass class GetworkSource(Source): def __init__(self, switch): super(GetworkSource, self).__init__(switch) self.connection = self.lp_connection = None self.long_poll_timeout = 3600 self.max_redirects = 3 self.postdata = {'method': 'getwork', 'id': 'json'} self.headers = {"User-Agent": self.switch.user_agent, "Authorization": 'Basic ' + b64encode('%s:%s' % (self.server().user, self.server().pwd)), "X-Mining-Extensions": 'hostlist midstate rollntime'} self.long_poll_url = '' self.long_poll_active = False self.authorization_failed = False def loop(self): if self.authorization_failed: return super(GetworkSource, self).loop() thread = Thread(target=self.long_poll_thread) thread.daemon = True thread.start() while True: if self.should_stop: return if self.check_failback(): return True try: with self.switch.lock: miner = self.switch.updatable_miner() while miner: work = self.getwork() self.queue_work(work, miner) miner = self.switch.updatable_miner() self.process_result_queue() sleep(1) except Exception: say_exception("Unexpected error:") break def ensure_connected(self, connection, proto, host): if connection != None and connection.sock != None: return connection, False if proto == 'https': connector = httplib.HTTPSConnection else: connector = httplib.HTTPConnection if not self.options.proxy: return connector(host, strict=True), True host, port = host.split(':') proxy_proto, user, pwd, proxy_host = self.options.proxy[:4] proxy_port = 9050 proxy_host = proxy_host.split(':') if len(proxy_host) > 1: proxy_port = int(proxy_host[1]); proxy_host = proxy_host[0] connection = connector(host, strict=True) connection.sock = socks.socksocket() proxy_type = socks.PROXY_TYPE_SOCKS5 if proxy_proto == 'http': proxy_type = socks.PROXY_TYPE_HTTP elif proxy_proto == 'socks4': proxy_type = socks.PROXY_TYPE_SOCKS4 connection.sock.setproxy(proxy_type, proxy_host, proxy_port, True, user, pwd) try: connection.sock.connect((host, int(port))) except socks.Socks5AuthError: say_exception('Proxy error:') self.stop() return connection, True def request(self, connection, url, headers, data=None, timeout=0): result = response = None try: if data: connection.request('POST', url, data, headers) else: connection.request('GET', url, headers=headers) response = self.timeout_response(connection, timeout) if not response: return None if response.status == httplib.UNAUTHORIZED: say_line('Wrong username or password for %s', self.server().name) self.authorization_failed = True raise NotAuthorized() r = self.max_redirects while response.status == httplib.TEMPORARY_REDIRECT: response.read() url = response.getheader('Location', '') if r == 0 or url == '': raise HTTPException('Too much or bad redirects') connection.request('GET', url, headers=headers) response = self.timeout_response(connection, timeout) r -= 1 self.long_poll_url = response.getheader('X-Long-Polling', '') self.switch.update_time = bool(response.getheader('X-Roll-NTime', '')) hostList = response.getheader('X-Host-List', '') self.stratum_header = response.getheader('x-stratum', '') if (not self.options.nsf) and hostList: self.switch.add_servers(loads(hostList)) result = loads(response.read()) if result['error']: say_line('server error: %s', result['error']['message']) raise RPCError(result['error']['message']) return (connection, result) finally: if not result or not response or (response.version == 10 and response.getheader('connection', '') != 'keep-alive') or response.getheader('connection', '') == 'close': connection.close() connection = None def timeout_response(self, connection, timeout): if timeout: start = time() connection.sock.settimeout(5) response = None while not response: if self.should_stop or time() - start > timeout: return try: response = connection.getresponse() except socket.timeout: pass connection.sock.settimeout(timeout) return response else: return connection.getresponse() def getwork(self, data=None): try: self.connection = self.ensure_connected(self.connection, self.server().proto, self.server().host)[0] self.postdata['params'] = if_else(data, [data], []) (self.connection, result) = self.request(self.connection, '/', self.headers, dumps(self.postdata)) self.switch.connection_ok() return result['result'] except (IOError, httplib.HTTPException, ValueError, socks.ProxyError, NotAuthorized, RPCError): self.stop() except Exception: say_exception() def send_internal(self, result, nonce): data = ''.join([result.header.encode('hex'), pack('III', long(result.time), long(result.difficulty), long(nonce)).encode('hex'), '000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000']) accepted = self.getwork(data) if accepted != None: self.switch.report(result.miner, nonce, accepted) return True def long_poll_thread(self): last_host = None while True: if self.should_stop or self.authorization_failed: return url = self.long_poll_url if url != '': proto = self.server().proto host = self.server().host parsedUrl = urlsplit(url) if parsedUrl.scheme != '': proto = parsedUrl.scheme if parsedUrl.netloc != '': host = parsedUrl.netloc url = url[url.find(host) + len(host):] if url == '': url = '/' try: if host != last_host: self.close_lp_connection() self.lp_connection, changed = self.ensure_connected(self.lp_connection, proto, host) if changed: say_line("LP connected to %s", self.server().name) last_host = host self.long_poll_active = True response = self.request(self.lp_connection, url, self.headers, timeout=self.long_poll_timeout) self.long_poll_active = False if response: (self.lp_connection, result) = response self.queue_work(result['result']) if self.options.verbose: say_line('long poll: new block %s%s', (result['result']['data'][56:64], result['result']['data'][48:56])) except (IOError, httplib.HTTPException, ValueError, socks.ProxyError, NotAuthorized, RPCError): say_exception('long poll IO error') self.close_lp_connection() sleep(.5) except Exception: say_exception() def stop(self): self.should_stop = True self.close_lp_connection() self.close_connection() def close_connection(self): if self.connection: self.connection.close() self.connection = None def close_lp_connection(self): if self.lp_connection: self.lp_connection.close() self.lp_connection = None def queue_work(self, work, miner=None): if work: if not 'target' in work: work['target'] = '0000000000000000000000000000000000000000000000000000ffff00000000' self.switch.queue_work(self, work['data'], work['target'], miner=miner) def detect_stratum(self): work = self.getwork() if self.authorization_failed: return False if work: if self.stratum_header: host = self.stratum_header proto = host.find('://') if proto != -1: host = self.stratum_header[proto+3:] #this doesn't work in windows/python 2.6 #host = urlparse.urlparse(self.stratum_header).netloc say_line('diverted to stratum on %s', host) return host else: say_line('using JSON-RPC (no stratum header)') self.queue_work(work) return False say_line('no response to getwork, using as stratum') return self.server().host