3 # Copyright (c) 2011 The Bitcoin developers
4 # Distributed under the MIT/X11 software license, see the accompanying
5 # file license.txt or http://www.opensource.org/licenses/mit-license.php.
17 from multiprocessing import Process
23 pp = pprint.PrettyPrinter(indent=4)
28 def __init__(self, host, port, username, password):
29 authpair = "%s:%s" % (username, password)
30 self.authhdr = "Basic %s" % (base64.b64encode(authpair))
31 self.conn = httplib.HTTPConnection(host, port, False, 30)
32 def rpc(self, method, params=None):
34 obj = { 'version' : '1.1',
40 obj['params'] = params
41 self.conn.request('POST', '/', json.dumps(obj),
42 { 'Authorization' : self.authhdr,
43 'Content-type' : 'application/json' })
45 resp = self.conn.getresponse()
47 print "JSON-RPC: no response"
51 resp_obj = json.loads(body)
53 print "JSON-RPC: cannot JSON-decode body"
55 if 'error' in resp_obj and resp_obj['error'] != None:
56 return resp_obj['error']
57 if 'result' not in resp_obj:
58 print "JSON-RPC: no result in object"
61 return resp_obj['result']
62 def getblockcount(self):
63 return self.rpc('getblockcount')
64 def getwork(self, data=None):
65 return self.rpc('getwork', data)
68 return x & 0xffffffffL
71 return uint32(( ((x) << 24) | (((x) << 8) & 0x00ff0000) |
72 (((x) >> 8) & 0x0000ff00) | ((x) >> 24) ))
74 def bufreverse(in_buf):
76 for i in range(0, len(in_buf), 4):
77 word = struct.unpack('@I', in_buf[i:i+4])[0]
78 out_words.append(struct.pack('@I', bytereverse(word)))
79 return ''.join(out_words)
81 def wordreverse(in_buf):
83 for i in range(0, len(in_buf), 4):
84 out_words.append(in_buf[i:i+4])
86 return ''.join(out_words)
89 def __init__(self, id):
91 self.max_nonce = MAX_NONCE
93 def work(self, datastr, targetstr):
94 # decode work data hex string to binary
95 static_data = datastr.decode('hex')
96 static_data = bufreverse(static_data)
98 # the first 76b of 80b do not change
99 blk_hdr = static_data[:76]
101 # decode 256-bit target value
102 targetbin = targetstr.decode('hex')
103 targetbin = targetbin[::-1] # byte-swap and dword-swap
104 targetbin_str = targetbin.encode('hex')
105 target = long(targetbin_str, 16)
107 # pre-hash first 76b of block header
108 static_hash = hashlib.sha256()
109 static_hash.update(blk_hdr)
111 for nonce in xrange(self.max_nonce):
113 # encode 32-bit nonce value
114 nonce_bin = struct.pack("<I", nonce)
116 # hash final 4b, the nonce value
117 hash1_o = static_hash.copy()
118 hash1_o.update(nonce_bin)
119 hash1 = hash1_o.digest()
121 # sha256 hash of sha256 hash
122 hash_o = hashlib.sha256()
124 hash = hash_o.digest()
126 # quick test for winning solution: high 32 bits zero?
127 if hash[-4:] != '\0\0\0\0':
130 # convert binary hash to 256-bit Python long
131 hash = bufreverse(hash)
132 hash = wordreverse(hash)
134 hash_str = hash.encode('hex')
135 l = long(hash_str, 16)
137 # proof-of-work test: hash < target
139 print time.asctime(), "PROOF-OF-WORK found: %064x" % (l,)
140 return (nonce + 1, nonce_bin)
142 print time.asctime(), "PROOF-OF-WORK false positive %064x" % (l,)
143 # return (nonce + 1, nonce_bin)
145 return (nonce + 1, None)
147 def submit_work(self, rpc, original_data, nonce_bin):
148 nonce_bin = bufreverse(nonce_bin)
149 nonce = nonce_bin.encode('hex')
150 solution = original_data[:152] + nonce + original_data[160:256]
151 param_arr = [ solution ]
152 result = rpc.getwork(param_arr)
153 print time.asctime(), "--> Upstream RPC result:", result
155 def iterate(self, rpc):
158 time.sleep(ERR_SLEEP)
160 if 'data' not in work or 'target' not in work:
161 time.sleep(ERR_SLEEP)
164 time_start = time.time()
166 (hashes_done, nonce_bin) = self.work(work['data'],
169 time_end = time.time()
170 time_diff = time_end - time_start
172 self.max_nonce = long(
173 (hashes_done * settings['scantime']) / time_diff)
174 if self.max_nonce > 0xfffffffaL:
175 self.max_nonce = 0xfffffffaL
177 if settings['hashmeter']:
178 print "HashMeter(%d): %d hashes, %.2f Khash/sec" % (
179 self.id, hashes_done,
180 (hashes_done / 1000.0) / time_diff)
182 if nonce_bin is not None:
183 self.submit_work(rpc, work['data'], nonce_bin)
186 rpc = BitcoinRPC(settings['host'], settings['port'],
187 settings['rpcuser'], settings['rpcpass'])
194 def miner_thread(id):
198 if __name__ == '__main__':
199 if len(sys.argv) != 2:
200 print "Usage: pyminer.py CONFIG-FILE"
203 f = open(sys.argv[1])
206 m = re.search('^\s*#', line)
210 # parse key=value lines
211 m = re.search('^(\w+)\s*=\s*(\S.*)$', line)
214 settings[m.group(1)] = m.group(2)
217 if 'host' not in settings:
218 settings['host'] = '127.0.0.1'
219 if 'port' not in settings:
220 settings['port'] = 8332
221 if 'threads' not in settings:
222 settings['threads'] = 1
223 if 'hashmeter' not in settings:
224 settings['hashmeter'] = 0
225 if 'scantime' not in settings:
226 settings['scantime'] = 30L
227 if 'rpcuser' not in settings or 'rpcpass' not in settings:
228 print "Missing username and/or password in cfg file"
231 settings['port'] = int(settings['port'])
232 settings['threads'] = int(settings['threads'])
233 settings['hashmeter'] = int(settings['hashmeter'])
234 settings['scantime'] = long(settings['scantime'])
237 for thr_id in range(settings['threads']):
238 p = Process(target=miner_thread, args=(thr_id,))
241 time.sleep(1) # stagger threads
243 print settings['threads'], "mining threads started"
245 print time.asctime(), "Miner Starts - %s:%s" % (settings['host'], settings['port'])
247 for thr_proc in thr_list:
249 except KeyboardInterrupt:
251 print time.asctime(), "Miner Stops - %s:%s" % (settings['host'], settings['port'])