Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
@@ -1,262 +1,87 @@
|
|
1 |
-
|
2 |
-
#
|
3 |
-
#
|
4 |
-
#
|
5 |
-
# This program is free software; you can redistribute it and/or modify
|
6 |
-
# it under the terms of the GNU General Public License as published by
|
7 |
-
# the Free Software Foundation.
|
8 |
-
#
|
9 |
-
# This program is distributed in the hope that it will be useful,
|
10 |
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11 |
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12 |
-
# GNU General Public License for more details.
|
13 |
-
#
|
14 |
-
# You should have received a copy of the GNU General Public License
|
15 |
-
# along with this program; see the file COPYING. If not, write to
|
16 |
-
# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
17 |
-
#
|
18 |
|
19 |
-
import
|
20 |
import json
|
21 |
-
import pprint
|
22 |
import hashlib
|
23 |
-
import
|
24 |
-
import
|
25 |
-
import
|
26 |
-
import
|
27 |
-
import sys
|
28 |
-
from multiprocessing import Process
|
29 |
-
|
30 |
-
ERR_SLEEP = 15
|
31 |
-
MAX_NONCE = 1000000L
|
32 |
-
|
33 |
-
settings = {}
|
34 |
-
pp = pprint.PrettyPrinter(indent=4)
|
35 |
-
|
36 |
-
class BitcoinRPC:
|
37 |
-
OBJID = 1
|
38 |
-
|
39 |
-
def __init__(self, host, port, username, password):
|
40 |
-
authpair = "%s:%s" % (username, password)
|
41 |
-
self.authhdr = "Basic %s" % (base64.b64encode(authpair))
|
42 |
-
self.conn = httplib.HTTPConnection(host, port, False, 30)
|
43 |
-
def rpc(self, method, params=None):
|
44 |
-
self.OBJID += 1
|
45 |
-
obj = { 'version' : '1.1',
|
46 |
-
'method' : method,
|
47 |
-
'id' : self.OBJID }
|
48 |
-
if params is None:
|
49 |
-
obj['params'] = []
|
50 |
-
else:
|
51 |
-
obj['params'] = params
|
52 |
-
self.conn.request('POST', '/', json.dumps(obj),
|
53 |
-
{ 'Authorization' : self.authhdr,
|
54 |
-
'Content-type' : 'application/json' })
|
55 |
-
|
56 |
-
resp = self.conn.getresponse()
|
57 |
-
if resp is None:
|
58 |
-
print "JSON-RPC: no response"
|
59 |
-
return None
|
60 |
-
|
61 |
-
body = resp.read()
|
62 |
-
resp_obj = json.loads(body)
|
63 |
-
if resp_obj is None:
|
64 |
-
print "JSON-RPC: cannot JSON-decode body"
|
65 |
-
return None
|
66 |
-
if 'error' in resp_obj and resp_obj['error'] != None:
|
67 |
-
return resp_obj['error']
|
68 |
-
if 'result' not in resp_obj:
|
69 |
-
print "JSON-RPC: no result in object"
|
70 |
-
return None
|
71 |
-
|
72 |
-
return resp_obj['result']
|
73 |
-
def getblockcount(self):
|
74 |
-
return self.rpc('getblockcount')
|
75 |
-
def getwork(self, data=None):
|
76 |
-
return self.rpc('getwork', data)
|
77 |
-
|
78 |
-
def uint32(x):
|
79 |
-
return x & 0xffffffffL
|
80 |
-
|
81 |
-
def bytereverse(x):
|
82 |
-
return uint32(( ((x) << 24) | (((x) << 8) & 0x00ff0000) |
|
83 |
-
(((x) >> 8) & 0x0000ff00) | ((x) >> 24) ))
|
84 |
-
|
85 |
-
def bufreverse(in_buf):
|
86 |
-
out_words = []
|
87 |
-
for i in range(0, len(in_buf), 4):
|
88 |
-
word = struct.unpack('@I', in_buf[i:i+4])[0]
|
89 |
-
out_words.append(struct.pack('@I', bytereverse(word)))
|
90 |
-
return ''.join(out_words)
|
91 |
-
|
92 |
-
def wordreverse(in_buf):
|
93 |
-
out_words = []
|
94 |
-
for i in range(0, len(in_buf), 4):
|
95 |
-
out_words.append(in_buf[i:i+4])
|
96 |
-
out_words.reverse()
|
97 |
-
return ''.join(out_words)
|
98 |
-
|
99 |
-
class Miner:
|
100 |
-
def __init__(self, id):
|
101 |
-
self.id = id
|
102 |
-
self.max_nonce = MAX_NONCE
|
103 |
-
|
104 |
-
def work(self, datastr, targetstr):
|
105 |
-
# decode work data hex string to binary
|
106 |
-
static_data = datastr.decode('hex')
|
107 |
-
static_data = bufreverse(static_data)
|
108 |
-
|
109 |
-
# the first 76b of 80b do not change
|
110 |
-
blk_hdr = static_data[:76]
|
111 |
-
|
112 |
-
# decode 256-bit target value
|
113 |
-
targetbin = targetstr.decode('hex')
|
114 |
-
targetbin = targetbin[::-1] # byte-swap and dword-swap
|
115 |
-
targetbin_str = targetbin.encode('hex')
|
116 |
-
target = long(targetbin_str, 16)
|
117 |
-
|
118 |
-
# pre-hash first 76b of block header
|
119 |
-
static_hash = hashlib.sha256()
|
120 |
-
static_hash.update(blk_hdr)
|
121 |
-
|
122 |
-
for nonce in xrange(self.max_nonce):
|
123 |
-
|
124 |
-
# encode 32-bit nonce value
|
125 |
-
nonce_bin = struct.pack("<I", nonce)
|
126 |
-
|
127 |
-
# hash final 4b, the nonce value
|
128 |
-
hash1_o = static_hash.copy()
|
129 |
-
hash1_o.update(nonce_bin)
|
130 |
-
hash1 = hash1_o.digest()
|
131 |
-
|
132 |
-
# sha256 hash of sha256 hash
|
133 |
-
hash_o = hashlib.sha256()
|
134 |
-
hash_o.update(hash1)
|
135 |
-
hash = hash_o.digest()
|
136 |
-
|
137 |
-
# quick test for winning solution: high 32 bits zero?
|
138 |
-
if hash[-4:] != '\0\0\0\0':
|
139 |
-
continue
|
140 |
-
|
141 |
-
# convert binary hash to 256-bit Python long
|
142 |
-
hash = bufreverse(hash)
|
143 |
-
hash = wordreverse(hash)
|
144 |
|
145 |
-
|
146 |
-
|
147 |
|
148 |
-
|
149 |
-
|
150 |
-
print time.asctime(), "PROOF-OF-WORK found: %064x" % (l,)
|
151 |
-
return (nonce + 1, nonce_bin)
|
152 |
-
else:
|
153 |
-
print time.asctime(), "PROOF-OF-WORK false positive %064x" % (l,)
|
154 |
-
# return (nonce + 1, nonce_bin)
|
155 |
|
156 |
-
|
|
|
157 |
|
158 |
-
|
159 |
-
|
160 |
-
nonce = nonce_bin.encode('hex')
|
161 |
-
solution = original_data[:152] + nonce + original_data[160:256]
|
162 |
-
param_arr = [ solution ]
|
163 |
-
result = rpc.getwork(param_arr)
|
164 |
-
print time.asctime(), "--> Upstream RPC result:", result
|
165 |
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
if 'data' not in work or 'target' not in work:
|
172 |
-
time.sleep(ERR_SLEEP)
|
173 |
-
return
|
174 |
|
175 |
-
|
|
|
176 |
|
177 |
-
|
178 |
-
|
|
|
|
|
179 |
|
180 |
-
time_end = time.time()
|
181 |
-
time_diff = time_end - time_start
|
182 |
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
self.max_nonce = 0xfffffffaL
|
187 |
|
188 |
-
|
189 |
-
|
190 |
-
self.id, hashes_done,
|
191 |
-
(hashes_done / 1000.0) / time_diff)
|
192 |
|
193 |
-
|
194 |
-
|
|
|
195 |
|
196 |
-
|
197 |
-
rpc = BitcoinRPC(settings['host'], settings['port'],
|
198 |
-
settings['rpcuser'], settings['rpcpass'])
|
199 |
-
if rpc is None:
|
200 |
-
return
|
201 |
|
202 |
-
|
203 |
-
|
204 |
|
205 |
-
|
206 |
-
|
207 |
-
|
|
|
208 |
|
209 |
-
|
210 |
-
if len(sys.argv) != 2:
|
211 |
-
print "Usage: pyminer.py CONFIG-FILE"
|
212 |
-
sys.exit(1)
|
213 |
|
214 |
-
|
215 |
-
|
216 |
-
# skip comment lines
|
217 |
-
m = re.search('^\s*#', line)
|
218 |
-
if m:
|
219 |
-
continue
|
220 |
|
221 |
-
|
222 |
-
m = re.search('^(\w+)\s*=\s*(\S.*)$', line)
|
223 |
-
if m is None:
|
224 |
-
continue
|
225 |
-
settings[m.group(1)] = m.group(2)
|
226 |
-
f.close()
|
227 |
|
228 |
-
|
229 |
-
|
230 |
-
if 'port' not in settings:
|
231 |
-
settings['port'] = 8332
|
232 |
-
if 'threads' not in settings:
|
233 |
-
settings['threads'] = 1
|
234 |
-
if 'hashmeter' not in settings:
|
235 |
-
settings['hashmeter'] = 0
|
236 |
-
if 'scantime' not in settings:
|
237 |
-
settings['scantime'] = 30L
|
238 |
-
if 'rpcuser' not in settings or 'rpcpass' not in settings:
|
239 |
-
print "Missing username and/or password in cfg file"
|
240 |
-
sys.exit(1)
|
241 |
|
242 |
-
|
243 |
-
settings['threads'] = int(settings['threads'])
|
244 |
-
settings['hashmeter'] = int(settings['hashmeter'])
|
245 |
-
settings['scantime'] = long(settings['scantime'])
|
246 |
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
p.start()
|
251 |
-
thr_list.append(p)
|
252 |
-
time.sleep(1) # stagger threads
|
253 |
|
254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
255 |
|
256 |
-
|
257 |
-
try:
|
258 |
-
for thr_proc in thr_list:
|
259 |
-
thr_proc.join()
|
260 |
-
except KeyboardInterrupt:
|
261 |
-
pass
|
262 |
-
print time.asctime(), "Miner Stops - %s:%s" % (settings['host'], settings['port'])
|
|
|
1 |
+
#A stratum compatible miniminer
|
2 |
+
#based in the documentation
|
3 |
+
#https://slushpool.com/help/#!/manual/stratum-protocol
|
4 |
+
#2017-2019 Martin Nadal https://martinnadal.eu
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
|
6 |
+
import socket
|
7 |
import json
|
|
|
8 |
import hashlib
|
9 |
+
import binascii
|
10 |
+
from pprint import pprint
|
11 |
+
import time
|
12 |
+
import random
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
|
14 |
+
address = '1GvSP13YjQAu9VAa8J1Hvbc4n3N8kUE3Ch'
|
15 |
+
nonce = hex(random.randint(0,2**32-1))[2:].zfill(8)
|
16 |
|
17 |
+
host = 'solo.ckpool.org'
|
18 |
+
port = 3333
|
|
|
|
|
|
|
|
|
|
|
19 |
|
20 |
+
print("address:{} nonce:{}".format(address,nonce))
|
21 |
+
print("host:{} port:{}".format(host,port))
|
22 |
|
23 |
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
24 |
+
sock.connect((host,port))
|
|
|
|
|
|
|
|
|
|
|
25 |
|
26 |
+
#server connection
|
27 |
+
sock.sendall(b'{"id": 1, "method": "mining.subscribe", "params": []}\n')
|
28 |
+
lines = sock.recv(1024).decode().split('\n')
|
29 |
+
response = json.loads(lines[0])
|
30 |
+
sub_details,extranonce1,extranonce2_size = response['result']
|
|
|
|
|
|
|
31 |
|
32 |
+
#authorize workers
|
33 |
+
sock.sendall(b'{"params": ["'+address.encode()+b'", "password"], "id": 2, "method": "mining.authorize"}\n')
|
34 |
|
35 |
+
#we read until 'mining.notify' is reached
|
36 |
+
response = b''
|
37 |
+
while response.count(b'\n') < 4 and not(b'mining.notify' in response):
|
38 |
+
response += sock.recv(1024)
|
39 |
|
|
|
|
|
40 |
|
41 |
+
#get rid of empty lines
|
42 |
+
responses = [json.loads(res) for res in response.decode().split('\n') if len(res.strip())>0 and 'mining.notify' in res]
|
43 |
+
pprint(responses)
|
|
|
44 |
|
45 |
+
job_id,prevhash,coinb1,coinb2,merkle_branch,version,nbits,ntime,clean_jobs \
|
46 |
+
= responses[0]['params']
|
|
|
|
|
47 |
|
48 |
+
#target https://bitcoin.stackexchange.com/a/36228/44319
|
49 |
+
target = (nbits[2:]+'00'*(int(nbits[:2],16) - 3)).zfill(64)
|
50 |
+
print('nbits:{} target:{}\n'.format(nbits,target))
|
51 |
|
52 |
+
extranonce2 = '00'*extranonce2_size
|
|
|
|
|
|
|
|
|
53 |
|
54 |
+
coinbase = coinb1 + extranonce1 + extranonce2 + coinb2
|
55 |
+
coinbase_hash_bin = hashlib.sha256(hashlib.sha256(binascii.unhexlify(coinbase)).digest()).digest()
|
56 |
|
57 |
+
print('coinbase:\n{}\n\ncoinbase hash:{}\n'.format(coinbase,binascii.hexlify(coinbase_hash_bin)))
|
58 |
+
merkle_root = coinbase_hash_bin
|
59 |
+
for h in merkle_branch:
|
60 |
+
merkle_root = hashlib.sha256(hashlib.sha256(merkle_root + binascii.unhexlify(h)).digest()).digest()
|
61 |
|
62 |
+
merkle_root = binascii.hexlify(merkle_root).decode()
|
|
|
|
|
|
|
63 |
|
64 |
+
#little endian
|
65 |
+
merkle_root = ''.join([merkle_root[i]+merkle_root[i+1] for i in range(0,len(merkle_root),2)][::-1])
|
|
|
|
|
|
|
|
|
66 |
|
67 |
+
print('merkle_root:{}\n'.format(merkle_root))
|
|
|
|
|
|
|
|
|
|
|
68 |
|
69 |
+
blockheader = version + prevhash + merkle_root + nbits + ntime + nonce +\
|
70 |
+
'000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
|
72 |
+
print('blockheader:\n{}\n'.format(blockheader))
|
|
|
|
|
|
|
73 |
|
74 |
+
hash = hashlib.sha256(hashlib.sha256(binascii.unhexlify(blockheader)).digest()).digest()
|
75 |
+
hash = binascii.hexlify(hash).decode()
|
76 |
+
print('hash: {}'.format(hash))
|
|
|
|
|
|
|
77 |
|
78 |
+
if hash < target :
|
79 |
+
print('success!!')
|
80 |
+
payload = '{"params": ["'+address+'", "'+job_id+'", "'+extranonce2 \
|
81 |
+
+'", "'+ntime+'", "'+nonce+'"], "id": 1, "method": "mining.submit"}\n'
|
82 |
+
sock.sendall(payload)
|
83 |
+
print(sock.recv(1024))
|
84 |
+
else:
|
85 |
+
print('failed mine, hash is greater than target')
|
86 |
|
87 |
+
sock.close()
|
|
|
|
|
|
|
|
|
|
|
|