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.
This commit is contained in:
Clinton Christian 2014-07-07 23:22:23 -04:00 committed by Wladimir J. van der Laan
parent 93659379bd
commit 9192ca9e33
1 changed files with 204 additions and 187 deletions

View File

@ -5,62 +5,66 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
# #
import time
import json
import pprint
import hashlib
import struct
import re
import base64
import httplib
import sys import sys
from multiprocessing import Process from multiprocessing import Process
import time
import struct
import hashlib
import base64
import re
import httplib
import json
ERR_SLEEP = 15 ERR_SLEEP = 15
MAX_NONCE = 1000000L MAX_NONCE = 1000000L
settings = {} settings = {}
pp = pprint.PrettyPrinter(indent=4)
class BitcoinRPC: class BitcoinRPC:
OBJID = 1 object_id = 1
def __init__(self, host, port, username, password): def __init__(self, host, port, username, password):
authpair = "%s:%s" % (username, password) authpair = "{0}:{1}".format(username, password)
self.authhdr = "Basic %s" % (base64.b64encode(authpair)) self.authhdr = "Basic {0}".format(base64.b64encode(authpair))
self.conn = httplib.HTTPConnection(host, port, False, 30) self.conn = httplib.HTTPConnection(host, port, strict=False, timeout=30)
def rpc(self, method, params=None): def rpc(self, method, params=None):
self.OBJID += 1 self.object_id += 1
obj = { 'version' : '1.1', obj = {'version' : '1.1',
'method' : method, 'method' : method,
'id' : self.OBJID } 'id' : self.object_id,
if params is None: 'params' : params or []}
obj['params'] = []
else:
obj['params'] = params
self.conn.request('POST', '/', json.dumps(obj), self.conn.request('POST', '/', json.dumps(obj),
{ 'Authorization' : self.authhdr, { 'Authorization' : self.authhdr,
'Content-type' : 'application/json' }) 'Content-type' : 'application/json' })
resp = self.conn.getresponse() resp = self.conn.getresponse()
if resp is None: if resp is None:
print "JSON-RPC: no response" print("JSON-RPC: no response")
return None return None
body = resp.read() body = resp.read()
resp_obj = json.loads(body) resp_obj = json.loads(body)
if resp_obj is None: if resp_obj is None:
print "JSON-RPC: cannot JSON-decode body" print("JSON-RPC: cannot JSON-decode body")
return None return None
if 'error' in resp_obj and resp_obj['error'] != None: if 'error' in resp_obj and resp_obj['error'] != None:
return resp_obj['error'] return resp_obj['error']
if 'result' not in resp_obj: if 'result' not in resp_obj:
print "JSON-RPC: no result in object" print("JSON-RPC: no result in object")
return None return None
return resp_obj['result'] return resp_obj['result']
def getblockcount(self): def getblockcount(self):
return self.rpc('getblockcount') return self.rpc('getblockcount')
def getwork(self, data=None): def getwork(self, data=None):
return self.rpc('getwork', data) return self.rpc('getwork', data)
@ -73,18 +77,24 @@ def bytereverse(x):
def bufreverse(in_buf): def bufreverse(in_buf):
out_words = [] out_words = []
for i in range(0, len(in_buf), 4): for i in range(0, len(in_buf), 4):
word = struct.unpack('@I', in_buf[i:i+4])[0] word = struct.unpack('@I', in_buf[i:i+4])[0]
out_words.append(struct.pack('@I', bytereverse(word))) out_words.append(struct.pack('@I', bytereverse(word)))
return ''.join(out_words) return ''.join(out_words)
def wordreverse(in_buf): def wordreverse(in_buf):
out_words = [] out_words = []
for i in range(0, len(in_buf), 4): for i in range(0, len(in_buf), 4):
out_words.append(in_buf[i:i+4]) out_words.append(in_buf[i:i+4])
out_words.reverse() out_words.reverse()
return ''.join(out_words) return ''.join(out_words)
class Miner: class Miner:
def __init__(self, id): def __init__(self, id):
self.id = id self.id = id
@ -132,15 +142,16 @@ class Miner:
hash = wordreverse(hash) hash = wordreverse(hash)
hash_str = hash.encode('hex') hash_str = hash.encode('hex')
l = long(hash_str, 16) long_hash = long(hash_str, 16)
# proof-of-work test: hash < target # proof-of-work test: hash < target
if l < target: if long_hash < target:
print time.asctime(), "PROOF-OF-WORK found: %064x" % (l,) print(time.asctime(), "PROOF-OF-WORK found: "
"{0:064x}".format(long_hash))
return (nonce + 1, nonce_bin) return (nonce + 1, nonce_bin)
else: else:
print time.asctime(), "PROOF-OF-WORK false positive %064x" % (l,) print(time.asctime(), "PROOF-OF-WORK false"
# return (nonce + 1, nonce_bin) "positive {0:064x}".format(long_hash))
return (nonce + 1, None) return (nonce + 1, None)
@ -150,13 +161,16 @@ class Miner:
solution = original_data[:152] + nonce + original_data[160:256] solution = original_data[:152] + nonce + original_data[160:256]
param_arr = [ solution ] param_arr = [ solution ]
result = rpc.getwork(param_arr) result = rpc.getwork(param_arr)
print time.asctime(), "--> Upstream RPC result:", result
print(time.asctime(), "--> Upstream RPC result:", result)
def iterate(self, rpc): def iterate(self, rpc):
work = rpc.getwork() work = rpc.getwork()
if work is None: if work is None:
time.sleep(ERR_SLEEP) time.sleep(ERR_SLEEP)
return return
if 'data' not in work or 'target' not in work: if 'data' not in work or 'target' not in work:
time.sleep(ERR_SLEEP) time.sleep(ERR_SLEEP)
return return
@ -171,13 +185,13 @@ class Miner:
self.max_nonce = long( self.max_nonce = long(
(hashes_done * settings['scantime']) / time_diff) (hashes_done * settings['scantime']) / time_diff)
if self.max_nonce > 0xfffffffaL: if self.max_nonce > 0xfffffffaL:
self.max_nonce = 0xfffffffaL self.max_nonce = 0xfffffffaL
if settings['hashmeter']: if settings['hashmeter']:
print "HashMeter(%d): %d hashes, %.2f Khash/sec" % ( print("HashMeter({:d}): {:d} hashes, {:.2f} Khash/sec".format(
self.id, hashes_done, self.id, hashes_done, (hashes_done / 1000.0) / time_diff))
(hashes_done / 1000.0) / time_diff)
if nonce_bin is not None: if nonce_bin is not None:
self.submit_work(rpc, work['data'], nonce_bin) self.submit_work(rpc, work['data'], nonce_bin)
@ -185,22 +199,26 @@ class Miner:
def loop(self): def loop(self):
rpc = BitcoinRPC(settings['host'], settings['port'], rpc = BitcoinRPC(settings['host'], settings['port'],
settings['rpcuser'], settings['rpcpass']) settings['rpcuser'], settings['rpcpass'])
if rpc is None:
return if rpc is not None:
while True: while True:
self.iterate(rpc) self.iterate(rpc)
self.conn.close()
def miner_thread(id): def miner_thread(id):
miner = Miner(id) miner = Miner(id)
miner.loop() miner.loop()
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) != 2: if len(sys.argv) != 2:
print "Usage: pyminer.py CONFIG-FILE" print("Usage: pyminer.py CONFIG-FILE")
sys.exit(1) sys.exit(1)
f = open(sys.argv[1]) with open(sys.argv[1]) as f:
for line in f: for line in f:
# skip comment lines # skip comment lines
m = re.search('^\s*#', line) m = re.search('^\s*#', line)
@ -211,21 +229,17 @@ if __name__ == '__main__':
m = re.search('^(\w+)\s*=\s*(\S.*)$', line) m = re.search('^(\w+)\s*=\s*(\S.*)$', line)
if m is None: if m is None:
continue continue
settings[m.group(1)] = m.group(2)
f.close()
if 'host' not in settings: settings[m.group(1)] = m.group(2)
settings['host'] = '127.0.0.1'
if 'port' not in settings: settings.setdefault('host', '127.0.0.1')
settings['port'] = 8332 settings.setdefault('port', 8332)
if 'threads' not in settings: settings.setdefault('threads', 1)
settings['threads'] = 1 settings.setdefault('hashmeter', 0)
if 'hashmeter' not in settings: settings.setdefault('scantime', 30L)
settings['hashmeter'] = 0
if 'scantime' not in settings:
settings['scantime'] = 30L
if 'rpcuser' not in settings or 'rpcpass' not in settings: if 'rpcuser' not in settings or 'rpcpass' not in settings:
print "Missing username and/or password in cfg file" print("Missing username and/or password in cfg file")
sys.exit(1) sys.exit(1)
settings['port'] = int(settings['port']) settings['port'] = int(settings['port'])
@ -233,20 +247,23 @@ if __name__ == '__main__':
settings['hashmeter'] = int(settings['hashmeter']) settings['hashmeter'] = int(settings['hashmeter'])
settings['scantime'] = long(settings['scantime']) settings['scantime'] = long(settings['scantime'])
thr_list = [] thread_list = []
for thr_id in range(settings['threads']):
p = Process(target=miner_thread, args=(thr_id,)) for thread_id in range(settings['threads']):
p = Process(target=miner_thread, args=(thread_id,))
p.start() p.start()
thr_list.append(p) thread_list.append(p)
time.sleep(1) # stagger threads time.sleep(1) # stagger threads
print settings['threads'], "mining threads started" print(settings['threads'], "mining threads started")
print time.asctime(), "Miner Starts - %s:%s" % (settings['host'], settings['port']) print(time.asctime(), "Miner Starts - {0}:{1}".format(settings['host'],
settings['port']))
try: try:
for thr_proc in thr_list: for thread_process in thread_list:
thr_proc.join() thread_process.join()
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
print time.asctime(), "Miner Stops - %s:%s" % (settings['host'], settings['port'])
print(time.asctime(), "Miner Stops - {0}:{1}".format(settings['host'],
settings['port']))