![jgarzik@bitpay.com](/assets/img/avatar_default.png)
4 changed files with 0 additions and 313 deletions
@ -1,8 +0,0 @@ |
|||
### PyMiner ### |
|||
|
|||
This is a 'getwork' CPU mining client for Bitcoin. It is pure-python, and therefore very, very slow. The purpose is to provide a reference implementation of a miner, for study. |
|||
|
|||
### Other Resources ### |
|||
|
|||
- [BitcoinTalk Thread](https://bitcointalk.org/index.php?topic=3546.0) |
|||
- [Jgarzik Repo](https://github.com/jgarzik/pyminer) |
@ -1,32 +0,0 @@ |
|||
|
|||
# |
|||
# RPC login details |
|||
# |
|||
host=127.0.0.1 |
|||
port=8332 |
|||
|
|||
rpcuser=myusername |
|||
rpcpass=mypass |
|||
|
|||
|
|||
# |
|||
# mining details |
|||
# |
|||
|
|||
threads=4 |
|||
|
|||
# periodic rate for requesting new work, if solution not found |
|||
scantime=60 |
|||
|
|||
|
|||
# |
|||
# misc. |
|||
# |
|||
|
|||
# not really used right now |
|||
logdir=/tmp/pyminer |
|||
|
|||
# set to 1, to enable hashmeter output |
|||
hashmeter=0 |
|||
|
|||
|
@ -1,269 +0,0 @@ |
|||
#!/usr/bin/python |
|||
# |
|||
# Copyright (c) 2011 The Bitcoin developers |
|||
# Distributed under the MIT/X11 software license, see the accompanying |
|||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. |
|||
# |
|||
|
|||
import sys |
|||
from multiprocessing import Process |
|||
import time |
|||
import struct |
|||
import hashlib |
|||
import base64 |
|||
import re |
|||
import httplib |
|||
import json |
|||
|
|||
ERR_SLEEP = 15 |
|||
MAX_NONCE = 1000000L |
|||
|
|||
settings = {} |
|||
|
|||
|
|||
class BitcoinRPC: |
|||
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 |
|||
|
|||
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') |
|||
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() |
|||
|
|||
if __name__ == '__main__': |
|||
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'])) |
Loading…
Reference in new issue