|
| 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