SHOW:
|
|
- or go back to the newest paste.
1 | #!/usr/bin/python | |
2 | # | |
3 | # Copyright (c) 2011 The Bitcoin developers | |
4 | # Distributed under the MIT/X11 software license, see the accompanying | |
5 | # file COPYING or http://www.opensource.org/licenses/mit-license.php. | |
6 | # | |
7 | ||
8 | import time | |
9 | import json | |
10 | import pprint | |
11 | import hashlib | |
12 | import struct | |
13 | import re | |
14 | import base64 | |
15 | import httplib | |
16 | import sys | |
17 | from multiprocessing import Process | |
18 | ||
19 | ERR_SLEEP = 15 | |
20 | MAX_NONCE = 1000000L | |
21 | ||
22 | settings = {} | |
23 | pp = pprint.PrettyPrinter(indent=4) | |
24 | ||
25 | class BitcoinRPC: | |
26 | OBJID = 1 | |
27 | ||
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): | |
33 | self.OBJID += 1 | |
34 | obj = { 'version' : '1.1', | |
35 | 'method' : method, | |
36 | 'id' : self.OBJID } | |
37 | if params is None: | |
38 | obj['params'] = [] | |
39 | else: | |
40 | obj['params'] = params | |
41 | self.conn.request('POST', '/', json.dumps(obj), | |
42 | { 'Authorization' : self.authhdr, | |
43 | 'Content-type' : 'application/json' }) | |
44 | ||
45 | resp = self.conn.getresponse() | |
46 | if resp is None: | |
47 | print "JSON-RPC: no response" | |
48 | return None | |
49 | ||
50 | body = resp.read() | |
51 | resp_obj = json.loads(body) | |
52 | if resp_obj is None: | |
53 | print "JSON-RPC: cannot JSON-decode body" | |
54 | return None | |
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" | |
59 | return None | |
60 | ||
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) | |
66 | ||
67 | def uint32(x): | |
68 | return x & 0xffffffffL | |
69 | ||
70 | def bytereverse(x): | |
71 | return uint32(( ((x) << 24) | (((x) << 8) & 0x00ff0000) | | |
72 | (((x) >> 8) & 0x0000ff00) | ((x) >> 24) )) | |
73 | ||
74 | def bufreverse(in_buf): | |
75 | out_words = [] | |
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) | |
80 | ||
81 | def wordreverse(in_buf): | |
82 | out_words = [] | |
83 | for i in range(0, len(in_buf), 4): | |
84 | out_words.append(in_buf[i:i+4]) | |
85 | out_words.reverse() | |
86 | return ''.join(out_words) | |
87 | ||
88 | class Miner: | |
89 | def __init__(self, id): | |
90 | self.id = id | |
91 | self.max_nonce = MAX_NONCE | |
92 | ||
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) | |
97 | ||
98 | # the first 76b of 80b do not change | |
99 | blk_hdr = static_data[:76] | |
100 | ||
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) | |
106 | ||
107 | # pre-hash first 76b of block header | |
108 | static_hash = hashlib.sha256() | |
109 | static_hash.update(blk_hdr) | |
110 | ||
111 | for nonce in xrange(self.max_nonce): | |
112 | ||
113 | # encode 32-bit nonce value | |
114 | nonce_bin = struct.pack("<I", nonce) | |
115 | ||
116 | # hash final 4b, the nonce value | |
117 | hash1_o = static_hash.copy() | |
118 | hash1_o.update(nonce_bin) | |
119 | hash1 = hash1_o.digest() | |
120 | ||
121 | # sha256 hash of sha256 hash | |
122 | hash_o = hashlib.sha256() | |
123 | hash_o.update(hash1) | |
124 | hash = hash_o.digest() | |
125 | ||
126 | # quick test for winning solution: high 32 bits zero? | |
127 | if hash[-4:] != '\0\0\0\0': | |
128 | continue | |
129 | ||
130 | # convert binary hash to 256-bit Python long | |
131 | hash = bufreverse(hash) | |
132 | hash = wordreverse(hash) | |
133 | ||
134 | hash_str = hash.encode('hex') | |
135 | l = long(hash_str, 16) | |
136 | ||
137 | # proof-of-work test: hash < target | |
138 | if l < target: | |
139 | print time.asctime(), "PROOF-OF-WORK found: %064x" % (l,) | |
140 | return (nonce + 1, nonce_bin) | |
141 | else: | |
142 | print time.asctime(), "PROOF-OF-WORK false positive %064x" % (l,) | |
143 | # return (nonce + 1, nonce_bin) | |
144 | ||
145 | return (nonce + 1, None) | |
146 | ||
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 | |
154 | ||
155 | def iterate(self, rpc): | |
156 | work = rpc.getwork() | |
157 | if work is None: | |
158 | time.sleep(ERR_SLEEP) | |
159 | return | |
160 | if 'data' not in work or 'target' not in work: | |
161 | time.sleep(ERR_SLEEP) | |
162 | return | |
163 | ||
164 | time_start = time.time() | |
165 | ||
166 | (hashes_done, nonce_bin) = self.work(work['data'], | |
167 | work['target']) | |
168 | ||
169 | time_end = time.time() | |
170 | time_diff = time_end - time_start | |
171 | ||
172 | self.max_nonce = long( | |
173 | (hashes_done * settings['scantime']) / time_diff) | |
174 | if self.max_nonce > 0xfffffffaL: | |
175 | self.max_nonce = 0xfffffffaL | |
176 | ||
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) | |
181 | ||
182 | if nonce_bin is not None: | |
183 | self.submit_work(rpc, work['data'], nonce_bin) | |
184 | ||
185 | def loop(self): | |
186 | rpc = BitcoinRPC(settings['host'], settings['port'], | |
187 | settings['rpcuser'], settings['rpcpass']) | |
188 | if rpc is None: | |
189 | return | |
190 | ||
191 | while True: | |
192 | self.iterate(rpc) | |
193 | ||
194 | def miner_thread(id): | |
195 | miner = Miner(id) | |
196 | miner.loop() | |
197 | ||
198 | if __name__ == '__main__': | |
199 | if len(sys.argv) != 2: | |
200 | print "Usage: pyminer.py CONFIG-FILE" | |
201 | sys.exit(1) | |
202 | ||
203 | f = open(sys.argv[1]) | |
204 | for line in f: | |
205 | # skip comment lines | |
206 | m = re.search('^\s*#', line) | |
207 | if m: | |
208 | continue | |
209 | ||
210 | # parse key=value lines | |
211 | m = re.search('^(\w+)\s*=\s*(\S.*)$', line) | |
212 | if m is None: | |
213 | continue | |
214 | settings[m.group(1)] = m.group(2) | |
215 | f.close() | |
216 | ||
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" | |
229 | sys.exit(1) | |
230 | ||
231 | settings['port'] = int(settings['port']) | |
232 | settings['threads'] = int(settings['threads']) | |
233 | settings['hashmeter'] = int(settings['hashmeter']) | |
234 | settings['scantime'] = long(settings['scantime']) | |
235 | ||
236 | thr_list = [] | |
237 | for thr_id in range(settings['threads']): | |
238 | p = Process(target=miner_thread, args=(thr_id,)) | |
239 | p.start() | |
240 | thr_list.append(p) | |
241 | time.sleep(1) # stagger threads | |
242 | ||
243 | print settings['threads'], "mining threads started" | |
244 | ||
245 | print time.asctime(), "Miner Starts - %s:%s" % (settings['host'], settings['port']) | |
246 | try: | |
247 | for thr_proc in thr_list: | |
248 | thr_proc.join() | |
249 | except KeyboardInterrupt: | |
250 | pass | |
251 | print time.asctime(), "Miner Stops - %s:%s" % (settings['host'], settings['port']) |