7 logging.basicConfig(level=logging.DEBUG)
8 for infoOnly in ('checkShare', 'JSONRPCHandler', 'merkleMaker'):
9 logging.getLogger(infoOnly).setLevel(logging.INFO)
11 def RaiseRedFlags(reason):
12 logging.getLogger('redflag').critical(reason)
16 from bitcoin.node import BitcoinLink
17 UpstreamBitcoind = BitcoinLink( config.UpstreamBitcoindNode, config.UpstreamNetworkId )
20 from bitcoin.script import BitcoinScript
21 from bitcoin.txn import Txn
22 from base58 import b58decode
23 from struct import pack
27 def makeCoinbaseTxn(coinbaseValue, useCoinbaser = True):
30 if useCoinbaser and hasattr(config, 'CoinbaserCmd'):
33 cmd = config.CoinbaserCmd
34 cmd = cmd.replace('%d', str(coinbaseValue))
35 p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
36 nout = int(p.stdout.readline())
38 amount = int(p.stdout.readline())
39 addr = p.stdout.readline().rstrip(b'\n').decode('utf8')
40 pkScript = BitcoinScript.toAddress(addr)
41 t.addOutput(amount, pkScript)
44 coinbased = coinbaseValue + 1
45 if coinbased >= coinbaseValue:
46 logging.getLogger('makeCoinbaseTxn').error('Coinbaser failed!')
49 coinbaseValue -= coinbased
51 pkScript = BitcoinScript.toAddress(config.TrackerAddr)
52 t.addOutput(coinbaseValue, pkScript)
55 # TODO: red flag on dupe coinbase
59 from util import Bits2Target
73 global MM, networkTarget, server
74 networkTarget = Bits2Target(MM.currentBlock[1])
79 from merklemaker import merkleMaker
81 MM.__dict__.update(config.__dict__)
82 MM.clearCoinbaseTxn = makeCoinbaseTxn(5000000000, False) # FIXME
83 MM.clearCoinbaseTxn.assemble()
84 MM.makeCoinbaseTxn = makeCoinbaseTxn
85 MM.onBlockChange = blockChanged
86 MM.onBlockUpdate = updateBlocks
87 MM._THISISUGLY = UpstreamBitcoind
91 from binascii import b2a_hex
92 from struct import pack, unpack
94 from util import RejectedShare, dblsha, hash2int
99 if hasattr(config, 'GotWorkURI'):
100 gotwork = jsonrpc.ServiceProxy(config.GotWorkURI)
102 def submitGotwork(info):
104 gotwork.gotwork(info)
106 checkShare.logger.warning('Failed to submit gotwork')
109 if hasattr(config, 'DbOptions'):
111 db = psycopg2.connect(**config.DbOptions)
113 def getBlockHeader(username):
115 (merkleRoot, merkleTree, coinbase, prevBlock, bits, rollPrevBlk) = MRD
116 timestamp = pack('<L', int(time()))
117 hdr = b'\1\0\0\0' + prevBlock + merkleRoot + timestamp + bits + b'iolE'
118 workLog.setdefault(username, {})[merkleRoot] = MRD
124 return 'Y' if b else 'N'
130 rem_host = share.get('remoteHost', '?')
131 username = share['username']
132 reason = share.get('rejectReason', None)
133 upstreamResult = share.get('upstreamResult', None)
134 solution = share['_origdata']
135 #solution = b2a_hex(solution).decode('utf8')
136 stmt = "insert into shares (rem_host, username, our_result, upstream_result, reason, solution) values (%s, %s, %s, %s, %s, decode(%s, 'hex'))"
137 params = (rem_host, username, YN(not reason), YN(upstreamResult), reason, solution)
138 dbc.execute(stmt, params)
141 def checkShare(share):
144 (prevBlock, bits) = MM.currentBlock
145 sharePrevBlock = data[4:36]
146 if sharePrevBlock != prevBlock:
147 if sharePrevBlock == MM.lastBlock[0]:
148 raise RejectedShare('stale-prevblk')
149 raise RejectedShare('bad-prevblk')
151 shareMerkleRoot = data[36:68]
153 username = share['username']
154 if username not in workLog:
155 raise RejectedShare('unknown-user')
157 if data[72:76] != bits:
158 raise RejectedShare('bad-diffbits')
159 if data[:4] != b'\1\0\0\0':
160 raise RejectedShare('bad-version')
162 MWL = workLog[username]
163 if shareMerkleRoot not in MWL:
164 raise RejectedShare('unknown-work')
165 MRD = MWL[shareMerkleRoot]
168 if data in DupeShareHACK:
169 raise RejectedShare('duplicate')
170 DupeShareHACK[data] = None
172 shareTimestamp = unpack('<L', data[68:72])[0]
173 shareTime = share['time'] = time()
174 if shareTimestamp < shareTime - 300:
175 raise RejectedShare('time-too-old')
176 if shareTimestamp > shareTime + 7200:
177 raise RejectedShare('time-too-new')
179 blkhash = dblsha(data)
180 if blkhash[28:] != b'\0\0\0\0':
181 raise RejectedShare('H-not-zero')
182 blkhashn = hash2int(blkhash)
185 logfunc = getattr(checkShare.logger, 'info' if blkhashn <= networkTarget else 'debug')
186 logfunc('BLKHASH: %64x' % (blkhashn,))
187 logfunc(' TARGET: %64x' % (networkTarget,))
191 t.setCoinbase(MRD[2])
194 if blkhashn <= networkTarget:
195 logfunc("Submitting upstream")
196 UpstreamBitcoind.submitBlock(data, txlist)
197 share['upstreamResult'] = True
198 MM.updateBlock(blkhash)
201 if gotwork and blkhashn <= config.GotWorkTarget:
203 coinbaseMrkl = t.data
204 coinbaseMrkl += blkhash
205 steps = MRD[1]._steps
206 coinbaseMrkl += pack('B', len(steps) + 1)
207 coinbaseMrkl += t.txid
210 coinbaseMrkl += b"\0\0\0\0"
212 info['hash'] = b2a_hex(blkhash).decode('ascii')
213 info['header'] = b2a_hex(data).decode('ascii')
214 info['coinbaseMrkl'] = b2a_hex(coinbaseMrkl).decode('ascii')
215 thr = threading.Thread(target=submitGotwork, args=(info,))
219 checkShare.logger.warning('Failed to build gotwork request')
222 checkShare.logger = logging.getLogger('checkShare')
224 def receiveShare(share):
225 # TODO: username => userid
228 except RejectedShare as rej:
229 share['rejectReason'] = str(rej)
234 def newBlockNotification(signum, frame):
235 MM.updateMerkleTree()
236 # TODO: Force RESPOND TO LONGPOLLS?
239 from signal import signal, SIGUSR1
240 signal(SIGUSR1, newBlockNotification)
243 from jsonrpcserver import JSONRPCServer
244 import interactivemode
246 if __name__ == "__main__":
247 server = JSONRPCServer(config.JSONRPCAddress)
248 if hasattr(config, 'SecretUser'):
249 server.SecretUser = config.SecretUser
250 server.aux = MM.CoinbaseAux
251 server.getBlockHeader = getBlockHeader
252 server.receiveShare = receiveShare
253 server.RaiseRedFlags = RaiseRedFlags
254 server.serve_forever()