コード例 #1
0
def test_underlying_requests(empty_data_folder):
    """This test might fail on MacOS
    see #1512
    """
    specter_mock = Specter(data_folder=empty_data_folder)
    requests_session = specter_mock.requests_session()
    currency = "eur"
    price = requests_session.get(
        "https://www.bitstamp.net/api/v2/ticker/btc{}".format(currency)
    ).json()["last"]
    assert type(price) == str
    assert float(price)
コード例 #2
0
def test_reencrypt_user_secret_on_set_password(empty_data_folder):
    """
    Should re-encrypt the user_secret when the user changes their password.
    """
    specter = Specter(data_folder=empty_data_folder)

    password = "******"
    user = User.from_json(
        user_dict={
            "id": "someuser",
            "username": "******",
            "password": hash_password(password),
            "config": {},
            "is_admin": False,
            "services": None,
        },
        specter=specter,
    )

    # Force generation of a new user_secret
    user.decrypt_user_secret(password)
    assert user.encrypted_user_secret is not None
    assert user.plaintext_user_secret is not None

    first_encrypted_user_secret = user.encrypted_user_secret
    first_plaintext_user_secret = user.plaintext_user_secret

    new_password = "******"
    user.set_password(new_password)

    # The new encrypted_user_secret will be different...
    assert first_encrypted_user_secret != user.encrypted_user_secret

    # ...but the plaintext_user_secret remains unchanged
    assert first_plaintext_user_secret == user.plaintext_user_secret
コード例 #3
0
def test_generate_user_secret_on_set_password(empty_data_folder):
    """
    Should generate a user_secret if one does not yet exist when the User's password
    is changed via set_password.
    """
    specter = Specter(data_folder=empty_data_folder)

    password = "******"
    user = User.from_json(
        user_dict={
            "id": "someuser",
            "username": "******",
            "password": hash_password(password),
            "config": {},
            "is_admin": False,
            "services": None,
        },
        specter=specter,
    )

    assert user.encrypted_user_secret is None
    assert user.plaintext_user_secret is None

    new_password = "******"
    user.set_password(new_password)

    assert user.encrypted_user_secret is not None
    assert user.plaintext_user_secret is not None

    # Reset the plaintext user_secret and test decryption
    user.plaintext_user_secret = None
    user.decrypt_user_secret(new_password)

    assert user.plaintext_user_secret is not None
コード例 #4
0
def test_generate_user_secret_on_decrypt_user_secret(empty_data_folder):
    """
    Should generate a user_secret if one does not yet exist when decrypt_user_secret
    is called (happens during the login flow).
    """
    specter = Specter(data_folder=empty_data_folder)

    password = "******"
    user = User.from_json(
        user_dict={
            "id": "someuser",
            "username": "******",
            "password": hash_password(password),
            "config": {},
            "is_admin": False,
            "services": None,
        },
        specter=specter,
    )

    assert user.encrypted_user_secret is None
    assert user.plaintext_user_secret is None

    # Even though there's no user_secret yet, the flow calls decrypt anyway...
    user.decrypt_user_secret(password)

    # ...and a new user_secret is created and stored encrypted and plaintext
    assert user.encrypted_user_secret is not None
    assert user.plaintext_user_secret is not None
コード例 #5
0
def test_create_user(empty_data_folder):
    """
    Should add a new User to the `users` list, generate a user_secret, and write
    the new User to json storage.
    """
    specter = Specter(data_folder=empty_data_folder)
    user_manager = UserManager(specter=specter)

    password = "******"
    user_id = "someuser"
    username = "******"
    config = {}

    user = user_manager.create_user(user_id=user_id,
                                    username=username,
                                    plaintext_password=password,
                                    config=config)

    # new User was added to `users`
    assert user in user_manager.users

    # Generated a `user_secret`
    assert user.encrypted_user_secret is not None
    assert user.plaintext_user_secret is not None

    # Already written to persistent storage
    with open(user_manager.users_file) as user_json_file:
        user_json = json.load(user_json_file)
        assert user_id in [u.get("id") for u in user_json]
コード例 #6
0
def specter_regtest_configured(bitcoin_regtest, devices_filled_data_folder):
    # Make sure that this folder never ever gets a reasonable non-testing use-case
    data_folder = "./test_specter_data_3456778"
    shutil.rmtree(data_folder, ignore_errors=True)
    config = {
        "rpc": {
            "autodetect": False,
            "user": bitcoin_regtest.rpcconn.rpcuser,
            "password": bitcoin_regtest.rpcconn.rpcpassword,
            "port": bitcoin_regtest.rpcconn.rpcport,
            "host": bitcoin_regtest.rpcconn.ipaddress,
            "protocol": "http",
        },
        "auth": "rpcpasswordaspin",
    }
    specter = Specter(data_folder=devices_filled_data_folder, config=config)
    specter.check()
    yield specter
    shutil.rmtree(data_folder, ignore_errors=True)
コード例 #7
0
def app_no_node(empty_data_folder) -> SpecterFlask:
    specter = Specter(data_folder=empty_data_folder)
    app = create_app(config="cryptoadvance.specter.config.TestConfig")
    app.app_context().push()
    app.config["TESTING"] = True
    app.testing = True
    app.tor_service_id = None
    app.tor_enabled = False
    init_app(app, specter=specter)
    return app
コード例 #8
0
def test_failsafe_request_get(empty_data_folder):
    specter_mock = Specter(data_folder=empty_data_folder)
    requests_session = specter_mock.requests_session()
    currency = "notExisting"
    url = "https://www.bitstamp.net/api/v2/ticker/btc{}".format(currency)
    with pytest.raises(SpecterError) as se:
        failsafe_request_get(requests_session, url)
    assert f"The currency_pair does not seem to exist for that provider" in str(
        se.value
    )

    currency = "usd"
    # timestamp most probably in the future
    url = "https://www.bitstamp.net/api/v2/ohlc/btc{}/?limit=A&step=86400&start={}".format(
        currency, 6275453759
    )
    with pytest.raises(SpecterError) as se:
        failsafe_request_get(requests_session, url)
    assert str(se.value).startswith("JSON error:")
コード例 #9
0
ファイル: conftest.py プロジェクト: janoside/specter-desktop
def specter_regtest_configured(bitcoin_regtest, devices_filled_data_folder):
    # Make sure that this folder never ever gets a reasonable non-testing use-case
    config = {
        "rpc": {
            "autodetect": False,
            "datadir": "",
            "user": bitcoin_regtest.rpcconn.rpcuser,
            "password": bitcoin_regtest.rpcconn.rpcpassword,
            "port": bitcoin_regtest.rpcconn.rpcport,
            "host": bitcoin_regtest.rpcconn.ipaddress,
            "protocol": "http",
        },
        "auth": {
            "method": "rpcpasswordaspin",
        },
    }
    specter = Specter(data_folder=devices_filled_data_folder, config=config)
    specter.check()
    assert not specter.wallet_manager.working_folder is None
    yield specter
コード例 #10
0
def test_storage_field_encrypt_decrypt(empty_data_folder):
    """
    Storage class should be able to use the associated User's decrypted user_secret
    to encrypt and decrypt the specified encrypted_fields to and from on-disk
    json. When loaded into memory, all fields -- whether encrypted or not -- should
    be plaintext readable.
    """
    specter = Specter(data_folder=empty_data_folder)

    password = "******"
    user = User.from_json(
        user_dict={
            "id": "someuser",
            "username": "******",
            "password": hash_password("somepassword"),
            "config": {},
            "is_admin": False,
            "services": None,
        },
        specter=specter,
    )

    # User must provide their password in order to decrypt their user_secret which is
    #   then used to decrypt/encrypt their service storage
    user.decrypt_user_secret(password)

    storage = ExampleStorage(
        data_folder=specter.data_folder, encryption_key=user.plaintext_user_secret
    )
    storage.data["testfield1"] = "This data is not encrypted"
    storage.data["testfield2"] = "This data WILL BE encrypted"
    storage._save()

    # Read the resulting storage file
    with open(storage.data_file, "r") as storage_json_file:
        data_on_disk = json.load(storage_json_file)
    print(data_on_disk)

    # Plaintext fields are readable...
    assert data_on_disk["testfield1"] == storage.data["testfield1"]

    # ...while encrypted fields are not
    assert data_on_disk["testfield2"] != storage.data["testfield2"]

    # Re-instantiate the storage so it has to load from the saved file...
    storage_2 = ExampleStorage(
        data_folder=specter.data_folder, encryption_key=user.plaintext_user_secret
    )

    # ...and verify the field decryption
    assert storage_2.data["testfield2"] == storage.data["testfield2"]
    assert data_on_disk["testfield2"] != storage_2.data["testfield2"]
コード例 #11
0
def test_reencrypt_user_secret_on_iterations_increase(empty_data_folder):
    """
    Should re-encrypt the user_secret when the User.encryption_iterations is increased
    """
    specter = Specter(data_folder=empty_data_folder)

    password = "******"
    user = User.from_json(
        user_dict={
            "id": "someuser",
            "username": "******",
            "password": hash_password(password),
            "config": {},
            "is_admin": False,
            "services": None,
        },
        specter=specter,
    )

    # Override current default iterations setting
    original_encryption_iterations = user.encryption_iterations
    user.encryption_iterations -= 10000

    # Force generation of a new user_secret
    user.decrypt_user_secret(password)
    assert user.encrypted_user_secret is not None
    assert user.plaintext_user_secret is not None
    assert user.encrypted_user_secret["iterations"] < original_encryption_iterations

    first_encrypted_user_secret = user.encrypted_user_secret
    first_plaintext_user_secret = user.plaintext_user_secret

    # Reset iterations to default
    user.encryption_iterations = original_encryption_iterations

    # On decrypt, should automatically re-encrypt the `user_secret`
    user.decrypt_user_secret(password)
    assert user.encrypted_user_secret["iterations"] == original_encryption_iterations

    # The new encrypted_user_secret will be different...
    assert first_encrypted_user_secret != user.encrypted_user_secret

    # ...but the plaintext_user_secret remains unchanged
    assert first_plaintext_user_secret == user.plaintext_user_secret
コード例 #12
0
def test_abandon_purged_tx(caplog, docker, request, devices_filled_data_folder,
                           device_manager):
    # Specter should support calling abandontransaction if a pending tx has been purged
    # from the mempool. Test starts a new bitcoind with a restricted mempool to make it
    # easier to spam the mempool and purge our target tx.
    # TODO: Similar test but for maxmempoolexpiry?

    # Copied and adapted from:
    #    https://github.com/bitcoin/bitcoin/blob/master/test/functional/mempool_limit.py
    from bitcoin_core.test.functional.test_framework.util import (
        gen_return_txouts,
        satoshi_round,
        create_lots_of_big_transactions,
    )
    from conftest import instantiate_bitcoind_controller

    caplog.set_level(logging.DEBUG)

    # ==== Specter-specific: do custom setup ====
    # Instantiate a new bitcoind w/limited mempool. Use a different port to not interfere
    # with existing instance for other tests.
    bitcoind_controller = instantiate_bitcoind_controller(
        docker,
        request,
        rpcport=18998,
        extra_args=[
            "-acceptnonstdtxn=1", "-maxmempool=5", "-spendzeroconfchange=0"
        ],
    )
    rpcconn = bitcoind_controller.rpcconn
    rpc = rpcconn.get_rpc()
    assert rpc is not None
    assert rpc.ipaddress != None

    # Note: Our utxo creation is simpler than mempool_limit.py's approach since we're
    # running in regtest and can just use generatetoaddress().

    # Instantiate a new Specter instance to talk to this bitcoind
    config = {
        "rpc": {
            "autodetect": False,
            "user": rpcconn.rpcuser,
            "password": rpcconn.rpcpassword,
            "port": rpcconn.rpcport,
            "host": rpcconn.ipaddress,
            "protocol": "http",
        },
        "auth": {
            "method": "rpcpasswordaspin",
        },
    }
    specter = Specter(data_folder=devices_filled_data_folder, config=config)
    specter.check()

    specter.check_node_info()
    assert specter._info["mempool_info"][
        "maxmempool"] == 5 * 1000 * 1000  # 5MB

    # Largely copy-and-paste from test_wallet_manager.test_wallet_createpsbt.
    # TODO: Make a test fixture in conftest.py that sets up already funded wallets
    # for a bitcoin core hot wallet.
    wallet_manager = WalletManager(
        200100,
        devices_filled_data_folder,
        rpc,
        "regtest",
        device_manager,
    )

    # Create a new device that can sign psbts (Bitcoin Core hot wallet)
    device = device_manager.add_device(name="bitcoin_core_hot_wallet",
                                       device_type="bitcoincore",
                                       keys=[])
    device.setup_device(file_password=None, wallet_manager=wallet_manager)
    device.add_hot_wallet_keys(
        mnemonic=generate_mnemonic(strength=128),
        passphrase="",
        paths=["m/49h/0h/0h"],
        file_password=None,
        wallet_manager=wallet_manager,
        testnet=True,
        keys_range=[0, 1000],
        keys_purposes=[],
    )

    wallet = wallet_manager.create_wallet("bitcoincore_test_wallet", 1,
                                          "sh-wpkh", [device.keys[0]],
                                          [device])

    # Fund the wallet. Going to need a LOT of utxos to play with.
    logging.info("Generating utxos to wallet")
    address = wallet.getnewaddress()
    wallet.rpc.generatetoaddress(91, address)

    # newly minted coins need 100 blocks to get spendable
    # let's mine another 100 blocks to get these coins spendable
    wallet.rpc.generatetoaddress(101, address)

    # update the wallet data
    wallet.get_balance()

    # ==== Begin test from mempool_limit.py ====
    txouts = gen_return_txouts()
    relayfee = satoshi_round(rpc.getnetworkinfo()["relayfee"])

    logging.info("Check that mempoolminfee is minrelytxfee")
    assert satoshi_round(
        rpc.getmempoolinfo()["minrelaytxfee"]) == Decimal("0.00001000")
    assert satoshi_round(
        rpc.getmempoolinfo()["mempoolminfee"]) == Decimal("0.00001000")

    txids = []
    utxos = wallet.rpc.listunspent()

    logging.info("Create a mempool tx that will be evicted")
    us0 = utxos.pop()
    inputs = [{"txid": us0["txid"], "vout": us0["vout"]}]
    outputs = {wallet.getnewaddress(): 0.0001}
    tx = wallet.rpc.createrawtransaction(inputs, outputs)
    wallet.rpc.settxfee(
        str(relayfee))  # specifically fund this tx with low fee
    txF = wallet.rpc.fundrawtransaction(tx)
    wallet.rpc.settxfee(0)  # return to automatic fee selection
    txFS = device.sign_raw_tx(txF["hex"], wallet)
    txid = wallet.rpc.sendrawtransaction(txFS["hex"])

    # ==== Specter-specific: can't abandon a valid pending tx ====
    try:
        wallet.abandontransaction(txid)
    except SpecterError as e:
        assert "Cannot abandon" in str(e)

    # ==== Resume test from mempool_limit.py ====
    # Spam the mempool with big transactions!
    relayfee = satoshi_round(rpc.getnetworkinfo()["relayfee"])
    base_fee = float(relayfee) * 100
    for i in range(3):
        txids.append([])
        txids[i] = create_lots_of_big_transactions(wallet, txouts,
                                                   utxos[30 * i:30 * i + 30],
                                                   30, (i + 1) * base_fee)

    logging.info("The tx should be evicted by now")
    assert txid not in wallet.rpc.getrawmempool()
    txdata = wallet.rpc.gettransaction(txid)
    assert txdata["confirmations"] == 0  # confirmation should still be 0

    # ==== Specter-specific: Verify purge and abandon ====
    assert wallet.is_tx_purged(txid)
    wallet.abandontransaction(txid)

    # tx will still be in the wallet but marked "abandoned"
    txdata = wallet.rpc.gettransaction(txid)
    for detail in txdata["details"]:
        if detail["category"] == "send":
            assert detail["abandoned"]

    # Can we now spend those same inputs?
    outputs = {wallet.getnewaddress(): 0.0001}
    tx = wallet.rpc.createrawtransaction(inputs, outputs)

    # Fund this tx with a high enough fee
    relayfee = satoshi_round(rpc.getnetworkinfo()["relayfee"])
    wallet.rpc.settxfee(str(relayfee * Decimal("3.0")))

    txF = wallet.rpc.fundrawtransaction(tx)
    wallet.rpc.settxfee(0)  # return to automatic fee selection
    txFS = device.sign_raw_tx(txF["hex"], wallet)
    txid = wallet.rpc.sendrawtransaction(txFS["hex"])

    # Should have been accepted by the mempool
    assert txid in wallet.rpc.getrawmempool()
    assert wallet.get_balance()["untrusted_pending"] == 0.0001

    # Clean up
    bitcoind_controller.stop_bitcoind()
コード例 #13
0
def test_import_address_labels(caplog, docker, request,
                               devices_filled_data_folder, device_manager):
    caplog.set_level(logging.DEBUG)

    # ==== Specter-specific: do custom setup ====
    # Instantiate a new bitcoind w/limited mempool. Use a different port to not interfere
    # with existing instance for other tests.
    bitcoind_controller = instantiate_bitcoind_controller(
        docker,
        request,
        rpcport=18968,
        extra_args=[
            "-acceptnonstdtxn=1", "-maxmempool=5", "-spendzeroconfchange=0"
        ],
    )
    try:
        assert bitcoind_controller.get_rpc().test_connection()
        rpcconn = bitcoind_controller.rpcconn
        rpc = rpcconn.get_rpc()
        assert rpc is not None
        assert rpc.ipaddress != None

        # Note: Our utxo creation is simpler than mempool_limit.py's approach since we're
        # running in regtest and can just use generatetoaddress().

        # Instantiate a new Specter instance to talk to this bitcoind
        config = {
            "rpc": {
                "autodetect": False,
                "datadir": "",
                "user": rpcconn.rpcuser,
                "password": rpcconn.rpcpassword,
                "port": rpcconn.rpcport,
                "host": rpcconn.ipaddress,
                "protocol": "http",
            },
            "auth": {
                "method": "rpcpasswordaspin",
            },
        }
        specter = Specter(data_folder=devices_filled_data_folder,
                          config=config)
        specter.check()

        # Largely copy-and-paste from test_wallet_manager.test_wallet_createpsbt.
        # TODO: Make a test fixture in conftest.py that sets up already funded wallets
        # for a bitcoin core hot wallet.
        wallet_manager = WalletManager(
            200100,
            devices_filled_data_folder,
            rpc,
            "regtest",
            device_manager,
            allow_threading=False,
        )

        # Create a new device that can sign psbts (Bitcoin Core hot wallet)
        device = device_manager.add_device(name="bitcoin_core_hot_wallet",
                                           device_type="bitcoincore",
                                           keys=[])
        device.setup_device(file_password=None, wallet_manager=wallet_manager)
        device.add_hot_wallet_keys(
            mnemonic=
            "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
            passphrase="",
            paths=["m/49h/0h/0h"],
            file_password=None,
            wallet_manager=wallet_manager,
            testnet=True,
            keys_range=[0, 1000],
            keys_purposes=[],
        )

        wallet = wallet_manager.create_wallet("bitcoincore_test_wallet", 1,
                                              "sh-wpkh", [device.keys[0]],
                                              [device])

        # Fund the wallet. Going to need a LOT of utxos to play with.
        logger.info("Generating utxos to wallet")
        test_address = wallet.getnewaddress(
        )  # 2NCSZrX49HHyzUy6oj8ggm9WD19hFvjzzou

        wallet.rpc.generatetoaddress(1, test_address)[0]

        # newly minted coins need 100 blocks to get spendable
        # let's mine another 100 blocks to get these coins spendable
        trash_address = wallet.getnewaddress()
        wallet.rpc.generatetoaddress(100, trash_address)

        # the utxo is only available after the 100 mined blocks
        utxos = wallet.rpc.listunspent()
        # txid of the funding of test_address
        txid = utxos[0]["txid"]

        assert wallet._addresses[test_address]["label"] is None
        number_of_addresses = len(wallet._addresses)

        # Electrum
        # Test it with a txid label that does not belong to the wallet -> should be ignored
        wallet.import_address_labels(
            json.dumps({
                "8d0958cb8701fac7421eb077e44b36809b90c7ad4a35e0c607c2cd591c522668":
                "txid label"
            }))
        assert wallet._addresses[test_address]["label"] is None
        assert len(wallet._addresses) == number_of_addresses

        # Test it with an address label that does not belong to the wallet -> should be ignored
        wallet.import_address_labels(
            json.dumps({"12dRugNcdxK39288NjcDV4GX7rMsKCGn6B":
                        "address label"}))
        assert wallet._addresses[test_address]["label"] is None
        assert len(wallet._addresses) == number_of_addresses

        # Test it with a txid label
        wallet.import_address_labels(json.dumps({txid: "txid label"}))
        assert wallet._addresses[test_address]["label"] == "txid label"

        # The txid label should now be replaced by the address label
        wallet.import_address_labels(
            json.dumps({test_address: "address label"}))
        assert wallet._addresses[test_address]["label"] == "address label"

        # Specter JSON
        wallet._addresses[test_address].set_label("some_fancy_label_json")
        specter_json = json.dumps(wallet.to_json(for_export=True))
        wallet._addresses[test_address].set_label("label_got_lost")
        wallet.import_address_labels(specter_json)
        assert wallet._addresses[test_address][
            "label"] == "some_fancy_label_json"

        # Specter CSV
        csv_string = """Index,Address,Type,Label,Used,UTXO,Amount (BTC)
        0,2NCSZrX49HHyzUy6oj8ggm9WD19hFvjzzou,receive,some_fancy_label_csv,Yes,0,0"""
        wallet._addresses[test_address].set_label("label_got_lost")
        wallet.import_address_labels(csv_string)
        assert wallet._addresses[test_address][
            "label"] == "some_fancy_label_csv"

    finally:
        # Clean up
        bitcoind_controller.stop_bitcoind()
コード例 #14
0
def specter_regtest_configured(bitcoin_regtest, devices_filled_data_folder):
    assert bitcoin_regtest.get_rpc().test_connection()
    config = {
        "rpc": {
            "autodetect": False,
            "datadir": "",
            "user": bitcoin_regtest.rpcconn.rpcuser,
            "password": bitcoin_regtest.rpcconn.rpcpassword,
            "port": bitcoin_regtest.rpcconn.rpcport,
            "host": bitcoin_regtest.rpcconn.ipaddress,
            "protocol": "http",
        },
        "auth": {
            "method": "rpcpasswordaspin",
        },
    }
    specter = Specter(data_folder=devices_filled_data_folder, config=config)
    assert specter.chain == "regtest"
    # Create a User
    someuser = specter.user_manager.add_user(
        User.from_json(
            user_dict={
                "id": "someuser",
                "username": "******",
                "password": hash_password("somepassword"),
                "config": {},
                "is_admin": False,
                "services": None,
            },
            specter=specter,
        ))
    specter.user_manager.save()
    specter.check()

    assert not someuser.wallet_manager.working_folder is None

    # Create a Wallet
    wallet_json = '{"label": "a_simple_wallet", "blockheight": 0, "descriptor": "wpkh([1ef4e492/84h/1h/0h]tpubDC5EUwdy9WWpzqMWKNhVmXdMgMbi4ywxkdysRdNr1MdM4SCfVLbNtsFvzY6WKSuzsaVAitj6FmP6TugPuNT6yKZDLsHrSwMd816TnqX7kuc/0/*)#xp8lv5nr", "devices": [{"type": "trezor", "label": "trezor"}]} '
    wallet_importer = WalletImporter(wallet_json,
                                     specter,
                                     device_manager=someuser.device_manager)
    wallet_importer.create_nonexisting_signers(
        someuser.device_manager,
        {
            "unknown_cosigner_0_name": "trezor",
            "unknown_cosigner_0_type": "trezor"
        },
    )
    dm: DeviceManager = someuser.device_manager
    wallet = wallet_importer.create_wallet(someuser.wallet_manager)
    try:
        # fund it with some coins
        bitcoin_regtest.testcoin_faucet(address=wallet.getnewaddress())
        # make sure it's confirmed
        bitcoin_regtest.mine()
        # Realize that the wallet has funds:
        wallet.update()
    except SpecterError as se:
        if str(se).startswith("Timeout"):
            pytest.fail(
                "We got a Bitcoin-RPC timeout while setting up the test, minting some coins. Test Error! Check cpu/mem utilastion and btc/elem logs!"
            )
            return
        else:
            raise se

    assert wallet.fullbalance >= 20
    assert not specter.wallet_manager.working_folder is None
    try:
        yield specter
    finally:
        # Deleting all Wallets (this will also purge them on core)
        for user in specter.user_manager.users:
            for wallet in list(user.wallet_manager.wallets.values()):
                user.wallet_manager.delete_wallet(
                    wallet,
                    bitcoin_datadir=bitcoin_regtest.datadir,
                    chain="regtest")