import hashlib import logging import threading import time import config from block_builder import ( build_block_header, build_coinbase_transaction, calculate_merkle_root, is_segwit_tx, serialize_block, ) from miner import mine_block from rpc import ( connect_rpc, get_best_block_hash, get_block_template, ensure_witness_data, submit_block, test_rpc_connection, ) from utils import calculate_target, watchdog_bestblock log = logging.getLogger(__name__) def _prepare_template(rpc) -> dict | None: """Fetch and enrich the block template. Return None on error.""" template = get_block_template(rpc) if not template: return None ensure_witness_data(rpc, template) tot_tx = len(template["transactions"]) witness_tx = sum(1 for tx in template["transactions"] if is_segwit_tx(tx["data"])) log.info( "Template height=%d total tx=%d legacy=%d segwit=%d", template["height"], tot_tx, tot_tx - witness_tx, witness_tx, ) return template def main( event_queue=None, worker_idx: int = 0, extranonce2: str | None = None, ) -> None: """ Main mining loop. Optional parameters for multiprocess usage via launcher: event_queue - queue used to send structured events to supervisor worker_idx - worker index (for event identification) extranonce2 - worker-specific extranonce2 value """ extranonce2 = extranonce2 or config.EXTRANONCE2 test_rpc_connection() log.info("Extranonce2: %s | Coinbase: %s", extranonce2, config.COINBASE_MESSAGE) # Main connection reused for the whole process lifecycle rpc = connect_rpc() # Fetch chain and scriptPubKey once - they do not change across cycles network = rpc.getblockchaininfo().get("chain", "") miner_script = rpc.getaddressinfo(config.WALLET_ADDRESS)["scriptPubKey"] log.info("Chain: %s", network) def _on_status(attempts: int, hashrate: float) -> None: if event_queue is not None: event_queue.put(("status", worker_idx, {"rate": hashrate / 1000, "attempts": attempts})) while True: try: log.info("=== New mining cycle ===") # STEP 1-3: template template = _prepare_template(rpc) if not template: log.error("Unable to fetch template. Retrying in 5s...") time.sleep(5) continue # STEP 4: coinbase coinbase_tx, coinbase_txid = build_coinbase_transaction( template, miner_script, config.EXTRANONCE1, extranonce2, config.COINBASE_MESSAGE, ) # STEP 5-7: target, Merkle root, header modified_target = calculate_target(template, config.DIFFICULTY_FACTOR, network) merkle_root = calculate_merkle_root(coinbase_txid, template["transactions"]) header_hex = build_block_header( template["version"], template["previousblockhash"], merkle_root, template["curtime"], template["bits"], 0, ) # STEP 8: start watchdog and mining stop_event = threading.Event() new_block_event = threading.Event() rpc_watch = connect_rpc() t_watch = threading.Thread( target=watchdog_bestblock, args=(rpc_watch, stop_event, new_block_event, get_best_block_hash), daemon=True, ) t_watch.start() mined_header_hex, nonce, hashrate = mine_block( header_hex, modified_target, config.NONCE_MODE, stop_event, _on_status, ) stop_event.set() t_watch.join(timeout=0.2) if new_block_event.is_set() or mined_header_hex is None: log.info("Cycle interrupted: restarting with updated template") continue # STEP 9: block hash and supervisor notification header_bytes = bytes.fromhex(mined_header_hex) block_hash = hashlib.sha256(hashlib.sha256(header_bytes).digest()).digest()[::-1].hex() log.info("Found block hash: %s", block_hash) if event_queue is not None: event_queue.put(("found", worker_idx, {"rate": hashrate / 1000 if hashrate else 0})) event_queue.put(("hash", worker_idx, block_hash)) # STEP 10: serialize and submit serialized_block = serialize_block(mined_header_hex, coinbase_tx, template["transactions"]) if not serialized_block: log.error("Block serialization failed. Retrying...") continue submit_block(rpc, serialized_block) if event_queue is not None: event_queue.put(("submit", worker_idx, None)) except Exception: log.exception("Error in mining cycle") time.sleep(1) if __name__ == "__main__": logging.basicConfig( level=logging.INFO, format="%(asctime)s | %(levelname)s | %(name)s | %(message)s", ) main()