Update to 0.3.0 (New upstream + new RPC calls)
[novacoin.git] / contrib / pyminer / pyminer.py
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2011 The Bitcoin developers
4 # Distributed under the MIT/X11 software license, see the accompanying
5 # file license.txt or http://www.opensource.org/licenses/mit-license.php.
6 #
7
8 import time
9 import json
10 import pprint
11 import hashlib
12 import struct
13 import re
14 import base64
15 import httplib
16 import sys
17 from multiprocessing import Process
18
19 ERR_SLEEP = 15
20 MAX_NONCE = 1000000L
21
22 settings = {}
23 pp = pprint.PrettyPrinter(indent=4)
24
25 class BitcoinRPC:
26         OBJID = 1
27
28         def __init__(self, host, port, username, password):
29                 authpair = "%s:%s" % (username, password)
30                 self.authhdr = "Basic %s" % (base64.b64encode(authpair))
31                 self.conn = httplib.HTTPConnection(host, port, False, 30)
32         def rpc(self, method, params=None):
33                 self.OBJID += 1
34                 obj = { 'version' : '1.1',
35                         'method' : method,
36                         'id' : self.OBJID }
37                 if params is None:
38                         obj['params'] = []
39                 else:
40                         obj['params'] = params
41                 self.conn.request('POST', '/', json.dumps(obj),
42                         { 'Authorization' : self.authhdr,
43                           'Content-type' : 'application/json' })
44
45                 resp = self.conn.getresponse()
46                 if resp is None:
47                         print "JSON-RPC: no response"
48                         return None
49
50                 body = resp.read()
51                 resp_obj = json.loads(body)
52                 if resp_obj is None:
53                         print "JSON-RPC: cannot JSON-decode body"
54                         return None
55                 if 'error' in resp_obj and resp_obj['error'] != None:
56                         return resp_obj['error']
57                 if 'result' not in resp_obj:
58                         print "JSON-RPC: no result in object"
59                         return None
60
61                 return resp_obj['result']
62         def getblockcount(self):
63                 return self.rpc('getblockcount')
64         def getwork(self, data=None):
65                 return self.rpc('getwork', data)
66
67 def uint32(x):
68         return x & 0xffffffffL
69
70 def bytereverse(x):
71         return uint32(( ((x) << 24) | (((x) << 8) & 0x00ff0000) |
72                         (((x) >> 8) & 0x0000ff00) | ((x) >> 24) ))
73
74 def bufreverse(in_buf):
75         out_words = []
76         for i in range(0, len(in_buf), 4):
77                 word = struct.unpack('@I', in_buf[i:i+4])[0]
78                 out_words.append(struct.pack('@I', bytereverse(word)))
79         return ''.join(out_words)
80
81 def wordreverse(in_buf):
82         out_words = []
83         for i in range(0, len(in_buf), 4):
84                 out_words.append(in_buf[i:i+4])
85         out_words.reverse()
86         return ''.join(out_words)
87
88 class Miner:
89         def __init__(self, id):
90                 self.id = id
91                 self.max_nonce = MAX_NONCE
92
93         def work(self, datastr, targetstr):
94                 # decode work data hex string to binary
95                 static_data = datastr.decode('hex')
96                 static_data = bufreverse(static_data)
97
98                 # the first 76b of 80b do not change
99                 blk_hdr = static_data[:76]
100
101                 # decode 256-bit target value
102                 targetbin = targetstr.decode('hex')
103                 targetbin = targetbin[::-1]     # byte-swap and dword-swap
104                 targetbin_str = targetbin.encode('hex')
105                 target = long(targetbin_str, 16)
106
107                 # pre-hash first 76b of block header
108                 static_hash = hashlib.sha256()
109                 static_hash.update(blk_hdr)
110
111                 for nonce in xrange(self.max_nonce):
112
113                         # encode 32-bit nonce value
114                         nonce_bin = struct.pack("<I", nonce)
115
116                         # hash final 4b, the nonce value
117                         hash1_o = static_hash.copy()
118                         hash1_o.update(nonce_bin)
119                         hash1 = hash1_o.digest()
120
121                         # sha256 hash of sha256 hash
122                         hash_o = hashlib.sha256()
123                         hash_o.update(hash1)
124                         hash = hash_o.digest()
125
126                         # quick test for winning solution: high 32 bits zero?
127                         if hash[-4:] != '\0\0\0\0':
128                                 continue
129
130                         # convert binary hash to 256-bit Python long
131                         hash = bufreverse(hash)
132                         hash = wordreverse(hash)
133
134                         hash_str = hash.encode('hex')
135                         l = long(hash_str, 16)
136
137                         # proof-of-work test:  hash < target
138                         if l < target:
139                                 print time.asctime(), "PROOF-OF-WORK found: %064x" % (l,)
140                                 return (nonce + 1, nonce_bin)
141                         else:
142                                 print time.asctime(), "PROOF-OF-WORK false positive %064x" % (l,)
143 #                               return (nonce + 1, nonce_bin)
144
145                 return (nonce + 1, None)
146
147         def submit_work(self, rpc, original_data, nonce_bin):
148                 nonce_bin = bufreverse(nonce_bin)
149                 nonce = nonce_bin.encode('hex')
150                 solution = original_data[:152] + nonce + original_data[160:256]
151                 param_arr = [ solution ]
152                 result = rpc.getwork(param_arr)
153                 print time.asctime(), "--> Upstream RPC result:", result
154
155         def iterate(self, rpc):
156                 work = rpc.getwork()
157                 if work is None:
158                         time.sleep(ERR_SLEEP)
159                         return
160                 if 'data' not in work or 'target' not in work:
161                         time.sleep(ERR_SLEEP)
162                         return
163
164                 time_start = time.time()
165
166                 (hashes_done, nonce_bin) = self.work(work['data'],
167                                                      work['target'])
168
169                 time_end = time.time()
170                 time_diff = time_end - time_start
171
172                 self.max_nonce = long(
173                         (hashes_done * settings['scantime']) / time_diff)
174                 if self.max_nonce > 0xfffffffaL:
175                         self.max_nonce = 0xfffffffaL
176
177                 if settings['hashmeter']:
178                         print "HashMeter(%d): %d hashes, %.2f Khash/sec" % (
179                               self.id, hashes_done,
180                               (hashes_done / 1000.0) / time_diff)
181
182                 if nonce_bin is not None:
183                         self.submit_work(rpc, work['data'], nonce_bin)
184
185         def loop(self):
186                 rpc = BitcoinRPC(settings['host'], settings['port'],
187                                  settings['rpcuser'], settings['rpcpass'])
188                 if rpc is None:
189                         return
190
191                 while True:
192                         self.iterate(rpc)
193
194 def miner_thread(id):
195         miner = Miner(id)
196         miner.loop()
197
198 if __name__ == '__main__':
199         if len(sys.argv) != 2:
200                 print "Usage: pyminer.py CONFIG-FILE"
201                 sys.exit(1)
202
203         f = open(sys.argv[1])
204         for line in f:
205                 # skip comment lines
206                 m = re.search('^\s*#', line)
207                 if m:
208                         continue
209
210                 # parse key=value lines
211                 m = re.search('^(\w+)\s*=\s*(\S.*)$', line)
212                 if m is None:
213                         continue
214                 settings[m.group(1)] = m.group(2)
215         f.close()
216
217         if 'host' not in settings:
218                 settings['host'] = '127.0.0.1'
219         if 'port' not in settings:
220                 settings['port'] = 8332
221         if 'threads' not in settings:
222                 settings['threads'] = 1
223         if 'hashmeter' not in settings:
224                 settings['hashmeter'] = 0
225         if 'scantime' not in settings:
226                 settings['scantime'] = 30L
227         if 'rpcuser' not in settings or 'rpcpass' not in settings:
228                 print "Missing username and/or password in cfg file"
229                 sys.exit(1)
230
231         settings['port'] = int(settings['port'])
232         settings['threads'] = int(settings['threads'])
233         settings['hashmeter'] = int(settings['hashmeter'])
234         settings['scantime'] = long(settings['scantime'])
235
236         thr_list = []
237         for thr_id in range(settings['threads']):
238                 p = Process(target=miner_thread, args=(thr_id,))
239                 p.start()
240                 thr_list.append(p)
241                 time.sleep(1)                   # stagger threads
242
243         print settings['threads'], "mining threads started"
244
245         print time.asctime(), "Miner Starts - %s:%s" % (settings['host'], settings['port'])
246         try:
247                 for thr_proc in thr_list:
248                         thr_proc.join()
249         except KeyboardInterrupt:
250                 pass
251         print time.asctime(), "Miner Stops - %s:%s" % (settings['host'], settings['port'])
252