Spaces:
Runtime error
Runtime error
| #!/usr/bin/python | |
| # | |
| # Copyright 2011 Jeff Garzik | |
| # | |
| # This program is free software; you can redistribute it and/or modify | |
| # it under the terms of the GNU General Public License as published by | |
| # the Free Software Foundation. | |
| # | |
| # This program is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| # GNU General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License | |
| # along with this program; see the file COPYING. If not, write to | |
| # the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| # | |
| import time | |
| import json | |
| import pprint | |
| import hashlib | |
| import struct | |
| import re | |
| import base64 | |
| import httplib | |
| import sys | |
| from multiprocessing import Process | |
| ERR_SLEEP = 15 | |
| MAX_NONCE = 1000000L | |
| settings = {} | |
| pp = pprint.PrettyPrinter(indent=4) | |
| class BitcoinRPC: | |
| OBJID = 1 | |
| def __init__(self, host, port, username, password): | |
| authpair = "%s:%s" % (username, password) | |
| self.authhdr = "Basic %s" % (base64.b64encode(authpair)) | |
| self.conn = httplib.HTTPConnection(host, port, False, 30) | |
| def rpc(self, method, params=None): | |
| self.OBJID += 1 | |
| obj = { 'version' : '1.1', | |
| 'method' : method, | |
| 'id' : self.OBJID } | |
| if params is None: | |
| obj['params'] = [] | |
| else: | |
| obj['params'] = params | |
| self.conn.request('POST', '/', json.dumps(obj), | |
| { 'Authorization' : self.authhdr, | |
| 'Content-type' : 'application/json' }) | |
| resp = self.conn.getresponse() | |
| if resp is None: | |
| print "JSON-RPC: no response" | |
| return None | |
| body = resp.read() | |
| resp_obj = json.loads(body) | |
| if resp_obj is None: | |
| print "JSON-RPC: cannot JSON-decode body" | |
| return None | |
| if 'error' in resp_obj and resp_obj['error'] != None: | |
| return resp_obj['error'] | |
| if 'result' not in resp_obj: | |
| print "JSON-RPC: no result in object" | |
| return None | |
| return resp_obj['result'] | |
| def getblockcount(self): | |
| return self.rpc('getblockcount') | |
| def getwork(self, data=None): | |
| return self.rpc('getwork', data) | |
| def uint32(x): | |
| return x & 0xffffffffL | |
| def bytereverse(x): | |
| return uint32(( ((x) << 24) | (((x) << 8) & 0x00ff0000) | | |
| (((x) >> 8) & 0x0000ff00) | ((x) >> 24) )) | |
| def bufreverse(in_buf): | |
| out_words = [] | |
| for i in range(0, len(in_buf), 4): | |
| word = struct.unpack('@I', in_buf[i:i+4])[0] | |
| out_words.append(struct.pack('@I', bytereverse(word))) | |
| return ''.join(out_words) | |
| def wordreverse(in_buf): | |
| out_words = [] | |
| for i in range(0, len(in_buf), 4): | |
| out_words.append(in_buf[i:i+4]) | |
| out_words.reverse() | |
| return ''.join(out_words) | |
| class Miner: | |
| def __init__(self, id): | |
| self.id = id | |
| self.max_nonce = MAX_NONCE | |
| def work(self, datastr, targetstr): | |
| # decode work data hex string to binary | |
| static_data = datastr.decode('hex') | |
| static_data = bufreverse(static_data) | |
| # the first 76b of 80b do not change | |
| blk_hdr = static_data[:76] | |
| # decode 256-bit target value | |
| targetbin = targetstr.decode('hex') | |
| targetbin = targetbin[::-1] # byte-swap and dword-swap | |
| targetbin_str = targetbin.encode('hex') | |
| target = long(targetbin_str, 16) | |
| # pre-hash first 76b of block header | |
| static_hash = hashlib.sha256() | |
| static_hash.update(blk_hdr) | |
| for nonce in xrange(self.max_nonce): | |
| # encode 32-bit nonce value | |
| nonce_bin = struct.pack("<I", nonce) | |
| # hash final 4b, the nonce value | |
| hash1_o = static_hash.copy() | |
| hash1_o.update(nonce_bin) | |
| hash1 = hash1_o.digest() | |
| # sha256 hash of sha256 hash | |
| hash_o = hashlib.sha256() | |
| hash_o.update(hash1) | |
| hash = hash_o.digest() | |
| # quick test for winning solution: high 32 bits zero? | |
| if hash[-4:] != '\0\0\0\0': | |
| continue | |
| # convert binary hash to 256-bit Python long | |
| hash = bufreverse(hash) | |
| hash = wordreverse(hash) | |
| hash_str = hash.encode('hex') | |
| l = long(hash_str, 16) | |
| # proof-of-work test: hash < target | |
| if l < target: | |
| print time.asctime(), "PROOF-OF-WORK found: %064x" % (l,) | |
| return (nonce + 1, nonce_bin) | |
| else: | |
| print time.asctime(), "PROOF-OF-WORK false positive %064x" % (l,) | |
| # return (nonce + 1, nonce_bin) | |
| return (nonce + 1, None) | |
| def submit_work(self, rpc, original_data, nonce_bin): | |
| nonce_bin = bufreverse(nonce_bin) | |
| nonce = nonce_bin.encode('hex') | |
| solution = original_data[:152] + nonce + original_data[160:256] | |
| param_arr = [ solution ] | |
| result = rpc.getwork(param_arr) | |
| print time.asctime(), "--> Upstream RPC result:", result | |
| def iterate(self, rpc): | |
| work = rpc.getwork() | |
| if work is None: | |
| time.sleep(ERR_SLEEP) | |
| return | |
| if 'data' not in work or 'target' not in work: | |
| time.sleep(ERR_SLEEP) | |
| return | |
| time_start = time.time() | |
| (hashes_done, nonce_bin) = self.work(work['data'], | |
| work['target']) | |
| time_end = time.time() | |
| time_diff = time_end - time_start | |
| self.max_nonce = long( | |
| (hashes_done * settings['scantime']) / time_diff) | |
| if self.max_nonce > 0xfffffffaL: | |
| self.max_nonce = 0xfffffffaL | |
| if settings['hashmeter']: | |
| print "HashMeter(%d): %d hashes, %.2f Khash/sec" % ( | |
| self.id, hashes_done, | |
| (hashes_done / 1000.0) / time_diff) | |
| if nonce_bin is not None: | |
| self.submit_work(rpc, work['data'], nonce_bin) | |
| def loop(self): | |
| rpc = BitcoinRPC(settings['host'], settings['port'], | |
| settings['rpcuser'], settings['rpcpass']) | |
| if rpc is None: | |
| return | |
| while True: | |
| self.iterate(rpc) | |
| def miner_thread(id): | |
| miner = Miner(id) | |
| miner.loop() | |
| if __name__ == '__main__': | |
| if len(sys.argv) != 2: | |
| print "Usage: pyminer.py CONFIG-FILE" | |
| sys.exit(1) | |
| f = open(sys.argv[1]) | |
| for line in f: | |
| # skip comment lines | |
| m = re.search('^\s*#', line) | |
| if m: | |
| continue | |
| # parse key=value lines | |
| m = re.search('^(\w+)\s*=\s*(\S.*)$', line) | |
| if m is None: | |
| continue | |
| settings[m.group(1)] = m.group(2) | |
| f.close() | |
| if 'host' not in settings: | |
| settings['host'] = '127.0.0.1' | |
| if 'port' not in settings: | |
| settings['port'] = 8332 | |
| if 'threads' not in settings: | |
| settings['threads'] = 1 | |
| if 'hashmeter' not in settings: | |
| settings['hashmeter'] = 0 | |
| if 'scantime' not in settings: | |
| settings['scantime'] = 30L | |
| if 'rpcuser' not in settings or 'rpcpass' not in settings: | |
| print "Missing username and/or password in cfg file" | |
| sys.exit(1) | |
| settings['port'] = int(settings['port']) | |
| settings['threads'] = int(settings['threads']) | |
| settings['hashmeter'] = int(settings['hashmeter']) | |
| settings['scantime'] = long(settings['scantime']) | |
| thr_list = [] | |
| for thr_id in range(settings['threads']): | |
| p = Process(target=miner_thread, args=(thr_id,)) | |
| p.start() | |
| thr_list.append(p) | |
| time.sleep(1) # stagger threads | |
| print settings['threads'], "mining threads started" | |
| print time.asctime(), "Miner Starts - %s:%s" % (settings['host'], settings['port']) | |
| try: | |
| for thr_proc in thr_list: | |
| thr_proc.join() | |
| except KeyboardInterrupt: | |
| pass | |
| print time.asctime(), "Miner Stops - %s:%s" % (settings['host'], settings['port']) | |