Browse Source

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.
0.10
Clinton Christian 11 years ago committed by Wladimir J. van der Laan
parent
commit
9192ca9e33
  1. 457
      contrib/pyminer/pyminer.py

457
contrib/pyminer/pyminer.py

@ -5,248 +5,265 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
# #
import sys
from multiprocessing import Process
import time import time
import json
import pprint
import hashlib
import struct import struct
import re import hashlib
import base64 import base64
import re
import httplib import httplib
import sys import json
from multiprocessing import Process
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):
self.OBJID += 1 def rpc(self, method, params=None):
obj = { 'version' : '1.1', self.object_id += 1
'method' : method, obj = {'version' : '1.1',
'id' : self.OBJID } 'method' : method,
if params is None: 'id' : self.object_id,
obj['params'] = [] 'params' : params or []}
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:
print "JSON-RPC: cannot JSON-decode body" if resp_obj is None:
return None print("JSON-RPC: cannot JSON-decode body")
if 'error' in resp_obj and resp_obj['error'] != None: return None
return resp_obj['error']
if 'result' not in resp_obj: if 'error' in resp_obj and resp_obj['error'] != None:
print "JSON-RPC: no result in object" return resp_obj['error']
return None
if 'result' not in resp_obj:
return resp_obj['result'] print("JSON-RPC: no result in object")
def getblockcount(self): return None
return self.rpc('getblockcount')
def getwork(self, data=None): return resp_obj['result']
return self.rpc('getwork', data)
def getblockcount(self):
return self.rpc('getblockcount')
def getwork(self, data=None):
return self.rpc('getwork', data)
def uint32(x): def uint32(x):
return x & 0xffffffffL return x & 0xffffffffL
def bytereverse(x): def bytereverse(x):
return uint32(( ((x) << 24) | (((x) << 8) & 0x00ff0000) | return uint32(( ((x) << 24) | (((x) << 8) & 0x00ff0000) |
(((x) >> 8) & 0x0000ff00) | ((x) >> 24) )) (((x) >> 8) & 0x0000ff00) | ((x) >> 24) ))
def bufreverse(in_buf): def bufreverse(in_buf):
out_words = [] out_words = []
for i in range(0, len(in_buf), 4):
word = struct.unpack('@I', in_buf[i:i+4])[0] for i in range(0, len(in_buf), 4):
out_words.append(struct.pack('@I', bytereverse(word))) word = struct.unpack('@I', in_buf[i:i+4])[0]
return ''.join(out_words) out_words.append(struct.pack('@I', bytereverse(word)))
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):
out_words.append(in_buf[i:i+4]) for i in range(0, len(in_buf), 4):
out_words.reverse() out_words.append(in_buf[i:i+4])
return ''.join(out_words)
out_words.reverse()
return ''.join(out_words)
class Miner: class Miner:
def __init__(self, id): def __init__(self, id):
self.id = id self.id = id
self.max_nonce = MAX_NONCE self.max_nonce = MAX_NONCE
def work(self, datastr, targetstr): def work(self, datastr, targetstr):
# decode work data hex string to binary # decode work data hex string to binary
static_data = datastr.decode('hex') static_data = datastr.decode('hex')
static_data = bufreverse(static_data) static_data = bufreverse(static_data)
# the first 76b of 80b do not change # the first 76b of 80b do not change
blk_hdr = static_data[:76] blk_hdr = static_data[:76]
# decode 256-bit target value # decode 256-bit target value
targetbin = targetstr.decode('hex') targetbin = targetstr.decode('hex')
targetbin = targetbin[::-1] # byte-swap and dword-swap targetbin = targetbin[::-1] # byte-swap and dword-swap
targetbin_str = targetbin.encode('hex') targetbin_str = targetbin.encode('hex')
target = long(targetbin_str, 16) target = long(targetbin_str, 16)
# pre-hash first 76b of block header # pre-hash first 76b of block header
static_hash = hashlib.sha256() static_hash = hashlib.sha256()
static_hash.update(blk_hdr) static_hash.update(blk_hdr)
for nonce in xrange(self.max_nonce): for nonce in xrange(self.max_nonce):
# encode 32-bit nonce value # encode 32-bit nonce value
nonce_bin = struct.pack("<I", nonce) nonce_bin = struct.pack("<I", nonce)
# hash final 4b, the nonce value # hash final 4b, the nonce value
hash1_o = static_hash.copy() hash1_o = static_hash.copy()
hash1_o.update(nonce_bin) hash1_o.update(nonce_bin)
hash1 = hash1_o.digest() hash1 = hash1_o.digest()
# sha256 hash of sha256 hash # sha256 hash of sha256 hash
hash_o = hashlib.sha256() hash_o = hashlib.sha256()
hash_o.update(hash1) hash_o.update(hash1)
hash = hash_o.digest() hash = hash_o.digest()
# quick test for winning solution: high 32 bits zero? # quick test for winning solution: high 32 bits zero?
if hash[-4:] != '\0\0\0\0': if hash[-4:] != '\0\0\0\0':
continue continue
# convert binary hash to 256-bit Python long # convert binary hash to 256-bit Python long
hash = bufreverse(hash) hash = bufreverse(hash)
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: "
return (nonce + 1, nonce_bin) "{0:064x}".format(long_hash))
else: return (nonce + 1, nonce_bin)
print time.asctime(), "PROOF-OF-WORK false positive %064x" % (l,) else:
# return (nonce + 1, nonce_bin) print(time.asctime(), "PROOF-OF-WORK false"
"positive {0:064x}".format(long_hash))
return (nonce + 1, None)
return (nonce + 1, None)
def submit_work(self, rpc, original_data, nonce_bin):
nonce_bin = bufreverse(nonce_bin) def submit_work(self, rpc, original_data, nonce_bin):
nonce = nonce_bin.encode('hex') nonce_bin = bufreverse(nonce_bin)
solution = original_data[:152] + nonce + original_data[160:256] nonce = nonce_bin.encode('hex')
param_arr = [ solution ] solution = original_data[:152] + nonce + original_data[160:256]
result = rpc.getwork(param_arr) param_arr = [ solution ]
print time.asctime(), "--> Upstream RPC result:", result result = rpc.getwork(param_arr)
def iterate(self, rpc): print(time.asctime(), "--> Upstream RPC result:", result)
work = rpc.getwork()
if work is None: def iterate(self, rpc):
time.sleep(ERR_SLEEP) work = rpc.getwork()
return
if 'data' not in work or 'target' not in work: if work is None:
time.sleep(ERR_SLEEP) time.sleep(ERR_SLEEP)
return return
time_start = time.time() if 'data' not in work or 'target' not in work:
time.sleep(ERR_SLEEP)
(hashes_done, nonce_bin) = self.work(work['data'], return
work['target'])
time_start = time.time()
time_end = time.time()
time_diff = time_end - time_start (hashes_done, nonce_bin) = self.work(work['data'],
work['target'])
self.max_nonce = long(
(hashes_done * settings['scantime']) / time_diff) time_end = time.time()
if self.max_nonce > 0xfffffffaL: time_diff = time_end - time_start
self.max_nonce = 0xfffffffaL
self.max_nonce = long(
if settings['hashmeter']: (hashes_done * settings['scantime']) / time_diff)
print "HashMeter(%d): %d hashes, %.2f Khash/sec" % (
self.id, hashes_done, if self.max_nonce > 0xfffffffaL:
(hashes_done / 1000.0) / time_diff) self.max_nonce = 0xfffffffaL
if nonce_bin is not None: if settings['hashmeter']:
self.submit_work(rpc, work['data'], nonce_bin) print("HashMeter({:d}): {:d} hashes, {:.2f} Khash/sec".format(
self.id, hashes_done, (hashes_done / 1000.0) / time_diff))
def loop(self):
rpc = BitcoinRPC(settings['host'], settings['port'], if nonce_bin is not None:
settings['rpcuser'], settings['rpcpass']) self.submit_work(rpc, work['data'], nonce_bin)
if rpc is None:
return def loop(self):
rpc = BitcoinRPC(settings['host'], settings['port'],
while True: settings['rpcuser'], settings['rpcpass'])
self.iterate(rpc)
if rpc is not None:
while True:
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:
# skip comment lines for line in f:
m = re.search('^\s*#', line) # skip comment lines
if m: m = re.search('^\s*#', line)
continue if m:
continue
# parse key=value lines
m = re.search('^(\w+)\s*=\s*(\S.*)$', line) # parse key=value lines
if m is None: m = re.search('^(\w+)\s*=\s*(\S.*)$', line)
continue if m is None:
settings[m.group(1)] = m.group(2) continue
f.close()
settings[m.group(1)] = m.group(2)
if 'host' not in settings:
settings['host'] = '127.0.0.1' settings.setdefault('host', '127.0.0.1')
if 'port' not in settings: settings.setdefault('port', 8332)
settings['port'] = 8332 settings.setdefault('threads', 1)
if 'threads' not in settings: settings.setdefault('hashmeter', 0)
settings['threads'] = 1 settings.setdefault('scantime', 30L)
if 'hashmeter' not in settings:
settings['hashmeter'] = 0 if 'rpcuser' not in settings or 'rpcpass' not in settings:
if 'scantime' not in settings: print("Missing username and/or password in cfg file")
settings['scantime'] = 30L sys.exit(1)
if 'rpcuser' not in settings or 'rpcpass' not in settings:
print "Missing username and/or password in cfg file" settings['port'] = int(settings['port'])
sys.exit(1) settings['threads'] = int(settings['threads'])
settings['hashmeter'] = int(settings['hashmeter'])
settings['port'] = int(settings['port']) settings['scantime'] = long(settings['scantime'])
settings['threads'] = int(settings['threads'])
settings['hashmeter'] = int(settings['hashmeter']) thread_list = []
settings['scantime'] = long(settings['scantime'])
for thread_id in range(settings['threads']):
thr_list = [] p = Process(target=miner_thread, args=(thread_id,))
for thr_id in range(settings['threads']): p.start()
p = Process(target=miner_thread, args=(thr_id,)) thread_list.append(p)
p.start() time.sleep(1) # stagger threads
thr_list.append(p)
time.sleep(1) # stagger threads print(settings['threads'], "mining threads started")
print settings['threads'], "mining threads started" print(time.asctime(), "Miner Starts - {0}:{1}".format(settings['host'],
settings['port']))
print time.asctime(), "Miner Starts - %s:%s" % (settings['host'], settings['port']) try:
try: for thread_process in thread_list:
for thr_proc in thr_list: thread_process.join()
thr_proc.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']))

Loading…
Cancel
Save