diff options
author | Clinton Christian <Clinton.Christian@me.com> | 2014-07-07 23:22:23 -0400 |
---|---|---|
committer | Wladimir J. van der Laan <laanwj@gmail.com> | 2014-07-25 10:58:00 +0200 |
commit | 9192ca9e3341ed82d6324ee03c8a21504fd0163e (patch) | |
tree | 971db9b63e68833a854cd99d5c592be806d58c1f /contrib/pyminer | |
parent | 93659379bdedc29a87fe351ba2950a2c976aebd9 (diff) |
pyminer: Fix memory leak, refactor to be more pythonic, maintainable, and possibly faster
Pull #4483.
Note that pyminer is not currently functional due to the removal of
getwork in cf0c47b.
Diffstat (limited to 'contrib/pyminer')
-rwxr-xr-x | contrib/pyminer/pyminer.py | 457 |
1 files changed, 237 insertions, 220 deletions
diff --git a/contrib/pyminer/pyminer.py b/contrib/pyminer/pyminer.py index 0a2932d66e..706a10b39d 100755 --- a/contrib/pyminer/pyminer.py +++ b/contrib/pyminer/pyminer.py @@ -5,248 +5,265 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. # +import sys +from multiprocessing import Process import time -import json -import pprint -import hashlib import struct -import re +import hashlib import base64 +import re import httplib -import sys -from multiprocessing import Process +import json 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) + object_id = 1 + + def __init__(self, host, port, username, password): + authpair = "{0}:{1}".format(username, password) + self.authhdr = "Basic {0}".format(base64.b64encode(authpair)) + self.conn = httplib.HTTPConnection(host, port, strict=False, timeout=30) + + def rpc(self, method, params=None): + self.object_id += 1 + obj = {'version' : '1.1', + 'method' : method, + 'id' : self.object_id, + 'params' : params or []} + + 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 + return x & 0xffffffffL def bytereverse(x): - return uint32(( ((x) << 24) | (((x) << 8) & 0x00ff0000) | - (((x) >> 8) & 0x0000ff00) | ((x) >> 24) )) + 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) + 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) + 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 __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') + long_hash = long(hash_str, 16) + + # proof-of-work test: hash < target + if long_hash < target: + print(time.asctime(), "PROOF-OF-WORK found: " + "{0:064x}".format(long_hash)) + return (nonce + 1, nonce_bin) + else: + print(time.asctime(), "PROOF-OF-WORK false" + "positive {0:064x}".format(long_hash)) + + 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".format( + 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 not None: + + while True: + self.iterate(rpc) + + self.conn.close() + def miner_thread(id): - miner = Miner(id) - miner.loop() + 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']) - + if len(sys.argv) != 2: + print("Usage: pyminer.py CONFIG-FILE") + sys.exit(1) + + with open(sys.argv[1]) as f: + + 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) + + settings.setdefault('host', '127.0.0.1') + settings.setdefault('port', 8332) + settings.setdefault('threads', 1) + settings.setdefault('hashmeter', 0) + settings.setdefault('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']) + + thread_list = [] + + for thread_id in range(settings['threads']): + p = Process(target=miner_thread, args=(thread_id,)) + p.start() + thread_list.append(p) + time.sleep(1) # stagger threads + + print(settings['threads'], "mining threads started") + + print(time.asctime(), "Miner Starts - {0}:{1}".format(settings['host'], + settings['port'])) + try: + for thread_process in thread_list: + thread_process.join() + except KeyboardInterrupt: + pass + + print(time.asctime(), "Miner Stops - {0}:{1}".format(settings['host'], + settings['port'])) |