Merge branch 'bugfix_gmpproxy_leak'
[bitcoin:eloipool.git] / gmp-proxy.py
1 #!/usr/bin/python3
2
3 import logging
4 logging.basicConfig(level=logging.DEBUG)
5
6 from binascii import b2a_hex
7 import bitcoin.txn
8 import bitcoin.varlen
9 import jsonrpc
10 import jsonrpcserver
11 import jsonrpc_getwork
12 import merkletree
13 import socket
14 from struct import pack
15 import sys
16 import threading
17 from time import time
18 from util import RejectedShare
19
20 try:
21         import jsonrpc.authproxy
22         jsonrpc.authproxy.USER_AGENT = 'gmp-proxy/0.1'
23 except:
24         pass
25
26 pool = jsonrpc.ServiceProxy(sys.argv[1])
27
28 worklog = {}
29 currentwork = [None, 0, 0]
30
31 def makeMRD(mp):
32         coinbase = bytes.fromhex(mp['coinbasetxn'])
33         cbtxn = bitcoin.txn.Txn(coinbase)
34         cbtxn.disassemble()
35         cbtxn.originalCB = cbtxn.getCoinbase()
36         txnlist = [cbtxn,] + list(map(bitcoin.txn.Txn, map(bytes.fromhex, mp['transactions'])))
37         merkleTree = merkletree.MerkleTree(txnlist)
38         merkleRoot = None
39         prevBlock = bytes.fromhex(mp['previousblockhash'])[::-1]
40         bits = bytes.fromhex(mp['bits'])[::-1]
41         rollPrevBlk = False
42         MRD = (merkleRoot, merkleTree, coinbase, prevBlock, bits, rollPrevBlk, mp)
43         if 'coinbase/append' in mp.get('mutable', ()):
44                 currentwork[:] = (MRD, time(), 0)
45         else:
46                 currentwork[2] = 0
47         return MRD
48
49 def getMRD():
50         now = time()
51         if currentwork[1] < now - 45:
52                 mp = pool.getmemorypool()
53                 MRD = makeMRD(mp)
54         else:
55                 MRD = currentwork[0]
56                 currentwork[2] += 1
57         
58         (merkleRoot, merkleTree, coinbase, prevBlock, bits, rollPrevBlk, mp) = MRD
59         cbtxn = merkleTree.data[0]
60         coinbase = cbtxn.originalCB + pack('>Q', currentwork[2]).lstrip(b'\0')
61         if len(coinbase) > 100:
62                 if len(cbtxn.originalCB) > 100:
63                         raise RuntimeError('Pool gave us a coinbase that is too long!')
64                 currentwork[1] = 0
65                 return getMRD()
66         cbtxn.setCoinbase(coinbase)
67         cbtxn.assemble()
68         merkleRoot = merkleTree.merkleRoot()
69         MRD = (merkleRoot, merkleTree, coinbase, prevBlock, bits, rollPrevBlk, mp)
70         return MRD
71
72 def MakeWork(username):
73         MRD = getMRD()
74         (merkleRoot, merkleTree, coinbase, prevBlock, bits, rollPrevBlk, mp) = MRD
75         timestamp = pack('<L', int(time()))
76         hdr = b'\1\0\0\0' + prevBlock + merkleRoot + timestamp + bits + b'ppmg'
77         worklog[hdr[4:68]] = (MRD, time())
78         return hdr
79
80 def SubmitShare(share):
81         hdr = share['data'][:80]
82         k = hdr[4:68]
83         if k not in worklog:
84                 raise RejectedShare('LOCAL unknown-work')
85         (MRD, issueT) = worklog[k]
86         (merkleRoot, merkleTree, coinbase, prevBlock, bits, rollPrevBlk, mp) = MRD
87         cbtxn = merkleTree.data[0]
88         cbtxn.setCoinbase(coinbase)
89         cbtxn.assemble()
90         blkdata = bitcoin.varlen.varlenEncode(len(merkleTree.data))
91         for txn in merkleTree.data:
92                 blkdata += txn.data
93         data = b2a_hex(hdr + blkdata).decode('utf8')
94         a = [data]
95         if 'workid' in mp:
96                 a.append({'workid': mp['workid']})
97         rejReason = pool.submitblock(*a)
98         if not rejReason is None:
99                 currentwork[1] = 0
100                 raise RejectedShare('pool-' + rejReason)
101
102 def HandleLP():
103         global server
104         
105         # FIXME: get path from gmp!
106         pool = jsonrpc.ServiceProxy(sys.argv[1].rstrip('/') + '/LP')
107         while True:
108                 try:
109                         mp = pool.getmemorypool()
110                         break
111                 except socket.timeout:
112                         pass
113         jsonrpc_getwork._CheckForDupesHACK = {}
114         makeMRD(mp)
115         server.wakeLongpoll()
116
117 LPThread = None
118 LPTrackReal = jsonrpcserver.JSONRPCHandler.LPTrack
119 class LPHook:
120         def LPTrack(self):
121                 global LPThread
122                 if LPThread is None or not LPThread.is_alive():
123                         LPThread = threading.Thread(target=HandleLP)
124                         LPThread.daemon = True
125                         LPThread.start()
126                 return LPTrackReal(self)
127 jsonrpcserver.JSONRPCHandler.LPTrack = LPHook.LPTrack
128
129 server = jsonrpcserver.JSONRPCServer()
130 server.getBlockHeader = MakeWork
131 server.receiveShare = SubmitShare
132 jsonrpcserver.JSONRPCListener(server, ('', 9332))
133
134 server.serve_forever()