refuse stratum submits with unknown job_id instead of returning jsonrpc error
[p2pool.git] / p2pool / bitcoin / stratum.py
1 import random
2 import sys
3
4 from twisted.internet import protocol, reactor
5
6 from p2pool.bitcoin import data as bitcoin_data, getwork
7 from p2pool.util import expiring_dict, jsonrpc, pack
8
9
10 class StratumRPCMiningProvider(object):
11     def __init__(self, wb, other):
12         self.wb = wb
13         self.other = other
14         
15         self.username = None
16         self.handler_map = expiring_dict.ExpiringDict(300)
17         
18         self.watch_id = self.wb.new_work_event.watch(self._send_work)
19     
20     def rpc_subscribe(self):
21         return [
22             ["mining.notify", "ae6812eb4cd7735a302a8a9dd95cf71f"], # subscription details
23             "", # extranonce1
24             4, # extranonce2_size
25         ]
26     
27     def rpc_authorize(self, username, password):
28         self.username = username
29         
30         reactor.callLater(0, self._send_work)
31     
32     def _send_work(self):
33         if self.username is None: # authorize hasn't been received yet
34             return
35         
36         x, got_response = self.wb.get_work(*self.wb.preprocess_request(self.username))
37         jobid = str(random.randrange(2**128))
38         self.other.svc_mining.rpc_set_difficulty(bitcoin_data.target_to_difficulty(x['share_target'])).addErrback(lambda err: None)
39         self.other.svc_mining.rpc_notify(
40             jobid, # jobid
41             getwork._swap4(pack.IntType(256).pack(x['previous_block'])).encode('hex'), # prevhash
42             x['coinb1'].encode('hex'), # coinb1
43             x['coinb2'].encode('hex'), # coinb2
44             [pack.IntType(256).pack(s).encode('hex') for s in x['merkle_link']['branch']], # merkle_branch
45             getwork._swap4(pack.IntType(32).pack(x['version'])).encode('hex'), # version
46             getwork._swap4(pack.IntType(32).pack(x['bits'].bits)).encode('hex'), # nbits
47             getwork._swap4(pack.IntType(32).pack(x['timestamp'])).encode('hex'), # ntime
48             True, # clean_jobs
49         ).addErrback(lambda err: None)
50         self.handler_map[jobid] = x, got_response
51     
52     def rpc_submit(self, worker_name, job_id, extranonce2, ntime, nonce):
53         if job_id not in self.handler_map:
54             print >>sys.stderr, '''Couldn't link returned work's job id with its handler. This should only happen if this process was recently restarted!'''
55             return False
56         x, got_response = self.handler_map[job_id]
57         coinb_nonce = pack.IntType(32).unpack(extranonce2.decode('hex'))
58         new_packed_gentx = x['coinb1'] + pack.IntType(32).pack(coinb_nonce) + x['coinb2']
59         header = dict(
60             version=x['version'],
61             previous_block=x['previous_block'],
62             merkle_root=bitcoin_data.check_merkle_link(bitcoin_data.hash256(new_packed_gentx), x['merkle_link']),
63             timestamp=pack.IntType(32).unpack(getwork._swap4(ntime.decode('hex'))),
64             bits=x['bits'],
65             nonce=pack.IntType(32).unpack(getwork._swap4(nonce.decode('hex'))),
66         )
67         return got_response(header, worker_name, coinb_nonce)
68     
69     def close(self):
70         self.wb.new_work_event.unwatch(self.watch_id)
71
72 class StratumProtocol(jsonrpc.LineBasedPeer):
73     def connectionMade(self):
74         self.svc_mining = StratumRPCMiningProvider(self.factory.wb, self.other)
75     
76     def connectionLost(self, reason):
77         self.svc_mining.close()
78
79 class StratumServerFactory(protocol.ServerFactory):
80     protocol = StratumProtocol
81     
82     def __init__(self, wb):
83         self.wb = wb