Post
Topic
Board Announcements (Altcoins)
Re: [XPM] [ANN] Primecoin Release - First Scientific Computing Cryptocurrency
by
bsunau7
on 27/08/2015, 11:11:13 UTC
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).

Code:
#!/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("
                        # 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