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:
parent
93659379bd
commit
9192ca9e33
@ -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 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):
|
|
||||||
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()
|
def rpc(self, method, params=None):
|
||||||
if resp is None:
|
self.object_id += 1
|
||||||
print "JSON-RPC: no response"
|
obj = {'version' : '1.1',
|
||||||
return None
|
'method' : method,
|
||||||
|
'id' : self.object_id,
|
||||||
|
'params' : params or []}
|
||||||
|
|
||||||
body = resp.read()
|
self.conn.request('POST', '/', json.dumps(obj),
|
||||||
resp_obj = json.loads(body)
|
{ 'Authorization' : self.authhdr,
|
||||||
if resp_obj is None:
|
'Content-type' : 'application/json' })
|
||||||
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']
|
resp = self.conn.getresponse()
|
||||||
def getblockcount(self):
|
|
||||||
return self.rpc('getblockcount')
|
if resp is None:
|
||||||
def getwork(self, data=None):
|
print("JSON-RPC: no response")
|
||||||
return self.rpc('getwork', data)
|
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):
|
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):
|
def submit_work(self, rpc, original_data, nonce_bin):
|
||||||
nonce_bin = bufreverse(nonce_bin)
|
nonce_bin = bufreverse(nonce_bin)
|
||||||
nonce = nonce_bin.encode('hex')
|
nonce = nonce_bin.encode('hex')
|
||||||
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
|
|
||||||
|
|
||||||
def iterate(self, rpc):
|
print(time.asctime(), "--> Upstream RPC result:", result)
|
||||||
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()
|
def iterate(self, rpc):
|
||||||
|
work = rpc.getwork()
|
||||||
|
|
||||||
(hashes_done, nonce_bin) = self.work(work['data'],
|
if work is None:
|
||||||
work['target'])
|
time.sleep(ERR_SLEEP)
|
||||||
|
return
|
||||||
|
|
||||||
time_end = time.time()
|
if 'data' not in work or 'target' not in work:
|
||||||
time_diff = time_end - time_start
|
time.sleep(ERR_SLEEP)
|
||||||
|
return
|
||||||
|
|
||||||
self.max_nonce = long(
|
time_start = time.time()
|
||||||
(hashes_done * settings['scantime']) / time_diff)
|
|
||||||
if self.max_nonce > 0xfffffffaL:
|
|
||||||
self.max_nonce = 0xfffffffaL
|
|
||||||
|
|
||||||
if settings['hashmeter']:
|
(hashes_done, nonce_bin) = self.work(work['data'],
|
||||||
print "HashMeter(%d): %d hashes, %.2f Khash/sec" % (
|
work['target'])
|
||||||
self.id, hashes_done,
|
|
||||||
(hashes_done / 1000.0) / time_diff)
|
|
||||||
|
|
||||||
if nonce_bin is not None:
|
time_end = time.time()
|
||||||
self.submit_work(rpc, work['data'], nonce_bin)
|
time_diff = time_end - time_start
|
||||||
|
|
||||||
def loop(self):
|
self.max_nonce = long(
|
||||||
rpc = BitcoinRPC(settings['host'], settings['port'],
|
(hashes_done * settings['scantime']) / time_diff)
|
||||||
settings['rpcuser'], settings['rpcpass'])
|
|
||||||
if rpc is None:
|
if self.max_nonce > 0xfffffffaL:
|
||||||
return
|
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()
|
||||||
|
|
||||||
while True:
|
|
||||||
self.iterate(rpc)
|
|
||||||
|
|
||||||
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
|
|
||||||
m = re.search('^\s*#', line)
|
|
||||||
if m:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# parse key=value lines
|
for line in f:
|
||||||
m = re.search('^(\w+)\s*=\s*(\S.*)$', line)
|
# skip comment lines
|
||||||
if m is None:
|
m = re.search('^\s*#', line)
|
||||||
continue
|
if m:
|
||||||
settings[m.group(1)] = m.group(2)
|
continue
|
||||||
f.close()
|
|
||||||
|
|
||||||
if 'host' not in settings:
|
# parse key=value lines
|
||||||
settings['host'] = '127.0.0.1'
|
m = re.search('^(\w+)\s*=\s*(\S.*)$', line)
|
||||||
if 'port' not in settings:
|
if m is None:
|
||||||
settings['port'] = 8332
|
continue
|
||||||
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[m.group(1)] = m.group(2)
|
||||||
settings['threads'] = int(settings['threads'])
|
|
||||||
settings['hashmeter'] = int(settings['hashmeter'])
|
|
||||||
settings['scantime'] = long(settings['scantime'])
|
|
||||||
|
|
||||||
thr_list = []
|
settings.setdefault('host', '127.0.0.1')
|
||||||
for thr_id in range(settings['threads']):
|
settings.setdefault('port', 8332)
|
||||||
p = Process(target=miner_thread, args=(thr_id,))
|
settings.setdefault('threads', 1)
|
||||||
p.start()
|
settings.setdefault('hashmeter', 0)
|
||||||
thr_list.append(p)
|
settings.setdefault('scantime', 30L)
|
||||||
time.sleep(1) # stagger threads
|
|
||||||
|
|
||||||
print settings['threads'], "mining threads started"
|
if 'rpcuser' not in settings or 'rpcpass' not in settings:
|
||||||
|
print("Missing username and/or password in cfg file")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
print time.asctime(), "Miner Starts - %s:%s" % (settings['host'], settings['port'])
|
settings['port'] = int(settings['port'])
|
||||||
try:
|
settings['threads'] = int(settings['threads'])
|
||||||
for thr_proc in thr_list:
|
settings['hashmeter'] = int(settings['hashmeter'])
|
||||||
thr_proc.join()
|
settings['scantime'] = long(settings['scantime'])
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
print time.asctime(), "Miner Stops - %s:%s" % (settings['host'], settings['port'])
|
|
||||||
|
|
||||||
|
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']))
|
||||||
|
Loading…
Reference in New Issue
Block a user