Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 320 lines (262 sloc) 9.458 kb
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
1 from Source import Source
d0b3913 @m0mchil support for stratum
authored
2 from binascii import hexlify, unhexlify
3 from hashlib import sha256
4 from json import dumps, loads
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
5 from log import say_exception, say_line
6 from struct import pack
c890423 @m0mchil implemented stratum reconnect and add_peers
authored
7 from threading import Thread, Lock, Timer
d0b3913 @m0mchil support for stratum
authored
8 from time import sleep, time
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
9 from util import chunks, Object
d0b3913 @m0mchil support for stratum
authored
10 import asynchat
11 import asyncore
12 import socket
13 import socks
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
14
15
d0b3913 @m0mchil support for stratum
authored
16 #import ssl
17
18
19 BASE_DIFFICULTY = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
20
e1fe107 @m0mchil detect stratum proxies
authored
21 def detect_stratum_proxy(host):
22 s = None
23 try:
24 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
25 s.sendto(dumps({"id": 0, "method": "mining.get_upstream", "params": []}), ('239.3.3.3', 3333))
26
27 say_line('Searching stratum proxy for %s', host)
28
29 s.settimeout(2)
30
31 try:
32 while True:
33 response, address = s.recvfrom(128)
34 try:
35 response = loads(response)
36 response_host = response['result'][0][0] + ':' + str(response['result'][0][1])
37 if response_host == host:
38 proxy_address = address[0] + ':' + str(response['result'][1])
39 say_line('Using stratum proxy at %s', proxy_address)
40 return proxy_address
41 except ValueError:
42 pass
43 except socket.timeout:
44 pass
45
46 finally:
47 if s != None:
48 s.close()
49
d0b3913 @m0mchil support for stratum
authored
50
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
51 class StratumSource(Source):
c890423 @m0mchil implemented stratum reconnect and add_peers
authored
52 def __init__(self, switch):
53 super(StratumSource, self).__init__(switch)
d0b3913 @m0mchil support for stratum
authored
54 self.handler = None
55 self.socket = None
56 self.channel_map = {}
57 self.subscribed = False
58 self.authorized = None
59 self.submits = {}
60 self.last_submits_cleanup = time()
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
61 self.server_difficulty = BASE_DIFFICULTY
d0b3913 @m0mchil support for stratum
authored
62 self.jobs = {}
63 self.current_job = None
64 self.extranonce = ''
65 self.extranonce2_size = 4
66 self.send_lock = Lock()
67
68 def loop(self):
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
69 super(StratumSource, self).loop()
d0b3913 @m0mchil support for stratum
authored
70
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
71 self.switch.update_time = True
f1ce310 @m0mchil initial for multi_miner
authored
72
d0b3913 @m0mchil support for stratum
authored
73 while True:
74 if self.should_stop: return
75
f1ce310 @m0mchil initial for multi_miner
authored
76 if self.current_job:
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
77 miner = self.switch.updatable_miner()
f1ce310 @m0mchil initial for multi_miner
authored
78 while miner:
79 self.current_job = self.refresh_job(self.current_job)
80 self.queue_work(self.current_job, miner)
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
81 miner = self.switch.updatable_miner()
d0b3913 @m0mchil support for stratum
authored
82
83 if self.check_failback():
84 return True
85
86 if not self.handler:
87 try:
88 #socket = ssl.wrap_socket(socket)
c890423 @m0mchil implemented stratum reconnect and add_peers
authored
89 address, port = self.server().host.split(':', 1)
d0b3913 @m0mchil support for stratum
authored
90
48a9787 @m0mchil further work on implementing multi miner support
authored
91
92 if not self.options.proxy:
fa165ee @m0mchil set keep alive only for TCP sockets
authored
93 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
d0b3913 @m0mchil support for stratum
authored
94 self.socket.connect((address, int(port)))
95 else:
96 self.socket = socks.socksocket()
9641ad4 @m0mchil fixed #55 (Proxy Woes)
authored
97 p = self.options.proxy
98 self.socket.setproxy(p.type, p.host, p.port, True, p.user, p.pwd)
d0b3913 @m0mchil support for stratum
authored
99 try:
100 self.socket.connect((address, int(port)))
48a9787 @m0mchil further work on implementing multi miner support
authored
101 except socks.Socks5AuthError:
102 say_exception('Proxy error:')
d0b3913 @m0mchil support for stratum
authored
103 self.stop()
104
105 self.handler = Handler(self.socket, self.channel_map, self)
106 thread = Thread(target=self.asyncore_thread)
107 thread.daemon = True
108 thread.start()
109
110 if not self.subscribe():
111 say_line('Failed to subscribe')
112 self.stop()
113 elif not self.authorize():
114 self.stop()
115
48a9787 @m0mchil further work on implementing multi miner support
authored
116 except socket.error:
117 say_exception()
d0b3913 @m0mchil support for stratum
authored
118 self.stop()
119 continue
120
121 with self.send_lock:
bb543e0 @m0mchil moved send logic to Transport
authored
122 self.process_result_queue()
d0b3913 @m0mchil support for stratum
authored
123 sleep(1)
124
125 def asyncore_thread(self):
126 asyncore.loop(map=self.channel_map)
127
128 def stop(self):
129 self.should_stop = True
130 if self.handler:
131 self.handler.close()
132
133 def refresh_job(self, j):
134 j.extranonce2 = self.increment_nonce(j.extranonce2)
135 coinbase = j.coinbase1 + self.extranonce + j.extranonce2 + j.coinbase2
a5ab744 @m0mchil use getwork only on explicit 'http';
authored
136 merkle_root = sha256(sha256(unhexlify(coinbase)).digest()).digest()
d0b3913 @m0mchil support for stratum
authored
137
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
138 for hash_ in j.merkle_branch:
139 merkle_root = sha256(sha256(merkle_root + unhexlify(hash_)).digest()).digest()
d0b3913 @m0mchil support for stratum
authored
140 merkle_root_reversed = ''
141 for word in chunks(merkle_root, 4):
142 merkle_root_reversed += word[::-1]
143 merkle_root = hexlify(merkle_root_reversed)
144
145 j.block_header = ''.join([j.version, j.prevhash, merkle_root, j.ntime, j.nbits])
146 j.time = time()
147 return j
148
149 def increment_nonce(self, nonce):
150 next_nonce = long(nonce, 16) + 1
151 if len('%x' % next_nonce) > (self.extranonce2_size * 2):
152 return '00' * self.extranonce2_size
153 return ('%0' + str(self.extranonce2_size * 2) +'x') % next_nonce
154
f1ce310 @m0mchil initial for multi_miner
authored
155 def handle_message(self, message):
d0b3913 @m0mchil support for stratum
authored
156
157 #Miner API
158 if 'method' in message:
159
160 #mining.notify
161 if message['method'] == 'mining.notify':
162 params = message['params']
163
164 j = Object()
165
166 j.job_id = params[0]
167 j.prevhash = params[1]
168 j.coinbase1 = params[2]
169 j.coinbase2 = params[3]
170 j.merkle_branch = params[4]
171 j.version = params[5]
172 j.nbits = params[6]
173 j.ntime = params[7]
174 clear_jobs = params[8]
175 if clear_jobs:
176 self.jobs.clear()
177 j.extranonce2 = self.extranonce2_size * '00'
178
179 j = self.refresh_job(j)
180
181 self.jobs[j.job_id] = j
182 self.current_job = j
183
184 self.queue_work(j)
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
185 self.switch.connection_ok()
d0b3913 @m0mchil support for stratum
authored
186
187 #mining.get_version
188 if message['method'] == 'mining.get_version':
189 with self.send_lock:
266a835 @m0mchil fixed missing id for 'subscribe' request
authored
190 self.send_message({"error": None, "id": message['id'], "result": self.user_agent})
191
d0b3913 @m0mchil support for stratum
authored
192 #mining.set_difficulty
193 elif message['method'] == 'mining.set_difficulty':
194 say_line("Setting new difficulty: %s", message['params'][0])
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
195 self.server_difficulty = BASE_DIFFICULTY / message['params'][0]
c890423 @m0mchil implemented stratum reconnect and add_peers
authored
196
d0b3913 @m0mchil support for stratum
authored
197 #client.reconnect
198 elif message['method'] == 'client.reconnect':
c890423 @m0mchil implemented stratum reconnect and add_peers
authored
199 address, port = self.server().host.split(':', 1)
200 (new_address, new_port, timeout) = message['params'][:3]
201 if new_address: address = new_address
202 if new_port != None: port = new_port
203 say_line("%s asked us to reconnect to %s:%d in %d seconds", (self.server().name, address, port, timeout))
204 self.server().host = address + ':' + str(port)
205 Timer(timeout, self.reconnect).start()
206
207 #client.add_peers
208 elif message['method'] == 'client.add_peers':
209 hosts = [{'host': host[0], 'port': host[1]} for host in message['params'][0]]
210 self.switch.add_servers(hosts)
d0b3913 @m0mchil support for stratum
authored
211
212 #responses to server API requests
213 elif 'result' in message:
214
215 #response to mining.subscribe
216 #store extranonce and extranonce2_size
266a835 @m0mchil fixed missing id for 'subscribe' request
authored
217 if message['id'] == 's':
d0b3913 @m0mchil support for stratum
authored
218 self.extranonce = message['result'][1]
219 self.extranonce2_size = message['result'][2]
220 self.subscribed = True
221
222 #check if this is submit confirmation (message id should be in submits dictionary)
48a9787 @m0mchil further work on implementing multi miner support
authored
223 #cleanup if necessary
d0b3913 @m0mchil support for stratum
authored
224 elif message['id'] in self.submits:
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
225 miner, nonce = self.submits[message['id']][:2]
f1ce310 @m0mchil initial for multi_miner
authored
226 accepted = message['result']
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
227 self.switch.report(miner, nonce, accepted)
d0b3913 @m0mchil support for stratum
authored
228 del self.submits[message['id']]
229 if time() - self.last_submits_cleanup > 3600:
230 now = time()
231 for key, value in self.submits.items():
f1ce310 @m0mchil initial for multi_miner
authored
232 if now - value[2] > 3600:
d0b3913 @m0mchil support for stratum
authored
233 del self.submits[key]
234 self.last_submits_cleanup = now
235
236 #response to mining.authorize
c890423 @m0mchil implemented stratum reconnect and add_peers
authored
237 elif message['id'] == self.server().user:
d0b3913 @m0mchil support for stratum
authored
238 if not message['result']:
c890423 @m0mchil implemented stratum reconnect and add_peers
authored
239 say_line('authorization failed with %s:%s@%s', (self.server().user, self.server().pwd, self.server().host))
d0b3913 @m0mchil support for stratum
authored
240 self.authorized = False
241 else:
242 self.authorized = True
243
c890423 @m0mchil implemented stratum reconnect and add_peers
authored
244 def reconnect(self):
245 say_line("%s reconnecting to %s", (self.server().name, self.server().host))
246 self.handler.close()
247
d0b3913 @m0mchil support for stratum
authored
248 def subscribe(self):
266a835 @m0mchil fixed missing id for 'subscribe' request
authored
249 self.send_message({'id': 's', 'method': 'mining.subscribe', 'params': []})
d0b3913 @m0mchil support for stratum
authored
250 for i in xrange(10):
251 sleep(1)
252 if self.subscribed: break
253 return self.subscribed
254
255 def authorize(self):
c890423 @m0mchil implemented stratum reconnect and add_peers
authored
256 self.send_message({'id': self.server().user, 'method': 'mining.authorize', 'params': [self.server().user, self.server().pwd]})
d0b3913 @m0mchil support for stratum
authored
257 for i in xrange(10):
258 sleep(1)
259 if self.authorized != None: break
260 return self.authorized
261
262 def send_internal(self, result, nonce):
263 job_id = result.job_id
264 if not job_id in self.jobs:
265 return True
266 extranonce2 = result.extranonce2
a5ab744 @m0mchil use getwork only on explicit 'http';
authored
267 ntime = pack('<I', long(result.time)).encode('hex')
268 hex_nonce = pack('<I', long(nonce)).encode('hex')
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
269 id_ = job_id + hex_nonce
270 self.submits[id_] = (result.miner, nonce, time())
c890423 @m0mchil implemented stratum reconnect and add_peers
authored
271 return self.send_message({'params': [self.server().user, job_id, extranonce2, ntime, hex_nonce], 'id': id_, 'method': u'mining.submit'})
d0b3913 @m0mchil support for stratum
authored
272
273 def send_message(self, message):
274 data = dumps(message) + '\n'
275 try:
276 #self.handler.push(data)
277
278 #there is some bug with asyncore's send mechanism
f1ce310 @m0mchil initial for multi_miner
authored
279 #so we send data 'manually'
d0b3913 @m0mchil support for stratum
authored
280 #note that this is not thread safe
281 if not self.handler:
282 return False
283 while data:
284 sent = self.handler.send(data)
285 data = data[sent:]
286 return True
287 except AttributeError:
288 self.stop()
48a9787 @m0mchil further work on implementing multi miner support
authored
289 except Exception:
290 say_exception()
d0b3913 @m0mchil support for stratum
authored
291 self.stop()
292
f1ce310 @m0mchil initial for multi_miner
authored
293 def queue_work(self, work, miner=None):
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
294 target = ''.join(list(chunks('%064x' % self.server_difficulty, 2))[::-1])
295 self.switch.queue_work(self, work.block_header, target, work.job_id, work.extranonce2, miner)
d0b3913 @m0mchil support for stratum
authored
296
297 class Handler(asynchat.async_chat):
ba0a06d @m0mchil initial support for non-opencl devices (BFL for now)
authored
298 def __init__(self, socket, map_, parent):
299 asynchat.async_chat.__init__(self, socket, map_)
d0b3913 @m0mchil support for stratum
authored
300 self.parent = parent
301 self.data = ''
302 self.set_terminator('\n')
303
304 def handle_close(self):
305 self.close()
306 self.parent.handler = None
307 self.parent.socket = None
308
309 def handle_error(self):
48a9787 @m0mchil further work on implementing multi miner support
authored
310 say_exception()
d0b3913 @m0mchil support for stratum
authored
311 self.parent.stop()
312
313 def collect_incoming_data(self, data):
314 self.data += data
315
316 def found_terminator(self):
317 message = loads(self.data)
f1ce310 @m0mchil initial for multi_miner
authored
318 self.parent.handle_message(message)
d0b3913 @m0mchil support for stratum
authored
319 self.data = ''
Something went wrong with that request. Please try again.