Skip to content

Commit 41f2750

Browse files
authored
Create test.py
1 parent f11df84 commit 41f2750

File tree

1 file changed

+256
-0
lines changed

1 file changed

+256
-0
lines changed

test.py

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
#This is a program for fun only.More details can go to https://github.com/HugoXOX3/PythonBitcoinMiner/discussions/66
2+
3+
import socket
4+
import json
5+
import hashlib
6+
import struct
7+
import time
8+
import multiprocessing
9+
import os
10+
import math
11+
12+
def get_input(prompt, data_type=str, default=None):
13+
while True:
14+
try:
15+
value = input(prompt + (f" (default: {default}): " if default is not None else ": "))
16+
if not value and default is not None:
17+
return default
18+
return data_type(value)
19+
except ValueError:
20+
print(f"Invalid input. Please enter a valid {data_type.__name__}.")
21+
22+
if os.path.isfile('config.json'):
23+
print("config.json found, start mining")
24+
with open('config.json','r') as file:
25+
config = json.load(file)
26+
pool_address = config['pool_address']
27+
pool_port = config["pool_port"]
28+
username = config["user_name"]
29+
password = config["password"]
30+
min_diff = config["min_diff"]
31+
# Load mining parameters if they exist
32+
default_nonce_start = config.get("nonce_start", 0)
33+
default_nonce_end = config.get("nonce_end", 2**32-1)
34+
default_extranonce_start = config.get("extranonce_start", 0)
35+
default_timestamp_start = config.get("timestamp_start", int(time.time()))
36+
else:
37+
print("config.json doesn't exist, generating now")
38+
pool_address = get_input("Enter the pool address: ")
39+
pool_port = get_input("Enter the pool port: ", int)
40+
username = get_input("Enter the user name: ")
41+
password = get_input("Enter the password: ")
42+
min_diff = get_input("Enter the minimum difficulty: ", float)
43+
44+
# Get mining parameters
45+
default_nonce_start = get_input("Enter starting nonce value (0-4294967295)", int, 0)
46+
default_nonce_end = get_input("Enter ending nonce value (must be > start)", int, 2**32-1)
47+
default_extranonce_start = get_input("Enter starting extra nonce value", int, 0)
48+
default_timestamp_start = get_input("Enter starting timestamp (unix epoch)", int, int(time.time()))
49+
50+
config_data = {
51+
"pool_address": pool_address,
52+
"pool_port": pool_port,
53+
"user_name": username,
54+
"password": password,
55+
"min_diff": min_diff,
56+
"nonce_start": default_nonce_start,
57+
"nonce_end": default_nonce_end,
58+
"extranonce_start": default_extranonce_start,
59+
"timestamp_start": default_timestamp_start
60+
}
61+
with open("config.json", "w") as config_file:
62+
json.dump(config_data, config_file, indent=4)
63+
print("Configuration data has been written to config.json")
64+
65+
def connect_to_pool(pool_address, pool_port, timeout=30, retries=5):
66+
for attempt in range(retries):
67+
try:
68+
print(f"Attempting to connect to pool (Attempt {attempt + 1}/{retries})...")
69+
sock = socket.create_connection((pool_address, pool_port), timeout)
70+
print("Connected to pool!")
71+
return sock
72+
except socket.gaierror as e:
73+
print(f"Address-related error connecting to server: {e}")
74+
except socket.timeout as e:
75+
print(f"Connection timed out: {e}")
76+
except socket.error as e:
77+
print(f"Socket error: {e}")
78+
79+
print(f"Retrying in 5 seconds...")
80+
time.sleep(5)
81+
82+
raise Exception("Failed to connect to the pool after multiple attempts")
83+
84+
def send_message(sock, message):
85+
print(f"Sending message: {message}")
86+
sock.sendall((json.dumps(message) + '\n').encode('utf-8'))
87+
88+
def receive_messages(sock, timeout=30):
89+
buffer = b''
90+
sock.settimeout(timeout)
91+
while True:
92+
try:
93+
chunk = sock.recv(1024)
94+
if not chunk:
95+
break
96+
buffer += chunk
97+
while b'\n' in buffer:
98+
line, buffer = buffer.split(b'\n', 1)
99+
print(f"Received message: {line.decode('utf-8')}")
100+
yield json.loads(line.decode('utf-8'))
101+
except socket.timeout:
102+
print("Receive operation timed out. Retrying...")
103+
continue
104+
105+
def subscribe(sock):
106+
message = {
107+
"id": 1,
108+
"method": "mining.subscribe",
109+
"params": []
110+
}
111+
send_message(sock, message)
112+
for response in receive_messages(sock):
113+
if response['id'] == 1:
114+
print(f"Subscribe response: {response}")
115+
return response['result']
116+
117+
def authorize(sock, username, password):
118+
message = {
119+
"id": 2,
120+
"method": "mining.authorize",
121+
"params": [username, password]
122+
}
123+
send_message(sock, message)
124+
for response in receive_messages(sock):
125+
if response['id'] == 2:
126+
print(f"Authorize response: {response}")
127+
return response['result']
128+
129+
def calculate_difficulty(hash_result):
130+
hash_int = int.from_bytes(hash_result[::-1], byteorder='big')
131+
max_target = 0xffff * (2**208)
132+
difficulty = max_target / hash_int
133+
return difficulty
134+
135+
def mine_worker(job, target, extranonce1, extranonce2_size,
136+
nonce_start, nonce_end,
137+
extranonce2_start, timestamp_start,
138+
result_queue, stop_event):
139+
job_id, prevhash, coinb1, coinb2, merkle_branch, version, nbits, ntime, clean_jobs = job
140+
141+
# Convert extranonce2_start to bytes of correct size
142+
extranonce2 = struct.pack('<Q', extranonce2_start)[:extranonce2_size]
143+
144+
# Use custom timestamp if provided
145+
current_ntime = timestamp_start if timestamp_start else ntime
146+
147+
coinbase = (coinb1 + extranonce1 + extranonce2.hex() + coinb2).encode('utf-8')
148+
coinbase_hash_bin = hashlib.sha256(hashlib.sha256(coinbase).digest()).digest()
149+
150+
merkle_root = coinbase_hash_bin
151+
for branch in merkle_branch:
152+
merkle_root = hashlib.sha256(hashlib.sha256((merkle_root + bytes.fromhex(branch))).digest()).digest()
153+
154+
block_header = (version + prevhash + merkle_root[::-1].hex() + current_ntime + nbits).encode('utf-8')
155+
target_bin = bytes.fromhex(target)[::-1]
156+
157+
for nonce in range(nonce_start, nonce_end):
158+
if stop_event.is_set():
159+
return
160+
161+
nonce_bin = struct.pack('<I', nonce)
162+
hash_result = hashlib.sha256(hashlib.sha256(hashlib.sha256(hashlib.sha256(block_header + nonce_bin).digest()).digest()).digest()
163+
164+
if hash_result[::-1] < target_bin:
165+
difficulty = calculate_difficulty(hash_result)
166+
if difficulty > min_diff:
167+
print(f"Nonce found: {nonce}, Difficulty: {difficulty}")
168+
print(f"Hash: {hash_result[::-1].hex()}")
169+
result_queue.put((job_id, extranonce2, current_ntime, nonce))
170+
stop_event.set()
171+
return
172+
173+
def mine(sock, job, target, extranonce1, extranonce2_size):
174+
num_processes = multiprocessing.cpu_count()
175+
176+
# Get custom mining parameters
177+
nonce_start = get_input("Enter nonce start value", int, default_nonce_start)
178+
nonce_end = get_input("Enter nonce end value", int, default_nonce_end)
179+
extranonce2_start = get_input("Enter extra nonce start value", int, default_extranonce_start)
180+
timestamp_start = get_input("Enter custom timestamp (0 for pool time)", int, 0)
181+
182+
if timestamp_start == 0:
183+
timestamp_start = job[7] # Use pool's ntime
184+
185+
nonce_range = (nonce_end - nonce_start) // num_processes
186+
if nonce_range < 1:
187+
nonce_range = 1
188+
189+
result_queue = multiprocessing.Queue()
190+
stop_event = multiprocessing.Event()
191+
192+
while not stop_event.is_set():
193+
processes = []
194+
for i in range(num_processes):
195+
start = nonce_start + (i * nonce_range)
196+
end = nonce_start + ((i + 1) * nonce_range) if i < num_processes - 1 else nonce_end
197+
198+
p = multiprocessing.Process(
199+
target=mine_worker,
200+
args=(job, target, extranonce1, extranonce2_size,
201+
start, end, extranonce2_start, timestamp_start,
202+
result_queue, stop_event)
203+
)
204+
processes.append(p)
205+
p.start()
206+
207+
for p in processes:
208+
p.join()
209+
210+
if not result_queue.empty():
211+
return result_queue.get()
212+
213+
# After completing nonce range, increment extranonce2 and reset nonce
214+
extranonce2_start += 1
215+
print(f"Incrementing extra nonce to {extranonce2_start}, resetting nonce range")
216+
217+
# Optionally increment timestamp as well
218+
if timestamp_start:
219+
timestamp_start += 1
220+
221+
def submit_solution(sock, job_id, extranonce2, ntime, nonce):
222+
message = {
223+
"id": 4,
224+
"method": "mining.submit",
225+
"params": [username, job_id, extranonce2.hex(), ntime, struct.pack('<I', nonce).hex()]
226+
}
227+
send_message(sock, message)
228+
for response in receive_messages(sock):
229+
if response['id'] == 4:
230+
print("Submission response:", response)
231+
if response['result'] == False and response['error']['code'] == 23:
232+
print(f"Low difficulty share: {response['error']['message']}")
233+
return
234+
235+
if __name__ == "__main__":
236+
if pool_address.startswith("stratum+tcp://"):
237+
pool_address = pool_address[len("stratum+tcp://"):]
238+
239+
while True:
240+
try:
241+
sock = connect_to_pool(pool_address, pool_port)
242+
243+
extranonce = subscribe(sock)
244+
extranonce1, extranonce2_size = extranonce[1], extranonce[2]
245+
authorize(sock, username, password)
246+
247+
while True:
248+
for response in receive_messages(sock):
249+
if response['method'] == 'mining.notify':
250+
job = response['params']
251+
result = mine(sock, job, job[6], extranonce1, extranonce2_size)
252+
if result:
253+
submit_solution(sock, *result)
254+
except Exception as e:
255+
print(f"An error occurred: {e}. Reconnecting...")
256+
time.sleep(5)

0 commit comments

Comments
 (0)