bsunau7
Member
Offline
Activity: 114
Merit: 10
|
|
August 27, 2015, 11:11:13 AM |
|
Hi, Attached is a functional single threaded python miner based on the bitcoin miner. I single threaded it and replaced the inner guts with primecoin's PoW. It was done so I could get my head around the PoW as the white paper wasn't helping much. It's mined some coins on testnet, but given it's performance I wouldn't even think of running in on prodnet! I've left the config file up to the reader. Also it doesn't use the fermat remainder as a test; it just throws the block up and lets the wallet validate (so you'll get a lot of failures with a testnet difficulty of 4.98). #!/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 pdb import time import json import pprint import hashlib import struct import re import base64 import httplib import sys import gmpy2 from gmpy2 import mpz
ERR_SLEEP = 15 MAX_NONCE = 0x000effff
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)
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 ch1_test(self,origin,l): t1 = gmpy2.sub(origin,1)
while l: if (gmpy2.is_prime(t1)): t2 = gmpy2.add(t1,t1) t1 = gmpy2.add(t2,1) else: break l = l - 1
# l = 0 found a complete chain, l > 0 short chain if(l): return 0 else: return 1
def ch2_test(self,origin,l): t1 = gmpy2.add(origin,1)
while l: if (gmpy2.is_prime(t1)): t2 = gmpy2.add(t1,t1) t1 = gmpy2.sub(t2,1) else: break l = l - 1
# l = 0 found a complete chain, l > 0 short chain if(l): return 0 else: return 1
def work(self, datastr, targetstr): # decode work data hex string to binary static_data = datastr.decode('hex')
# before we reverse we need to extract the target # target is encoded in the datastr 72-75 (nBits) target = struct.unpack(">I",static_data[72:76])[0] print "target: ", target/16777216.0 tar_len = target/16777216
# now flip data static_data = bufreverse(static_data)
# the first 76b of 80b do not change blk_hdr = static_data[:76]
# 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()
# convert binary hash to 256-bit Python long hash = bufreverse(hash) hash = wordreverse(hash)
hash_str = hash.encode('hex') l = long(hash_str, 16) mpz_l = mpz(l)
# high bit set? if l < 1<<255: continue
# Origin cannot be a prime, perform a quick odd/even test if l & 0x01: continue
# Chain length 4 needs mod 3 and mod 5 need to be zero. So mod 15 == 0 if((l % 15)): continue
# do fermat (and trial division) test on chain, must not be prime! if (gmpy2.is_prime(mpz_l)): continue
#pdb.set_trace()
# Multiply by a number, pick one... #origin = gmpy2.mul(mpz_l,510510) #origin = gmpy2.mul(mpz_l,2310) origin = gmpy2.mul(mpz_l,1)
# chain length 4. mod 7 ;0 = bi; 1,2,4 = neg, 3,5,6 = pos m7 = l % 7
if(m7 == 0): # test both chains if (self.ch1_test(origin,tar_len) or self.ch2_test(origin,tar_len)): print "origin passed +-@%d" % (nonce,)
# just submit print time.asctime(), "PROOF-OF-WORK found: %064x" % (l,) return (nonce + 1, nonce_bin) elif((m7 == 1) or (m7 == 2) or (m7 == 4)): # negative if (self.ch2_test(origin,tar_len)): print "origin passed -@%d" % (nonce,)
# just submit print time.asctime(), "PROOF-OF-WORK found: %064x" % (l,) return (nonce + 1, nonce_bin) else: # positive if (self.ch1_test(origin,tar_len)): print "origin passed +@%d" % (nonce,)
# just submit print time.asctime(), "PROOF-OF-WORK found: %064x" % (l,) return (nonce + 1, nonce_bin)
# loop for a new nonce pass
return (nonce + 1, None)
def submit_work(self, rpc, original_data, nonce_bin): nonce_bin = bufreverse(nonce_bin) nonce = nonce_bin.encode('hex') # 510510 #solution = original_data[:152] + nonce + "07CA2E03" + original_data[168:256] # 2310 #solution = original_data[:152] + nonce + "00090602" + original_data[168:256] # 1 solution = original_data[:152] + nonce + "00000101" + original_data[168: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 > 0xffff0000L: self.max_nonce = 0xffff0000L
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 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)
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'] = 9914 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'])
print time.asctime(), "Miner Starts - %s:%s" % (settings['host'], settings['port'])
# Single thread as the python debugger is even yuckier if you use them miner_thread(1)
print time.asctime(), "Miner Stops - %s:%s" % (settings['host'], settings['port'])
Regards, -- bsunau7
|