From 243be6f95b52125fb663bb78ff7658aa3d15c497 Mon Sep 17 00:00:00 2001
From: Matt Whitlock <c-lightning@mattwhitlock.name>
Date: Tue, 26 Aug 2025 03:03:18 -0400
Subject: [PATCH] tests: allow python-bitcointx as alternative to
 python-bitcoinlib

python-bitcointx is a fork of python-bitcoinlib that slims down the API
but still retains the functionality that we need. It is a near drop-in
alternative for our purposes, so we can trivially support it.

Notably, the JoinMarket project has been relying on python-bitcointx, so
evidently it has withstood a trial by fire.

See: https://gist.github.com/dgpv/6607c7d0eff66c387d8a5eaeb378e787
---
 contrib/pyln-testing/pyln/testing/btcproxy.py | 12 +++++++++---
 contrib/pyln-testing/pyln/testing/utils.py    | 18 +++++++++++++-----
 tests/test_closing.py                         |  5 ++++-
 tests/test_misc.py                            | 15 ++++++++++++---
 tests/test_plugin.py                          | 11 +++++++++--
 tests/test_wallet.py                          |  5 ++++-
 6 files changed, 51 insertions(+), 15 deletions(-)

diff --git a/contrib/pyln-testing/pyln/testing/btcproxy.py b/contrib/pyln-testing/pyln/testing/btcproxy.py
index 6c5994791..1da273a01 100644
--- a/contrib/pyln-testing/pyln/testing/btcproxy.py
+++ b/contrib/pyln-testing/pyln/testing/btcproxy.py
@@ -1,8 +1,12 @@
 """ A bitcoind proxy that allows instrumentation and canned responses
 """
 from flask import Flask, request  # type: ignore
-from bitcoin.rpc import JSONRPCError  # type: ignore
-from bitcoin.rpc import RawProxy as BitcoinProxy  # type: ignore
+try:
+    from bitcoin.rpc import RawProxy as BitcoinProxy, JSONRPCError  # type: ignore
+    _using_bitcoinlib = 'bitcoinlib'
+except ModuleNotFoundError:
+    from bitcointx.rpc import RPCCaller as BitcoinProxy, JSONRPCError  # type: ignore
+    _using_bitcoinlib = 'bitcointx'
 from cheroot.wsgi import Server  # type: ignore
 from cheroot.wsgi import PathInfoDispatcher  # type: ignore
 
@@ -33,7 +37,9 @@ class BitcoinRpcProxy(object):
         self.request_count = 0
 
     def _handle_request(self, r):
-        brpc = BitcoinProxy(btc_conf_file=self.bitcoind.conf_file)
+        brpc = BitcoinProxy(**{'btc_conf_file' if _using_bitcoinlib == 'bitcoinlib'
+                               else 'conf_file' if _using_bitcoinlib == 'bitcointx'
+                               else None: self.bitcoind.conf_file})
         method = r['method']
 
         # If we have set a mock for this method reply with that instead of
diff --git a/contrib/pyln-testing/pyln/testing/utils.py b/contrib/pyln-testing/pyln/testing/utils.py
index e6b01bf89..b357c1e15 100644
--- a/contrib/pyln-testing/pyln/testing/utils.py
+++ b/contrib/pyln-testing/pyln/testing/utils.py
@@ -1,6 +1,12 @@
-from bitcoin.core import COIN  # type: ignore
-from bitcoin.rpc import RawProxy as BitcoinProxy  # type: ignore
-from bitcoin.rpc import JSONRPCError
+try:
+    from bitcoin.core import COIN  # type: ignore
+    from bitcoin.rpc import RawProxy as BitcoinProxy, JSONRPCError  # type: ignore
+    _using_bitcoinlib = 'bitcoinlib'
+except ModuleNotFoundError:
+    from bitcointx.core import CoreCoinParams  # type: ignore
+    COIN = CoreCoinParams.COIN
+    from bitcointx.rpc import RPCCaller as BitcoinProxy, JSONRPCError  # type: ignore
+    _using_bitcoinlib = 'bitcointx'
 from contextlib import contextmanager
 from pathlib import Path
 from pyln.client import RpcError
@@ -428,8 +434,10 @@ class SimpleBitcoinProxy:
             raise AttributeError
 
         # Create a callable to do the actual call
-        proxy = BitcoinProxy(btc_conf_file=self.__btc_conf_file__,
-                             timeout=self.__timeout__)
+        proxy = BitcoinProxy(**{'btc_conf_file' if _using_bitcoinlib == 'bitcoinlib'
+                                else 'conf_file' if _using_bitcoinlib == 'bitcointx'
+                                else None: self.__btc_conf_file__,
+                                'timeout': self.__timeout__})
 
         def f(*args):
             logging.debug("Calling {name} with arguments {args}".format(
diff --git a/tests/test_closing.py b/tests/test_closing.py
index 5c4a17acc..d76d2f92e 100644
--- a/tests/test_closing.py
+++ b/tests/test_closing.py
@@ -13,7 +13,10 @@ from utils import (
 
 from typing import List, Optional
 
-import bitcoin
+try:
+    import bitcoin
+except ModuleNotFoundError:
+    import bitcointx as bitcoin
 import os
 import queue
 import pytest
diff --git a/tests/test_misc.py b/tests/test_misc.py
index e53b283ca..291d9cc1a 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -1,5 +1,10 @@
 import base64
-from bitcoin.rpc import RawProxy
+try:
+    from bitcoin.rpc import RawProxy as BitcoinProxy
+    _using_bitcoinlib = 'bitcoinlib'
+except ModuleNotFoundError:
+    from bitcointx.rpc import RPCCaller as BitcoinProxy
+    _using_bitcoinlib = 'bitcointx'
 from decimal import Decimal
 from fixtures import *  # noqa: F401,F403
 from fixtures import LightningNode, TEST_NETWORK
@@ -175,7 +180,9 @@ def test_bitcoin_pruned(node_factory, bitcoind):
         if fetched_peerblock:
             fetched_peerblock = False
             conf_file = os.path.join(bitcoind.bitcoin_dir, "bitcoin.conf")
-            brpc = RawProxy(btc_conf_file=conf_file)
+            brpc = BitcoinProxy(**{'btc_conf_file' if _using_bitcoinlib == 'bitcoinlib'
+                                   else 'conf_file' if _using_bitcoinlib == 'bitcointx'
+                                   else None: conf_file})
             return {
                 "result": brpc._call(r["method"], *r["params"]),
                 "error": None,
@@ -271,7 +278,9 @@ def test_lightningd_still_loading(node_factory, bitcoind, executor):
     # This is slow enough that we're going to notice.
     def mock_getblock(r):
         conf_file = os.path.join(bitcoind.bitcoin_dir, 'bitcoin.conf')
-        brpc = RawProxy(btc_conf_file=conf_file)
+        brpc = BitcoinProxy(**{'btc_conf_file' if _using_bitcoinlib == 'bitcoinlib'
+                               else 'conf_file' if _using_bitcoinlib == 'bitcointx'
+                               else None: conf_file})
         if r['params'][0] == slow_blockid:
             mock_release.wait(TIMEOUT)
         return {
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index 428864c43..ebc5990bb 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -1,4 +1,9 @@
-from bitcoin.rpc import RawProxy
+try:
+    from bitcoin.rpc import RawProxy as BitcoinProxy
+    _using_bitcoinlib = 'bitcoinlib'
+except ModuleNotFoundError:
+    from bitcointx.rpc import RPCCaller as BitcoinProxy
+    _using_bitcoinlib = 'bitcointx'
 from collections import OrderedDict
 from datetime import datetime
 from fixtures import *  # noqa: F401,F403
@@ -2043,7 +2048,9 @@ def test_bcli_concurrent(node_factory, bitcoind, executor):
     def mock_getblock(r):
         if getblockfrompeer_count >= retry_count:
             conf_file = os.path.join(bitcoind.bitcoin_dir, "bitcoin.conf")
-            brpc = RawProxy(btc_conf_file=conf_file)
+            brpc = BitcoinProxy(**{'btc_conf_file' if _using_bitcoinlib == 'bitcoinlib'
+                                   else 'conf_file' if _using_bitcoinlib == 'bitcointx'
+                                   else None: conf_file})
             return {
                 "result": brpc._call(r["method"], *r["params"]),
                 "error": None,
diff --git a/tests/test_wallet.py b/tests/test_wallet.py
index 5be686d11..bcee05ff4 100644
--- a/tests/test_wallet.py
+++ b/tests/test_wallet.py
@@ -1,4 +1,7 @@
-from bitcoin.rpc import JSONRPCError
+try:
+    from bitcoin.rpc import JSONRPCError
+except ModuleNotFoundError:
+    from bitcointx.rpc import JSONRPCError
 from decimal import Decimal
 from fixtures import *  # noqa: F401,F403
 from fixtures import TEST_NETWORK
-- 
2.53.0

