146 lines
5.0 KiB
Python
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()
|