stratum support
[p2pool.git] / p2pool / bitcoin / stratum.py
diff --git a/p2pool/bitcoin/stratum.py b/p2pool/bitcoin/stratum.py
new file mode 100644 (file)
index 0000000..6fcc3e7
--- /dev/null
@@ -0,0 +1,79 @@
+import random
+
+from twisted.internet import protocol, reactor
+
+from p2pool.bitcoin import data as bitcoin_data, getwork
+from p2pool.util import expiring_dict, jsonrpc, pack
+
+
+class StratumRPCMiningProvider(object):
+    def __init__(self, wb, other):
+        self.wb = wb
+        self.other = other
+        
+        self.username = None
+        self.handler_map = expiring_dict.ExpiringDict(300)
+        
+        self.watch_id = self.wb.new_work_event.watch(self._send_work)
+    
+    def rpc_subscribe(self):
+        return [
+            ["mining.notify", "ae6812eb4cd7735a302a8a9dd95cf71f"], # subscription details
+            "", # extranonce1
+            4, # extranonce2_size
+        ]
+    
+    def rpc_authorize(self, username, password):
+        self.username = username
+        
+        reactor.callLater(0, self._send_work)
+    
+    def _send_work(self):
+        if self.username is None: # authorize hasn't been received yet
+            return
+        
+        x, got_response = self.wb.get_work(*self.wb.get_user_details(self.username)[1:])
+        jobid = str(random.randrange(2**128))
+        self.other.svc_mining.rpc_set_difficulty(bitcoin_data.target_to_difficulty(x['share_target'])).addErrback(lambda err: None)
+        self.other.svc_mining.rpc_notify(
+            jobid, # jobid
+            getwork._swap4(pack.IntType(256).pack(x['previous_block'])).encode('hex'), # prevhash
+            x['coinb1'].encode('hex'), # coinb1
+            x['coinb2'].encode('hex'), # coinb2
+            [pack.IntType(256).pack(s).encode('hex') for s in x['merkle_link']['branch']], # merkle_branch
+            getwork._swap4(pack.IntType(32).pack(x['version'])).encode('hex'), # version
+            getwork._swap4(pack.IntType(32).pack(x['bits'].bits)).encode('hex'), # nbits
+            getwork._swap4(pack.IntType(32).pack(x['timestamp'])).encode('hex'), # ntime
+            True, # clean_jobs
+        ).addErrback(lambda err: None)
+        self.handler_map[jobid] = x, got_response
+    
+    def rpc_submit(self, worker_name, job_id, extranonce2, ntime, nonce):
+        x, got_response = self.handler_map[job_id]
+        coinb_nonce = pack.IntType(32).unpack(extranonce2.decode('hex'))
+        new_packed_gentx = x['coinb1'] + pack.IntType(32).pack(coinb_nonce) + x['coinb2']
+        header = dict(
+            version=x['version'],
+            previous_block=x['previous_block'],
+            merkle_root=bitcoin_data.check_merkle_link(bitcoin_data.hash256(new_packed_gentx), x['merkle_link']),
+            timestamp=pack.IntType(32).unpack(getwork._swap4(ntime.decode('hex'))),
+            bits=x['bits'],
+            nonce=pack.IntType(32).unpack(getwork._swap4(nonce.decode('hex'))),
+        )
+        return got_response(header, worker_name, coinb_nonce)
+    
+    def close(self):
+        self.wb.new_work_event.unwatch(self.watch_id)
+
+class StratumProtocol(jsonrpc.LineBasedPeer):
+    def connectionMade(self):
+        self.svc_mining = StratumRPCMiningProvider(self.factory.wb, self.other)
+    
+    def connectionLost(self, reason):
+        self.svc_mining.close()
+
+class StratumServerFactory(protocol.ServerFactory):
+    protocol = StratumProtocol
+    
+    def __init__(self, wb):
+        self.wb = wb