102 lines
3.2 KiB
Python
102 lines
3.2 KiB
Python
import logging
|
|
|
|
from bitcoinrpc.authproxy import AuthServiceProxy
|
|
|
|
import config
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def connect_rpc() -> AuthServiceProxy:
|
|
"""Create an RPC connection to the Bitcoin node."""
|
|
return AuthServiceProxy(
|
|
f"http://{config.RPC_USER}:{config.RPC_PASSWORD}@{config.RPC_HOST}:{config.RPC_PORT}"
|
|
)
|
|
|
|
|
|
def test_rpc_connection() -> None:
|
|
"""Check the connection and show basic blockchain information."""
|
|
log.info("Checking RPC connection")
|
|
try:
|
|
info = connect_rpc().getblockchaininfo()
|
|
log.info(
|
|
"RPC connection successful - chain=%s, blocks=%d, difficulty=%s",
|
|
info["chain"], info["blocks"], info["difficulty"],
|
|
)
|
|
except Exception:
|
|
log.exception("RPC connection error")
|
|
raise
|
|
|
|
|
|
def get_best_block_hash(rpc) -> str | None:
|
|
"""Fetch the most recent block hash."""
|
|
try:
|
|
h = rpc.getbestblockhash()
|
|
log.debug("Best block hash: %s", h)
|
|
return h
|
|
except Exception as e:
|
|
log.error("RPC error getbestblockhash: %s", e)
|
|
return None
|
|
|
|
|
|
def get_block_template(rpc) -> dict | None:
|
|
"""Request a block template with SegWit support."""
|
|
try:
|
|
tpl = rpc.getblocktemplate({"rules": ["segwit"]})
|
|
log.debug("Template received - height=%d, tx=%d", tpl["height"], len(tpl["transactions"]))
|
|
return tpl
|
|
except Exception as e:
|
|
log.error("RPC error getblocktemplate: %s", e)
|
|
return None
|
|
|
|
|
|
def ensure_witness_data(rpc, template: dict) -> None:
|
|
"""
|
|
Enrich template transactions with full witness data.
|
|
Use a single HTTP batch call to reduce latency versus N single requests.
|
|
"""
|
|
txs = template["transactions"]
|
|
if not txs:
|
|
return
|
|
|
|
# JSON-RPC batch call: one HTTP request for all transactions
|
|
try:
|
|
batch = [["getrawtransaction", tx["txid"], False] for tx in txs]
|
|
results = rpc._batch(batch)
|
|
raw_map = {
|
|
txs[r["id"]]["txid"]: r["result"]
|
|
for r in results
|
|
if r.get("result") is not None
|
|
}
|
|
except Exception as e:
|
|
log.warning("RPC batch unavailable, falling back to single calls: %s", e)
|
|
raw_map = {}
|
|
for tx in txs:
|
|
try:
|
|
raw = rpc.getrawtransaction(tx["txid"], False)
|
|
if raw:
|
|
raw_map[tx["txid"]] = raw
|
|
except Exception as e2:
|
|
log.debug("Missing raw witness for %s: %s", tx["txid"], e2)
|
|
|
|
template["transactions"] = [
|
|
{"hash": tx["txid"], "data": raw_map.get(tx["txid"], tx["data"])}
|
|
for tx in txs
|
|
]
|
|
|
|
|
|
def submit_block(rpc, serialized_block: str) -> None:
|
|
"""Submit the mined block to the Bitcoin node."""
|
|
log.info("Submitting serialized block (%d bytes) to node", len(serialized_block) // 2)
|
|
if not serialized_block:
|
|
log.error("Block not serialized correctly - submission canceled")
|
|
return
|
|
try:
|
|
result = rpc.submitblock(serialized_block)
|
|
if result is None:
|
|
log.info("Block accepted into the blockchain")
|
|
else:
|
|
log.error("submitblock returned: %s", result)
|
|
except Exception:
|
|
log.exception("RPC error during submitblock")
|