Bugfix: Use a new thread to handle SIGUSR1, in case the signal handler is called...
[bitcoin:eloipool.git] / eloipool.py
1 #!/usr/bin/python3
2 import config
3
4
5 import logging
6
7 logging.basicConfig(level=logging.DEBUG)
8
9 def RaiseRedFlags(reason):
10         logging.getLogger('redflag').critical(reason)
11         return reason
12
13
14 from bitcoin.node import BitcoinLink
15 UpstreamBitcoind = BitcoinLink( config.UpstreamBitcoindNode, config.UpstreamNetworkId )
16
17
18 from bitcoin.script import BitcoinScript
19 from bitcoin.txn import Txn
20 from base58 import b58decode
21 from struct import pack
22 import subprocess
23 from time import time
24
25 def makeCoinbaseTxn(coinbaseValue, useCoinbaser = True):
26         t = Txn.new()
27         
28         if useCoinbaser and hasattr(config, 'CoinbaserCmd'):
29                 coinbased = 0
30                 try:
31                         cmd = config.CoinbaserCmd
32                         cmd = cmd.replace('%d', str(coinbaseValue))
33                         p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
34                         nout = int(p.stdout.readline())
35                         for i in range(nout):
36                                 amount = int(p.stdout.readline())
37                                 addr = p.stdout.readline().rstrip(b'\n').decode('utf8')
38                                 pkScript = BitcoinScript.toAddress(addr)
39                                 t.addOutput(amount, pkScript)
40                                 coinbased += amount
41                 except:
42                         coinbased = coinbaseValue + 1
43                 if coinbased >= coinbaseValue:
44                         logging.getLogger('makeCoinbaseTxn').error('Coinbaser failed!')
45                         t.outputs = []
46                 else:
47                         coinbaseValue -= coinbased
48         
49         pkScript = BitcoinScript.toAddress(config.TrackerAddr)
50         t.addOutput(coinbaseValue, pkScript)
51         
52         # TODO
53         # TODO: red flag on dupe coinbase
54         return t
55
56
57 from util import Bits2Target
58
59 workLog = {}
60 networkTarget = None
61
62 def blockChanged():
63         global MM, networkTarget
64         networkTarget = Bits2Target(MM.currentBlock[1])
65         workLog.clear()
66
67
68 from merklemaker import merkleMaker
69 MM = merkleMaker()
70 MM.__dict__.update(config.__dict__)
71 MM.clearCoinbaseTxn = makeCoinbaseTxn(5000000000, False)  # FIXME
72 MM.clearCoinbaseTxn.assemble()
73 MM.makeCoinbaseTxn = makeCoinbaseTxn
74 MM.onBlockChange = blockChanged
75 MM._THISISUGLY = UpstreamBitcoind
76 MM.start()
77
78
79 from binascii import b2a_hex
80 from struct import pack, unpack
81 import threading
82 from time import time
83 from util import RejectedShare, dblsha, hash2int
84
85 db = None
86 if hasattr(config, 'DbOptions'):
87         import psycopg2
88         db = psycopg2.connect(**config.DbOptions)
89
90 def getBlockHeader(username):
91         MRD = MM.getMRD()
92         (merkleRoot, merkleTree, coinbase, prevBlock, bits, rollPrevBlk) = MRD
93         timestamp = pack('<L', int(time()))
94         hdr = b'\1\0\0\0' + prevBlock + merkleRoot + timestamp + bits + b'iolE'
95         workLog.setdefault(username, {})[merkleRoot] = MRD
96         return hdr
97
98 def YN(b):
99         if b is None:
100                 return None
101         return 'Y' if b else 'N'
102
103 def logShare(share):
104         if db is None:
105                 return
106         dbc = db.cursor()
107         rem_host = share.get('remoteHost', '?')
108         username = share['username']
109         reason = share.get('rejectReason', None)
110         upstreamResult = share.get('upstreamResult', None)
111         solution = share['data']
112         solution = b2a_hex(solution).decode('utf8')
113         stmt = "insert into shares (rem_host, username, our_result, upstream_result, reason, solution) values (%s, %s, %s, %s, %s, decode(%s, 'hex'))"
114         params = (rem_host, username, YN(not reason), YN(upstreamResult), reason, solution)
115         print((stmt,params))
116         dbc.execute(stmt, params)
117         db.commit()
118
119 def checkShare(share):
120         data = share['data']
121         (prevBlock, bits) = MM.currentBlock
122         sharePrevBlock = data[4:36]
123         if sharePrevBlock != prevBlock:
124                 if sharePrevBlock == MM.lastBlock[0]:
125                         raise RejectedShare('stale-prevblk')
126                 raise RejectedShare('bad-prevblk')
127         
128         shareMerkleRoot = data[36:68]
129         # TODO: use userid
130         username = share['username']
131         if username not in workLog:
132                 raise RejectedShare('unknown-user')
133         MWL = workLog[username]
134         if shareMerkleRoot not in MWL:
135                 raise RejectedShare('unknown-work')
136         MRD = MWL[shareMerkleRoot]
137         share['MRD'] = MRD
138         
139         shareTimestamp = unpack('<L', data[68:72])[0]
140         shareTime = share['time'] = time()
141         if shareTimestamp < shareTime - 300:
142                 raise RejectedShare('time-too-old')
143         if shareTimestamp > shareTime + 7200:
144                 raise RejectedShare('time-too-new')
145         if data[72:76] != bits:
146                 raise RejectedShare('bad-diffbits')
147         if data[:4] != b'\1\0\0\0':
148                 raise RejectedShare('bad-version')
149         
150         blkhash = dblsha(data)
151         if blkhash[28:] != b'\0\0\0\0':
152                 raise RejectedShare('H-not-zero')
153         blkhashn = hash2int(blkhash)
154         
155         global networkTarget
156         logfunc = getattr(checkShare.logger, 'info' if blkhashn <= networkTarget else 'debug')
157         logfunc('BLKHASH: %64x' % (blkhashn,))
158         logfunc(' TARGET: %64x' % (networkTarget,))
159         
160         if blkhashn <= networkTarget:
161                 logfunc("Submitting upstream")
162                 txlist = MRD[1].data
163                 t = txlist[0]
164                 t.setCoinbase(MRD[2])
165                 t.assemble()
166                 UpstreamBitcoind.submitBlock(data, txlist)
167                 share['upstreamResult'] = True
168         
169         logShare(share)
170 checkShare.logger = logging.getLogger('checkShare')
171
172 def receiveShare(share):
173         # TODO: username => userid
174         checkShare(share)
175         # TODO
176
177 def newBlockNotification():
178         MM.updateMerkleTree()
179         # TODO: Force RESPOND TO LONGPOLLS?
180         pass
181
182 def newBlockNotificationSIGNAL(signum, frame):
183         # Use a new thread, in case the signal handler is called with locks held
184         thr = threading.Thread(target=newBlockNotification, name='newBlockNotification via signal %s' % (signum,))
185         thr.daemon = True
186         thr.start()
187
188 from signal import signal, SIGUSR1
189 signal(SIGUSR1, newBlockNotificationSIGNAL)
190
191
192 from jsonrpcserver import JSONRPCServer
193 import interactivemode
194
195 if __name__ == "__main__":
196         server = JSONRPCServer(('', 8444))
197         server.getBlockHeader = getBlockHeader
198         server.receiveShare = receiveShare
199         server.RaiseRedFlags = RaiseRedFlags
200         server.serve_forever()