From 371e31e3f35ac5054fe2bd7dd04bcdd8790e8988 Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Thu, 22 Aug 2024 13:00:08 +0100 Subject: [PATCH] func test: allow to run using electrs backend --- tests/fixtures.py | 11 +++++ tests/test_chain.py | 12 +++-- tests/test_framework/electrs.py | 79 +++++++++++++++++++++++++++++++++ tests/test_framework/lianad.py | 8 +++- tests/test_framework/utils.py | 3 ++ tests/test_misc.py | 10 +++++ tests/test_rpc.py | 6 ++- 7 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 tests/test_framework/electrs.py diff --git a/tests/fixtures.py b/tests/fixtures.py index fd42ab0c..4c362c83 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -3,6 +3,7 @@ from bip32.utils import _pubkey_to_fingerprint from bip380.descriptors import Descriptor from concurrent import futures from test_framework.bitcoind import Bitcoind +from test_framework.electrs import Electrs from test_framework.lianad import Lianad from test_framework.signer import SingleSigner, MultiSigner from test_framework.utils import ( @@ -126,6 +127,16 @@ def bitcoin_backend(directory, bitcoind): if BITCOIN_BACKEND_TYPE is BitcoinBackendType.Bitcoind: yield bitcoind bitcoind.cleanup() + elif BITCOIN_BACKEND_TYPE is BitcoinBackendType.Electrs: + electrs = Electrs( + electrs_dir=os.path.join(directory, "electrs"), + bitcoind_dir=bitcoind.bitcoin_dir, + bitcoind_rpcport=bitcoind.rpcport, + bitcoind_p2pport=bitcoind.p2pport, + ) + electrs.startup() + yield electrs + electrs.cleanup() else: raise NotImplementedError diff --git a/tests/test_chain.py b/tests/test_chain.py index 26487c99..b4bf5960 100644 --- a/tests/test_chain.py +++ b/tests/test_chain.py @@ -238,7 +238,8 @@ def test_rescan_edge_cases(lianad, bitcoind): outpoints_before = set(c["outpoint"] for c in coins_before) bitcoind.generate_block(1) lianad.restart_fresh(bitcoind) - assert len(list_coins()) == 0 + if BITCOIN_BACKEND_TYPE is BitcoinBackendType.Bitcoind: + assert len(list_coins()) == 0 # We can be stopped while we are rescanning lianad.rpc.startrescan(initial_tip["time"]) @@ -252,7 +253,8 @@ def test_rescan_edge_cases(lianad, bitcoind): bitcoind.generate_block(1) lianad.restart_fresh(bitcoind) wait_for(lambda: lianad.rpc.getinfo()["rescan_progress"] is None) - assert len(list_coins()) == 0 + if BITCOIN_BACKEND_TYPE is BitcoinBackendType.Bitcoind: + assert len(list_coins()) == 0 # There can be a reorg when we start rescanning reorg_shift(initial_tip["height"], txs) @@ -271,7 +273,8 @@ def test_rescan_edge_cases(lianad, bitcoind): lianad.restart_fresh(bitcoind) wait_synced() wait_for(lambda: lianad.rpc.getinfo()["rescan_progress"] is None) - assert len(list_coins()) == 0 + if BITCOIN_BACKEND_TYPE is BitcoinBackendType.Bitcoind: + assert len(list_coins()) == 0 # We can be rescanning when a reorg happens lianad.rpc.startrescan(initial_tip["time"]) @@ -350,7 +353,8 @@ def test_rescan_and_recovery(lianad, bitcoind): # Clear lianad state lianad.restart_fresh(bitcoind) - assert len(lianad.rpc.listcoins()["coins"]) == 0 + if BITCOIN_BACKEND_TYPE is BitcoinBackendType.Bitcoind: + assert len(lianad.rpc.listcoins()["coins"]) == 0 # Start rescan lianad.rpc.startrescan(initial_tip["time"]) diff --git a/tests/test_framework/electrs.py b/tests/test_framework/electrs.py new file mode 100644 index 00000000..ee8c6a51 --- /dev/null +++ b/tests/test_framework/electrs.py @@ -0,0 +1,79 @@ +import logging +import os + +from ephemeral_port_reserve import reserve +from test_framework.utils import BitcoinBackend, TailableProc, ELECTRS_PATH + + +class Electrs(BitcoinBackend): + def __init__( + self, + bitcoind_dir, + bitcoind_rpcport, + bitcoind_p2pport, + electrs_dir, + rpcport=None, + ): + TailableProc.__init__(self, electrs_dir, verbose=False) + + if rpcport is None: + rpcport = reserve() + + # Prometheus metrics can't be deactivated in Electrs. Configure the port so it doesn't + # conflict with other instances when running tests in parallel. + monitoring_port = reserve() + + self.electrs_dir = electrs_dir + self.rpcport = rpcport + + regtestdir = os.path.join(electrs_dir, "regtest") + if not os.path.exists(regtestdir): + os.makedirs(regtestdir) + + self.cmd_line = [ + ELECTRS_PATH, + "--conf", + "{}/electrs.toml".format(regtestdir), + ] + electrs_conf = { + "daemon_dir": bitcoind_dir, + "cookie_file": os.path.join(bitcoind_dir, "regtest", ".cookie"), + "daemon_rpc_addr": f"127.0.0.1:{bitcoind_rpcport}", + "daemon_p2p_addr": f"127.0.0.1:{bitcoind_p2pport}", + "db_dir": electrs_dir, + "network": "regtest", + "electrum_rpc_addr": f"127.0.0.1:{self.rpcport}", + "monitoring_addr": f"127.0.0.1:{monitoring_port}", + } + self.conf_file = os.path.join(regtestdir, "electrs.toml") + with open(self.conf_file, "w") as f: + for k, v in electrs_conf.items(): + f.write(f'{k} = "{v}"\n') + + self.env = {"RUST_LOG": "DEBUG"} + + def start(self): + TailableProc.start(self) + logging.info("Electrs started") + + def startup(self): + try: + self.start() + except Exception: + self.stop() + raise + + def stop(self): + return TailableProc.stop(self) + + def cleanup(self): + try: + self.stop() + except Exception: + self.proc.kill() + self.proc.wait() + + def append_to_lianad_conf(self, conf_file): + with open(conf_file, "a") as f: + f.write("[electrum_config]\n") + f.write(f"addr = '127.0.0.1:{self.rpcport}'\n") diff --git a/tests/test_framework/lianad.py b/tests/test_framework/lianad.py index d9b4afdb..5fcf9a38 100644 --- a/tests/test_framework/lianad.py +++ b/tests/test_framework/lianad.py @@ -5,6 +5,8 @@ import shutil from bip380.descriptors import Descriptor from bip380.miniscript import SatisfactionMaterial from test_framework.utils import ( + BITCOIN_BACKEND_TYPE, + BitcoinBackendType, UnixDomainSocketRpc, TailableProc, VERBOSE, @@ -43,6 +45,7 @@ class Lianad(TailableProc): self.cmd_line = [LIANAD_PATH, "--conf", f"{self.conf_file}"] socket_path = os.path.join(os.path.join(datadir, "regtest"), "lianad_rpc") self.rpc = UnixDomainSocketRpc(socket_path) + self.bitcoin_backend = bitcoin_backend with open(self.conf_file, "w") as f: f.write(f"data_dir = '{datadir}'\n") @@ -103,8 +106,9 @@ class Lianad(TailableProc): self.stop() dir_path = os.path.join(self.datadir, "regtest") shutil.rmtree(dir_path) - wallet_path = os.path.join(dir_path, "lianad_watchonly_wallet") - bitcoind.node_rpc.unloadwallet(wallet_path) + if BITCOIN_BACKEND_TYPE is BitcoinBackendType.Bitcoind: + wallet_path = os.path.join(dir_path, "lianad_watchonly_wallet") + bitcoind.node_rpc.unloadwallet(wallet_path) self.start() wait_for( lambda: self.rpc.getinfo()["block_height"] == bitcoind.rpc.getblockcount() diff --git a/tests/test_framework/utils.py b/tests/test_framework/utils.py index d92f862f..c48edc48 100644 --- a/tests/test_framework/utils.py +++ b/tests/test_framework/utils.py @@ -26,6 +26,7 @@ LIANAD_PATH = os.getenv("LIANAD_PATH", DEFAULT_MS_PATH) class BitcoinBackendType(str, enum.Enum): Bitcoind = "bitcoind" + Electrs = "electrs" DEFAULT_BITCOIN_BACKEND_TYPE = "bitcoind" @@ -34,6 +35,8 @@ BITCOIN_BACKEND_TYPE = BitcoinBackendType( ) DEFAULT_BITCOIND_PATH = "bitcoind" BITCOIND_PATH = os.getenv("BITCOIND_PATH", DEFAULT_BITCOIND_PATH) +DEFAULT_ELECTRS_PATH = "electrs" +ELECTRS_PATH = os.getenv("ELECTRS_PATH", DEFAULT_ELECTRS_PATH) OLD_LIANAD_PATH = os.getenv("OLD_LIANAD_PATH", None) IS_NOT_BITCOIND_24 = bool(int(os.getenv("IS_NOT_BITCOIND_24", True))) USE_TAPROOT = bool( diff --git a/tests/test_misc.py b/tests/test_misc.py index fecad7c3..7cd1f95b 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -7,6 +7,8 @@ from fixtures import * from test_framework.authproxy import JSONRPCException from test_framework.serializations import PSBT from test_framework.utils import ( + BitcoinBackendType, + BITCOIN_BACKEND_TYPE, wait_for, RpcError, OLD_LIANAD_PATH, @@ -260,6 +262,10 @@ def test_coinbase_deposit(lianad, bitcoind): OLD_LIANAD_PATH is None or USE_TAPROOT, reason="Need the old lianad binary to create the datadir.", ) +@pytest.mark.skipif( + BITCOIN_BACKEND_TYPE is not BitcoinBackendType.Bitcoind, + reason="Only bitcoind backend was available for older lianad versions.", +) def test_migration(lianad_multisig, bitcoind): """Test we can start a newer lianad on a datadir created by an older lianad.""" lianad = lianad_multisig @@ -315,6 +321,10 @@ def bitcoind_wait_new_block(bitcoind): @pytest.mark.skipif( not IS_NOT_BITCOIND_24, reason="Need 'generateblock' with 'submit=False'" ) +@pytest.mark.skipif( + BITCOIN_BACKEND_TYPE is not BitcoinBackendType.Bitcoind, + reason="Tests the retry logic specific to the bitcoind backend.", +) def test_retry_on_workqueue_exceeded(lianad, bitcoind, executor): """Make sure we retry requests to bitcoind if it is temporarily overloaded.""" # Start by reducing the work queue to a single slot. Note we need to stop lianad diff --git a/tests/test_rpc.py b/tests/test_rpc.py index cd697aae..dedc81bc 100644 --- a/tests/test_rpc.py +++ b/tests/test_rpc.py @@ -686,11 +686,13 @@ def test_start_rescan(lianad, bitcoind): # descriptor. coins_before = sorted_coins() lianad.restart_fresh(bitcoind) - assert len(list_coins()) == 0 + if BITCOIN_BACKEND_TYPE is BitcoinBackendType.Bitcoind: + assert len(list_coins()) == 0 # The wallet isn't aware what derivation indexes were used. Necessarily it'll start # from 0. - assert lianad.rpc.getnewaddress() == first_address + if BITCOIN_BACKEND_TYPE is BitcoinBackendType.Bitcoind: + assert lianad.rpc.getnewaddress() == first_address # Once the rescan is done, we must have detected all previous transactions. lianad.rpc.startrescan(initial_timestamp)