Files
cpu-miner/prototype/main.py

146 lines
5.0 KiB
Python

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