Files
stealth/backend/script/bitcoin_rpc.py
2026-02-27 13:22:19 -03:00

159 lines
4.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
bitcoin_rpc.py — Facade that selects regtest or testnet backend
based on config.ini [bitcoin] network setting.
Exposes:
NETWORK "regtest" or "testnet"
IS_REGTEST True when running on regtest
cli(...) RPC call routed to the correct backend
mine_blocks, get_tx, get_utxos, get_balance, send_raw, ...
"""
import json
import time
import os
import configparser
# ── Load config ──────────────────────────────────────────────────────────────
def _load_config():
cfg = configparser.ConfigParser()
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.ini")
cfg.read(config_path)
return cfg["bitcoin"] if "bitcoin" in cfg else {}
_cfg = _load_config()
NETWORK = _cfg.get("network", "regtest").strip().lower()
IS_REGTEST = NETWORK == "regtest"
# ── Select the right cli backend ─────────────────────────────────────────────
if IS_REGTEST:
from lib.clis import cli_regtest as _cli_backend
else:
from lib.clis import cli_testnet as _cli_backend
def cli(*args, wallet=None):
"""Route RPC calls to the active backend."""
return _cli_backend(*args, wallet=wallet)
# ── Block mining (regtest only) ──────────────────────────────────────────────
def mine_blocks(n=1):
"""Mine n blocks. Only works on regtest; no-op on testnet."""
if not IS_REGTEST:
# On testnet we cannot mine — just wait for propagation
time.sleep(2)
return get_block_count()
miner_addr = cli("getnewaddress", "", "bech32", wallet="miner")
cli("generatetoaddress", n, miner_addr)
return int(cli("getblockcount"))
def fund_wallet(wallet_name, amount=1.0, from_wallet="miner"):
"""Send `amount` BTC from `from_wallet` to a new address in `wallet_name`."""
addr = cli("getnewaddress", "", "bech32", wallet=wallet_name)
txid = cli("sendtoaddress", addr, f"{amount:.8f}", wallet=from_wallet)
return txid, addr
def wait_for_mempool_empty(timeout=60):
"""Wait until mempool is empty (all txs mined)."""
for _ in range(timeout * 2):
info = cli("getmempoolinfo")
if info["size"] == 0:
return True
time.sleep(0.5)
return False
def get_tx(txid):
"""Get decoded transaction."""
return cli("getrawtransaction", txid, "true")
def get_utxos(wallet_name, min_conf=0):
"""List unspent outputs for a wallet."""
return cli("listunspent", min_conf, wallet=wallet_name)
def get_balance(wallet_name):
"""Get wallet balance."""
return float(cli("getbalance", wallet=wallet_name))
def send_raw(hex_tx):
"""Broadcast a raw transaction."""
return cli("sendrawtransaction", hex_tx)
def create_funded_psbt(wallet_name, inputs, outputs, options=None):
"""Create a funded PSBT."""
args = ["walletcreatefundedpsbt", json.dumps(inputs), json.dumps(outputs), 0]
if options:
args.append(json.dumps(options))
return cli(*args, wallet=wallet_name)
def process_psbt(wallet_name, psbt):
"""Sign a PSBT."""
return cli("walletprocesspsbt", psbt, wallet=wallet_name)
def finalize_psbt(psbt):
"""Finalize a PSBT."""
return cli("finalizepsbt", psbt)
def decode_psbt(psbt):
"""Decode a PSBT."""
return cli("decodepsbt", psbt)
def create_raw_tx(inputs, outputs):
"""Create a raw transaction."""
return cli("createrawtransaction", json.dumps(inputs), json.dumps(outputs))
def sign_raw_tx(wallet_name, hex_tx):
"""Sign a raw transaction."""
return cli("signrawtransactionwithwallet", hex_tx, wallet=wallet_name)
def decode_raw_tx(hex_tx):
"""Decode a raw transaction."""
return cli("decoderawtransaction", hex_tx)
def get_block_count():
"""Get current block height."""
return int(cli("getblockcount"))
def get_new_address(wallet_name, addr_type="bech32"):
"""Get a new address."""
return cli("getnewaddress", "", addr_type, wallet=wallet_name)
def send_to_address(wallet_name, address, amount):
"""Send BTC to an address."""
print(f"Sending {amount:.8f} BTC from {wallet_name} to {address}...")
return cli("sendtoaddress", address, f"{amount:.8f}", wallet=wallet_name)
if __name__ == "__main__":
print(f"Network mode: {NETWORK} (IS_REGTEST={IS_REGTEST})")
print("Testing RPC connection...")
info = cli("getblockchaininfo")
print(f" Chain: {info['chain']}")
print(f" Blocks: {info['blocks']}")
for w in ["miner", "alice", "bob", "carol", "exchange", "risky"]:
try:
bal = get_balance(w)
print(f" {w}: {bal} BTC")
except Exception as e:
print(f" {w}: ERROR - {e}")