From 1566c8243e7f5623528f4e14d9819d8a2785a556 Mon Sep 17 00:00:00 2001 From: Kim Neunert Date: Thu, 26 Jan 2023 13:27:50 +0100 Subject: [PATCH 1/5] Some smaller improvements and fixes --- .vscode/settings.json | 4 ++++ src/cryptoadvance/specter/managers/wallet_manager.py | 4 +++- src/cryptoadvance/specter/server_endpoints/wallets/wallets.py | 4 +--- src/cryptoadvance/specter/wallet.py | 2 +- src/cryptoadvance/specterext/spectrum/spectrum_node.py | 4 ++++ 5 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..e137fadb9e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/src/cryptoadvance/specter/managers/wallet_manager.py b/src/cryptoadvance/specter/managers/wallet_manager.py index d44006a265..8731c53ae3 100644 --- a/src/cryptoadvance/specter/managers/wallet_manager.py +++ b/src/cryptoadvance/specter/managers/wallet_manager.py @@ -249,7 +249,7 @@ def _update(self, wallets_update_list: Dict): ) self.wallets[wallet_name] = loaded_wallet except Exception as e: - handle_exception(e) + logger.exception(e) self._failed_load_wallets.append( { **wallets_update_list[wallet], @@ -263,6 +263,8 @@ def _update(self, wallets_update_list: Dict): # only ignore rpc errors except RpcError as e: logger.error(f"Failed updating wallet manager. RPC error: {e}") + finally: + self.is_loading = False logger.info("Updating wallet manager done. Result:") logger.info(f" * loaded_wallets: {len(self.wallets)}") logger.info(f" * failed_load_wallets: {len(self._failed_load_wallets)}") diff --git a/src/cryptoadvance/specter/server_endpoints/wallets/wallets.py b/src/cryptoadvance/specter/server_endpoints/wallets/wallets.py index 3e08f76f43..5234d74213 100644 --- a/src/cryptoadvance/specter/server_endpoints/wallets/wallets.py +++ b/src/cryptoadvance/specter/server_endpoints/wallets/wallets.py @@ -444,7 +444,7 @@ def history(wallet_alias): wallet.update_balance() wallet.check_utxo() - renderting = render_template( + return render_template( "wallet/history/wallet_history.jinja", wallet_alias=wallet_alias, wallet=wallet, @@ -453,8 +453,6 @@ def history(wallet_alias): rand=rand, services=app.specter.service_manager.services, ) - logger.info("-------------------end render_template()") - return renderting ###### Wallet receive ###### diff --git a/src/cryptoadvance/specter/wallet.py b/src/cryptoadvance/specter/wallet.py index bf4066000d..6920334d9e 100644 --- a/src/cryptoadvance/specter/wallet.py +++ b/src/cryptoadvance/specter/wallet.py @@ -875,7 +875,7 @@ def check_utxo(self): except Exception as e: logger.exception(e) self._full_utxo = [] - raise SpecterError(f"Failed to load utxos, {e}") + raise SpecterError(f"Failed to load utxos, {type(e).__name__}: {e}") def check_utxo_orig(self): """fetches the utxo-set from core and stores the result in self.__full_utxo which is diff --git a/src/cryptoadvance/specterext/spectrum/spectrum_node.py b/src/cryptoadvance/specterext/spectrum/spectrum_node.py index 137c116d3c..35b93f7e38 100644 --- a/src/cryptoadvance/specterext/spectrum/spectrum_node.py +++ b/src/cryptoadvance/specterext/spectrum/spectrum_node.py @@ -212,3 +212,7 @@ def node_logo_template(self): def node_connection_template(self): return "spectrum/components/spectrum_node_connection.jinja" + + @property + def taproot_support(self): + return False From defb4a9b0fcb3eabc6b992a604152ef07fd75061 Mon Sep 17 00:00:00 2001 From: Kim Neunert Date: Thu, 26 Jan 2023 13:28:20 +0100 Subject: [PATCH 2/5] mitigating 2078 --- src/cryptoadvance/specter/txlist.py | 25 +- src/cryptoadvance/specter/util/common.py | 9 +- tests/conftest2.py | 695 +++++++++++++++++++++++ 3 files changed, 720 insertions(+), 9 deletions(-) create mode 100644 tests/conftest2.py diff --git a/src/cryptoadvance/specter/txlist.py b/src/cryptoadvance/specter/txlist.py index 4d8f42b11f..204bb5f4e8 100644 --- a/src/cryptoadvance/specter/txlist.py +++ b/src/cryptoadvance/specter/txlist.py @@ -16,6 +16,7 @@ from .specter_error import SpecterError, SpecterInternalException from embit.descriptor import Descriptor from embit.liquid.descriptor import LDescriptor +from .util.common import str2bool from .util.psbt import ( AbstractTxContext, SpecterInputScope, @@ -84,6 +85,7 @@ class TxItem(dict, AbstractTxListContext): "vsize", "address", ] + # type_converter will be used to _read_csv to have a proper mapping type_converter = [ str, int, @@ -289,13 +291,14 @@ def __dict__(self): class WalletAwareTxItem(TxItem): PSBTCls = SpecterPSBT + + # Columns for writing CSVs, type_converter for reading columns = TxItem.columns.copy() columns.extend( ["category", "flow_amount", "utxo_amount", "ismine"], ) - type_converter = TxItem.type_converter.copy() - type_converter.extend([str, float, float, bool]) + type_converter.extend([str, float, float, str2bool]) def __init__(self, parent, addresses, rawdir, **kwargs): super().__init__(parent, addresses, rawdir, **kwargs) @@ -323,6 +326,15 @@ def psbt(self) -> SpecterPSBT: self._psbt.update(updated) return self._psbt + @property + def is_taproot(self): + return str(self.descriptor).startswith("tr(") + + @property + def psbt_decoded(self) -> SpecterPSBT: + """This tx but as a psbt. Need rpc-calls""" + return self.rpc.decodepsbt(str(self.psbt)) + @property def category(self): """One of mixed (default), generate, selftransfer, receive or send""" @@ -385,8 +397,11 @@ def flow_amount(self) -> float: def ismine(self) -> bool: if self.get("ismine"): return self["ismine"] - inputs = self.psbt.inputs - outputs = self.psbt.outputs + if self.is_taproot: + # This is a bug mitigation, see #2078 + return True + inputs: List[SpecterInputScope] = self.psbt.inputs + outputs: List[SpecterOutputScope] = self.psbt.outputs any_inputs_mine = any([inp.is_mine for inp in inputs]) any_outputs_mine = any([out.is_mine for out in outputs]) self["ismine"] = any_inputs_mine or any_outputs_mine @@ -474,6 +489,8 @@ def clear_cache(self): tx.clear_cache() delete_file(self.path) self._file_exists = False + self.clear() + logger.info(f"Cleared the Cache for {self.path} (and rawdir)") def getfetch(self, txid): diff --git a/src/cryptoadvance/specter/util/common.py b/src/cryptoadvance/specter/util/common.py index d5cf555472..a071bde31e 100644 --- a/src/cryptoadvance/specter/util/common.py +++ b/src/cryptoadvance/specter/util/common.py @@ -4,6 +4,7 @@ import json from flask_babel.speaklater import LazyString from typing import Union +from distutils.util import strtobool logger = logging.getLogger(__name__) @@ -12,11 +13,9 @@ def str2bool(my_str): """returns a reasonable boolean from a string so that "False" will result in False""" if my_str is None: return False - elif isinstance(my_str, str) and my_str.lower() == "false": - return False - elif isinstance(my_str, str) and my_str.lower() == "off": - return False - return bool(my_str) + elif isinstance(my_str, bool): + return my_str + return bool(strtobool(my_str)) def camelcase2snake_case(name): diff --git a/tests/conftest2.py b/tests/conftest2.py new file mode 100644 index 0000000000..f1f917d1fc --- /dev/null +++ b/tests/conftest2.py @@ -0,0 +1,695 @@ +import atexit +import code +import json +import logging +import os +import signal +import sys +import tempfile +import traceback + +import pytest +from cryptoadvance.specter.config import TestConfig +from cryptoadvance.specter.node import Node +from cryptoadvance.specter.managers.device_manager import DeviceManager +from cryptoadvance.specter.managers.node_manager import NodeManager +from cryptoadvance.specter.managers.user_manager import UserManager +from cryptoadvance.specter.process_controller.bitcoind_controller import ( + BitcoindPlainController, +) +from cryptoadvance.specter.process_controller.elementsd_controller import ( + ElementsPlainController, +) +from cryptoadvance.specter.server import SpecterFlask, create_app, init_app +from cryptoadvance.specter.specter import Specter +from cryptoadvance.specter.specter_error import ( + BrokenCoreConnectionException, + SpecterError, + handle_exception, +) +from cryptoadvance.specter.user import User, hash_password +from cryptoadvance.specter.util.common import str2bool +from cryptoadvance.specter.util.shell import which +from cryptoadvance.specter.util.wallet_importer import WalletImporter + +logger = logging.getLogger(__name__) + +pytest_plugins = [ + "conftest_visibility", + "fix_ghost_machine", + "fix_keys_and_seeds", + "fix_devices_and_wallets", + "fix_testnet", +] + +# This is from https://stackoverflow.com/questions/132058/showing-the-stack-trace-from-a-running-python-application +# it enables stopping a hanging test via sending the pytest-process a SIGUSR2 (12) +# kill 12 pid-of-pytest +# In the article they claim to open a debug-console which didn't work for me but at least +# you get a stacktrace in the output. +def debug(sig, frame): + """Interrupt running process, and provide a python prompt for + interactive debugging.""" + d = {"_frame": frame} # Allow access to frame object. + d.update(frame.f_globals) # Unless shadowed by global + d.update(frame.f_locals) + + i = code.InteractiveConsole(d) + message = "Signal received : entering python shell.\nTraceback:\n" + message += "".join(traceback.format_stack(frame)) + i.interact(message) + + +def listen(): + signal.signal(signal.SIGUSR2, debug) # Register handler + + +def pytest_addoption(parser): + """Internally called to add options to pytest + see pytest_generate_tests(metafunc) on how to check that + Also used to register the SIGUSR2 (12) as decribed in conftest.py + """ + parser.addoption( + "--bitcoind-version", + action="store", + default="v0.20.1", + help="Version of bitcoind (something which works with git checkout ...)", + ) + parser.addoption( + "--bitcoind-log-stdout", + action="store", + default=False, + help="Whether bitcoind should log to stdout (default:False)", + ) + parser.addoption( + "--elementsd-version", + action="store", + default="master", + help="Version of elementsd (something which works with git checkout ...)", + ) + listen() + + +def pytest_generate_tests(metafunc): + # ToDo: use custom compiled version of bitcoind + # E.g. test again bitcoind version [currentRelease] + master-branch + if "docker" in metafunc.fixturenames: + if metafunc.config.getoption("docker"): + # That's a list because we could do both (see above) but currently that doesn't make sense in that context + metafunc.parametrize("docker", [True], scope="session") + else: + metafunc.parametrize("docker", [False], scope="session") + + +def instantiate_bitcoind_controller( + request, rpcport=18543, extra_args=[] +) -> BitcoindPlainController: + # logging.getLogger().setLevel(logging.DEBUG) + requested_version = request.config.getoption("--bitcoind-version") + log_stdout = str2bool(request.config.getoption("--bitcoind-log-stdout")) + if os.path.isfile("tests/bitcoin/src/bitcoind"): + bitcoind_controller = BitcoindPlainController( + bitcoind_path="tests/bitcoin/src/bitcoind", rpcport=rpcport + ) # always prefer the self-compiled bitcoind if existing + elif os.path.isfile("tests/bitcoin/bin/bitcoind"): + bitcoind_controller = BitcoindPlainController( + bitcoind_path="tests/bitcoin/bin/bitcoind", rpcport=rpcport + ) # next take the self-installed binary if existing + else: + bitcoind_controller = BitcoindPlainController( + rpcport=rpcport + ) # Alternatively take the one on the path for now + bitcoind_controller.start_bitcoind( + cleanup_at_exit=True, + cleanup_hard=True, + extra_args=extra_args, + log_stdout=log_stdout, + ) + assert not bitcoind_controller.datadir is None + running_version = bitcoind_controller.version() + requested_version = request.config.getoption("--bitcoind-version") + assert running_version == requested_version, ( + "Please make sure that the Bitcoind-version (%s) matches with the version in pytest.ini (%s)" + % (running_version, requested_version) + ) + return bitcoind_controller + + +def instantiate_elementsd_controller(request, rpcport=18643, extra_args=[]): + if os.path.isfile("tests/elements/src/elementsd"): + elementsd_controller = ElementsPlainController( + elementsd_path="tests/elements/src/elementsd", rpcport=rpcport + ) # always prefer the self-compiled bitcoind if existing + elif os.path.isfile("tests/elements/bin/elementsd"): + elementsd_controller = ElementsPlainController( + elementsd_path="tests/elements/bin/elementsd", rpcport=rpcport + ) # next take the self-installed binary if existing + else: + elementsd_controller = ElementsPlainController( + rpcport=rpcport + ) # Alternatively take the one on the path for now + elementsd_controller.start_elementsd( + cleanup_at_exit=True, cleanup_hard=True, extra_args=extra_args + ) + assert not elementsd_controller.datadir is None + running_version = elementsd_controller.version() + requested_version = request.config.getoption("--elementsd-version") + assert running_version == requested_version, ( + "Please make sure that the elementsd-version (%s) matches with the version in pytest.ini (%s)" + % (running_version, requested_version) + ) + return elementsd_controller + + +# Below this point are fixtures. Fixtures have a scope. Check about scopes here: +# https://docs.pytest.org/en/6.2.x/fixture.html#scope-sharing-fixtures-across-classes-modules-packages-or-session +# possible values: function, class, module, package or session. +# The nodes are of scope session. All else is the default (function) + + +@pytest.fixture(scope="session") +def bitcoind_path(): + if os.path.isfile("tests/bitcoin/src/bitcoind"): + return "tests/bitcoin/src/bitcoind" + elif os.path.isfile("tests/bitcoin/bin/bitcoind"): + return "tests/bitcoin/bin/bitcoind" + else: + return which("bitcoind") + + +@pytest.fixture(scope="session") +def bitcoin_regtest(request) -> BitcoindPlainController: + bitcoind_regtest = instantiate_bitcoind_controller(request, extra_args=None) + try: + assert bitcoind_regtest.get_rpc().test_connection() + assert not bitcoind_regtest.datadir is None + assert bitcoind_regtest.datadir is not "" + yield bitcoind_regtest + finally: + bitcoind_regtest.stop_bitcoind() + + +@pytest.fixture(scope="session") +def bitcoin_regtest2(request) -> BitcoindPlainController: + """If a test needs two nodes ...""" + bitcoind_regtest = instantiate_bitcoind_controller( + request, rpcport=18544, extra_args=None + ) + try: + assert bitcoind_regtest.get_rpc().test_connection() + assert not bitcoind_regtest.datadir is None + yield bitcoind_regtest + finally: + bitcoind_regtest.stop_bitcoind() + + +@pytest.fixture +def node(empty_data_folder, bitcoin_regtest): + nodes_folder = empty_data_folder + "/nodes" + if not os.path.isdir(nodes_folder): + os.makedirs(nodes_folder) + nm = NodeManager(data_folder=nodes_folder) + node = nm.add_external_node( + "BTC", + "Standard node", + False, + bitcoin_regtest.datadir, + bitcoin_regtest.rpcconn.rpcuser, + bitcoin_regtest.rpcconn.rpcpassword, + bitcoin_regtest.rpcconn.rpcport, + bitcoin_regtest.rpcconn._ipaddress, + "http", + # "standard_node", + ) + return node + + +@pytest.fixture +def node_with_different_port(empty_data_folder, bitcoin_regtest): + nodes_folder = empty_data_folder + "/nodes" + if not os.path.isdir(nodes_folder): + os.makedirs(nodes_folder) + nm = NodeManager(data_folder=nodes_folder) + node = nm.add_external_node( + "BTC", + "Node with a different port", + False, + "", + bitcoin_regtest.rpcconn.rpcuser, + bitcoin_regtest.rpcconn.rpcpassword, + 18333, + bitcoin_regtest.rpcconn._ipaddress, + "http", + # "satoshis_node", + ) + return node + + +@pytest.fixture +def node_with_empty_datadir(empty_data_folder, bitcoin_regtest): + nodes_folder = empty_data_folder + "/nodes" + if not os.path.isdir(nodes_folder): + os.makedirs(nodes_folder) + node = Node.from_json( + { + "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", + }, + manager=NodeManager(data_folder=nodes_folder), + default_fullpath=os.path.join(nodes_folder, "node_with_empty_datadir.json"), + ) + return node + + +@pytest.fixture(scope="session") +def elements_elreg(request): + elements_elreg = instantiate_elementsd_controller(request, extra_args=None) + try: + yield elements_elreg + assert not elements_elreg.datadir is None + finally: + elements_elreg.stop_elementsd() + + +@pytest.fixture +def empty_data_folder(): + # Make sure that this folder never ever gets a reasonable non-testing use-case + with tempfile.TemporaryDirectory( + prefix="specter_home_tmp_", ignore_cleanup_errors=False + ) as data_folder: + yield data_folder + + +@pytest.fixture +def devices_filled_data_folder(empty_data_folder): + devices_folder = empty_data_folder + "/devices" + if not os.path.isdir(devices_folder): + os.makedirs(devices_folder) + with open(empty_data_folder + "/devices/trezor.json", "w") as text_file: + text_file.write( + """ +{ + "name": "Trezor", + "type": "trezor", + "keys": [ + { + "derivation": "m/49h/0h/0h", + "original": "ypub6XFn7hfb676MLm6ZsAviuQKXeRDNgT9Bs32KpRDPnkKgKDjKcrhYCXJ88aBfy8co2k9eujugJX5nwq7RPG4sj6yncDEPWN9dQGmFWPy4kFB", + "fingerprint": "1ef4e492", + "type": "sh-wpkh", + "xpub": "xpub6CRWp2zfwRYsVTuT2p96hKE2UT4vjq9gwvW732KWQjwoG7v6NCXyaTdz7NE5yDxsd72rAGK7qrjF4YVrfZervsJBjsXxvTL98Yhc7poBk7K" + }, + { + "derivation": "m/84h/0h/0h", + "original": "zpub6rGoJTXEhKw7hUFkjMqNctTzojkzRPa3VFuUWAirqpuj13mRweRmnYpGD1aQVFpxNfp17zVU9r7F6oR3c4zL3DjXHdewVvA7kjugHSqz5au", + "fingerprint": "1ef4e492", + "type": "wpkh", + "xpub": "xpub6CcGh8BQPxr9zssX4eG8CiGzToU6Y9b3f2s2wNw65p9xtr8ySL6eYRVzAbfEVSX7ZPaPd3JMEXQ9LEBvAgAJSkNKYxG6L6X9DHnPWNQud4H" + }, + { + "derivation": "m/48h/0h/0h/1h", + "original": "Ypub6jtWQ1r2D7EwqNoxERU28MWZH4WdL3pWdN8guFJRBTmGwstJGzMXJe1VaNZEuAAVsZwpKPhs5GzNPEZR77mmX1mjwzEiouxmQYsrxFBNVNN", + "fingerprint": "1ef4e492", + "type": "sh-wsh", + "xpub": "xpub6EA9y7SfVU96ZWTTTQDR6C5FPJKvB59RPyxoCb8zRgYzGbWAFvogbTVRkTeBLpHgETm2hL7BjQFKNnL66CCoaHyUFBRtpbgHF6YLyi7fr6m" + }, + { + "derivation": "m/48h/0h/0h/2h", + "original": "Zpub74imhgWwMnnRkSPkiNavCQtSBu1fGo8RP96h9eT2GHCgN5eFU9mZVPhGphvGnG26A1cwJxtkmbHR6nLeTw4okpCDjZCEj2HRLJoVHAEsch9", + "fingerprint": "1ef4e492", + "type": "wsh", + "xpub": "xpub6EA9y7SfVU96dGr96zYgxAMd8AgWBCTqEeQafbPi8VcWdhStCS4AA9X4yb3dE1VM7GKLwRhWy4BpD3VkjK5q1riMAQgz9oBSu8QKv5S7KzD" + }, + { + "derivation": "m/49h/1h/0h", + "original": "upub5EKoQv21nQNkhdt4yuLyRnWitA3EGhW1ru1Y8VTG8gdys2JZhqiYkhn4LHp2heHnH41kz95bXPvrYVRuFUrdUMik6YdjFV4uL4EubnesttQ", + "fingerprint": "1ef4e492", + "type": "sh-wpkh", + "xpub": "tpubDDCDr9rSwixeXKeGwAgwFy8bjBaE5wya9sAVqEC4ccXWmcQxY34KmLRJdwmaDsCnHsu5r9P9SUpYtXmCoRwukWDqmAUJgkBbjC2FXUzicn6" + }, + { + "derivation": "m/84h/1h/0h", + "original": "vpub5Y35MNUT8sUR2SnRCU9A9S6z1JDACMTuNnM8WHXvuS7hCwuVuoRAWJGpi66Yo8evGPiecN26oLqx19xf57mqVQjiYb9hbb4QzbNmFfsS9ko", + "fingerprint": "1ef4e492", + "type": "wpkh", + "xpub": "tpubDC5EUwdy9WWpzqMWKNhVmXdMgMbi4ywxkdysRdNr1MdM4SCfVLbNtsFvzY6WKSuzsaVAitj6FmP6TugPuNT6yKZDLsHrSwMd816TnqX7kuc" + }, + { + "derivation": "m/48h/1h/0h/1h", + "original": "Upub5Tk9tZtdzVaTGWtygRTKDDmaN5vfB59pn2L5MQyH6BkVpg2Y5J95rtpQndjmXNs3LNFiy8zxpHCTtvxxeePjgipF7moTHQZhe3E5uPzDXh8", + "fingerprint": "1ef4e492", + "type": "sh-wsh", + "xpub": "tpubDFiVCZzdarbyfdVoh2LJDL3eVKRPmxwnkiqN8tSYCLod75a2966anQbjHajqVAZ97j54xZJPr9hf7ogVuNL4pPCfwvXdKGDQ9SjZF7vXQu1" + }, + { + "derivation": "m/48h/1h/0h/2h", + "original": "Vpub5naRCEZZ9B7wCKLWuqoNdg6ddWEx8ruztUygXFZDJtW5LRMqUP5HV2TsNw1nc74Ba3QPDSH7qzauZ8LdfNmnmofpfmztCGPgP7vaaYSmpgN", + "fingerprint": "1ef4e492", + "type": "wsh", + "xpub": "tpubDFiVCZzdarbyk8kE65tjRhHCambEo8iTx4xkXL8b33BKZj66HWsDnUb3rg4GZz6Mwm6vTNyzRCjYtiScCQJ77ENedb2deDDtcoNQXiUouJQ" + } + ] +} +""" + ) + with open(empty_data_folder + "/devices/specter.json", "w") as text_file: + text_file.write( + """ +{ + "name": "Specter", + "type": "specter", + "keys": [ + { + "derivation": "m/48h/1h/0h/2h", + "original": "Vpub5n9kKePTPPGtw3RddeJWJe29epEyBBcoHbbPi5HhpoG2kTVsSCUzsad33RJUt3LktEUUPPofcZczuudnwR7ZgkAkT6N2K2Z7wdyjYrVAkXM", + "fingerprint": "08686ac6", + "type": "wsh", + "xpub": "tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL" + }, + { + "derivation": "m/84h/1h/0h", + "original": "vpub5ZSem3mLXiSJzgDX6pJb2N9L6sJ8m6ejaksLPLSuB53LBzCi2mMsBg19eEUSDkHtyYp75GATjLgt5p3S43WjaVCXAWU9q9H5GhkwJBrMiAb", + "fingerprint": "08686ac6", + "type": "wpkh", + "xpub": "tpubDDUotcvrYMUiy4ncDirveTfhmvggdj8nxcW5JgHpGzYz3UVscJY5aEzFvgUPk4YyajadBnsTBmE2YZmAtJC14Q21xncJgVaHQ7UdqMRVRbU" + }, + { + "derivation": "m/84h/1h/1h", + "original": "vpub5ZSem3mLXiSK55jPzfLVhbHbTEwGzEFZv3xrGFCw1vGHSNw7WcVuJXysJLWcgENQd3iXSNQaeSXUBW55Hy4GAjSTjrWP4vpKKkUN9jiU1Tc", + "fingerprint": "08686ac6", + "type": "wpkh", + "xpub": "tpubDDUotcvrYMUj3UJV7ZtqKgoy8JKprrjdHubbBb3r7qmwHsEH69g7h6xyanWaCYdVEEV3Yu7a6s4ceFnp8DjXeeFxY8eXvH7XTAC4gxfDNEW" + }, + { + "derivation": "m/84h/1h/2h", + "original": "vpub5ZSem3mLXiSK64v64deytnDCoYqbUSYHvmVurUGVMEnXMyEybtF3FEnNuiFDDC6J18a81fv5ptQXaQaaRiYx8MRxahipgxPLdxubpYt1dkD", + "fingerprint": "08686ac6", + "type": "wpkh", + "xpub": "tpubDDUotcvrYMUj4TVBBYDKWsjaUcE9M52MJd8emp7QTAJBDTY9BRRFdomVCAFAjWMNcKLe8Cd5HJwg3AJKFyEDcGFTNyryYJgYmNdJMhwB2RG" + }, + { + "derivation": "m/84h/1h/3h", + "original": "vpub5ZSem3mLXiSK8cKzh4sHxTvN7mgYQA29HfoAZeCDtX1M2zdejN5XVAtVyqhk8eui18JTtZ9M3VD3AiWCz8VwrybhBUh3HxzS8js3mLVybDT", + "fingerprint": "08686ac6", + "type": "wpkh", + "xpub": "tpubDDUotcvrYMUj6zu5oyRdaZSjnq56GnWCfXRuUz38zSWztUvpJuFjsjscGHhheyAncK4z15rLVukBdUDwpPBDLtRBykqC9KHeG9akJWRipKK" + } + ] +} +""" + ) + return empty_data_folder # no longer empty, though + + +@pytest.fixture +def wallets_filled_data_folder(devices_filled_data_folder): + simple_json = """ +{ + "alias": "simple", + "fullpath": "/home/kim/.specter/wallets/regtest/simple.json", + "name": "Simple", + "address_index": 0, + "keypool": 5, + "address": "bcrt1qcatuhg0gll3h7py4cmn53rjjn9xlsqfwj3zcej", + "change_index": 0, + "change_address": "bcrt1qt28v03278lmmxllys89acddp2p5y4zds94944n", + "change_keypool": 5, + "type": "simple", + "description": "Single (Segwit)", + "keys": [{ + "derivation": "m/84h/1h/0h", + "original": "vpub5Y35MNUT8sUR2SnRCU9A9S6z1JDACMTuNnM8WHXvuS7hCwuVuoRAWJGpi66Yo8evGPiecN26oLqx19xf57mqVQjiYb9hbb4QzbNmFfsS9ko", + "fingerprint": "1ef4e492", + "type": "wpkh", + "xpub": "tpubDC5EUwdy9WWpzqMWKNhVmXdMgMbi4ywxkdysRdNr1MdM4SCfVLbNtsFvzY6WKSuzsaVAitj6FmP6TugPuNT6yKZDLsHrSwMd816TnqX7kuc" + }], + "recv_descriptor": "wpkh([1ef4e492/84h/1h/0h]tpubDC5EUwdy9WWpzqMWKNhVmXdMgMbi4ywxkdysRdNr1MdM4SCfVLbNtsFvzY6WKSuzsaVAitj6FmP6TugPuNT6yKZDLsHrSwMd816TnqX7kuc/0/*)#xp8lv5nr", + "change_descriptor": "wpkh([1ef4e492/84h/1h/0h]tpubDC5EUwdy9WWpzqMWKNhVmXdMgMbi4ywxkdysRdNr1MdM4SCfVLbNtsFvzY6WKSuzsaVAitj6FmP6TugPuNT6yKZDLsHrSwMd816TnqX7kuc/1/*)#h4z73prm", + "device": "Trezor", + "device_type": "trezor", + "address_type": "bech32" +} +""" + another_wallet_json = """ +{ + "name": "sdsd", + "alias": "sdsd", + "description": "Single (Segwit)", + "address_type": "bech32", + "address": "bcrt1q4h86vfanswhsle63hw2muv9h5a45cg2878uez5", + "address_index": 0, + "change_address": "bcrt1qxsj28ddr95xvp7xjyzkkfq6qknrn4kap30zkut", + "change_index": 0, + "keypool": 60, + "change_keypool": 20, + "recv_descriptor": "wpkh([41490ec7/84h/1h/0h]tpubDCTPz7KwyetfhQNMSWiK34pPR2zSTsTybrMPgRVAzouNLqtgsv51o81KjccmTbjkWJ8mVhRJM1LxZD6AfRH2635tHpHeCAKW446iwADNv7C/0/*)#rn833s5g", + "change_descriptor": "wpkh([41490ec7/84h/1h/0h]tpubDCTPz7KwyetfhQNMSWiK34pPR2zSTsTybrMPgRVAzouNLqtgsv51o81KjccmTbjkWJ8mVhRJM1LxZD6AfRH2635tHpHeCAKW446iwADNv7C/1/*)#j8zsv9ys", + "keys": [ + { + "original": "vpub5YRErYARy1rFj1oGKc9yQyJ1jybtbEyvDziem5eFttPiVMbXJNtoQZ2DTAcowHUfu7NFPAiJtaop6TNRqAbkc8GPVY9VLp2HveP2PygjuYh", + "fingerprint": "41490ec7", + "derivation": "m/84h/1h/0h", + "type": "wpkh", + "purpose": "#0 Single Sig (Segwit)", + "xpub": "tpubDCTPz7KwyetfhQNMSWiK34pPR2zSTsTybrMPgRVAzouNLqtgsv51o81KjccmTbjkWJ8mVhRJM1LxZD6AfRH2635tHpHeCAKW446iwADNv7C" + } + ], + "devices": [ + "dsds" + ], + "sigs_required": 1, + "blockheight": 0, + "pending_psbts": {}, + "frozen_utxo": [], + "last_block": "187e2db380eb6d901efd87188f00c7074506c9c3813b8ecec7300ecc4e55eb46" +} +""" + + os.makedirs(os.path.join(devices_filled_data_folder, "wallets", "regtest")) + with open( + os.path.join(devices_filled_data_folder, "wallets", "regtest", "simple.json"), + "w", + ) as json_file: + json_file.write(simple_json) + os.makedirs(os.path.join(devices_filled_data_folder, "wallets_someuser", "regtest")) + with open( + os.path.join( + devices_filled_data_folder, "wallets_someuser", "regtest", "simple.json" + ), + "w", + ) as json_file: + json_file.write(another_wallet_json) + return devices_filled_data_folder # and with wallets obviously + + +@pytest.fixture +def device_manager(devices_filled_data_folder): + return DeviceManager(os.path.join(devices_filled_data_folder, "devices")) + + +def create_specter_with_node_and_user( + bitcoin_regtest, devices_filled_data_folder, node, allow_threading_for_testing +): + assert bitcoin_regtest.get_rpc().test_connection() + config = { + "auth": { + "method": "rpcpasswordaspin", + }, + "testing": { + "allow_threading_for_testing": allow_threading_for_testing, + }, + } + specter = Specter( + data_folder=devices_filled_data_folder, config=config, checker_threads=False + ) + node = specter.node_manager.add_external_node( + "BTC", + "Node with a different port", + False, + "", + bitcoin_regtest.rpcconn.rpcuser, + bitcoin_regtest.rpcconn.rpcpassword, + bitcoin_regtest.rpcconn.rpcport, + bitcoin_regtest.rpcconn._ipaddress, + "http", + ) + assert specter.chain == "regtest" + + # Create a User + someuser = specter.user_manager.add_user( + User.from_json( + user_dict={ + "id": "someuser", + "username": "someuser", + "password": hash_password("somepassword"), + "config": {}, + "is_admin": False, + "services": None, + }, + specter=specter, + ) + ) + specter.user_manager.save() + specter.check() + + assert not specter.wallet_manager.working_folder is None + + # "rpc": { + # "autodetect": False, + # "datadir": bitcoin_regtest.datadir, + # "user": bitcoin_regtest.rpcconn.rpcuser, + # "password": bitcoin_regtest.rpcconn.rpcpassword, + # "port": bitcoin_regtest.rpcconn.rpcport, + # "host": bitcoin_regtest.rpcconn.ipaddress, + # "protocol": "http", + + +# @pytest.fixture +# def user_manager(empty_data_folder) -> UserManager: +# """A UserManager having users alice, bob and eve""" +# specter = Specter(data_folder=empty_data_folder) +# user_manager = UserManager(specter=specter) +# config = {} +# user_manager.get_user("admin").decrypt_user_secret("admin") +# user_manager.create_user( +# user_id="alice", +# username="alice", +# plaintext_password="plain_pass_alice", +# config=config, +# ) +# user_manager.create_user( +# user_id="bob", +# username="bob", +# plaintext_password="plain_pass_bob", +# config=config, +# ) +# user_manager.create_user( +# user_id="eve", +# username="eve", +# plaintext_password="plain_pass_eve", +# config=config, +# ) +# return user_manager + + +@pytest.fixture +def specter_regtest_configured(bitcoin_regtest, devices_filled_data_folder, node): + specter = create_specter_with_node_and_user( + bitcoin_regtest, devices_filled_data_folder, node, False + ) + try: + yield specter + finally: + # End all threads + # 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, node) + + +@pytest.fixture +def specter_regtest_configured_with_threading( + bitcoin_regtest, devices_filled_data_folder, node +): + assert bitcoin_regtest.get_rpc().test_connection() + config = { + "rpc": { + "autodetect": False, + "datadir": bitcoin_regtest.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", + }, + "testing": { + "allow_threading_for_testing": True, + }, + } + 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": "someuser", + "password": hash_password("somepassword"), + "config": {}, + "is_admin": False, + "services": None, + }, + specter=specter, + ) + ) + specter.user_manager.save() + specter.check() + + 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, node) + + +def specter_app_with_config(config={}, specter=None): + """helper-function to create SpecterFlasks""" + if isinstance(config, dict): + tempClass = type("tempClass", (TestConfig,), {}) + for key, value in config.items(): + setattr(tempClass, key, value) + # service_manager will expect the class to be defined as a direct property of the module: + if hasattr(sys.modules[__name__], "tempClass"): + delattr(sys.modules[__name__], "tempClass") + assert not hasattr(sys.modules[__name__], "tempClass") + setattr(sys.modules[__name__], "tempClass", tempClass) + assert hasattr(sys.modules[__name__], "tempClass") + assert getattr(sys.modules[__name__], "tempClass") == tempClass + config = tempClass + app = create_app(config=config) + 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 + + +@pytest.fixture +def app(specter_regtest_configured) -> SpecterFlask: + """the Flask-App, but uninitialized""" + return specter_app_with_config( + config="cryptoadvance.specter.config.TestConfig", + specter=specter_regtest_configured, + ) + + +@pytest.fixture +def app_no_node(empty_data_folder) -> SpecterFlask: + specter = Specter(data_folder=empty_data_folder, checker_threads=False) + 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 + + +@pytest.fixture +def client(app): + """a test_client from an initialized Flask-App""" + return app.test_client() From caf2f401b8627998cd9836220ee9caffe0bb19a5 Mon Sep 17 00:00:00 2001 From: Kim Neunert Date: Thu, 26 Jan 2023 15:00:33 +0100 Subject: [PATCH 3/5] removed Microsoft link tracking --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index ccb112863e..a633ef7dfd 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,7 +1,7 @@ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + // For more information, visit: https://code.visualstudio.com/docs/editor/debugging#_launch-configurations "version": "0.2.0", "configurations": [ { From 557cec2792e36b02f9fd019195d9ba5c719c09f6 Mon Sep 17 00:00:00 2001 From: Kim Neunert Date: Fri, 27 Jan 2023 13:15:43 +0100 Subject: [PATCH 4/5] wrap up a failed update properly --- tests/conftest2.py | 695 --------------------------------------------- 1 file changed, 695 deletions(-) delete mode 100644 tests/conftest2.py diff --git a/tests/conftest2.py b/tests/conftest2.py deleted file mode 100644 index f1f917d1fc..0000000000 --- a/tests/conftest2.py +++ /dev/null @@ -1,695 +0,0 @@ -import atexit -import code -import json -import logging -import os -import signal -import sys -import tempfile -import traceback - -import pytest -from cryptoadvance.specter.config import TestConfig -from cryptoadvance.specter.node import Node -from cryptoadvance.specter.managers.device_manager import DeviceManager -from cryptoadvance.specter.managers.node_manager import NodeManager -from cryptoadvance.specter.managers.user_manager import UserManager -from cryptoadvance.specter.process_controller.bitcoind_controller import ( - BitcoindPlainController, -) -from cryptoadvance.specter.process_controller.elementsd_controller import ( - ElementsPlainController, -) -from cryptoadvance.specter.server import SpecterFlask, create_app, init_app -from cryptoadvance.specter.specter import Specter -from cryptoadvance.specter.specter_error import ( - BrokenCoreConnectionException, - SpecterError, - handle_exception, -) -from cryptoadvance.specter.user import User, hash_password -from cryptoadvance.specter.util.common import str2bool -from cryptoadvance.specter.util.shell import which -from cryptoadvance.specter.util.wallet_importer import WalletImporter - -logger = logging.getLogger(__name__) - -pytest_plugins = [ - "conftest_visibility", - "fix_ghost_machine", - "fix_keys_and_seeds", - "fix_devices_and_wallets", - "fix_testnet", -] - -# This is from https://stackoverflow.com/questions/132058/showing-the-stack-trace-from-a-running-python-application -# it enables stopping a hanging test via sending the pytest-process a SIGUSR2 (12) -# kill 12 pid-of-pytest -# In the article they claim to open a debug-console which didn't work for me but at least -# you get a stacktrace in the output. -def debug(sig, frame): - """Interrupt running process, and provide a python prompt for - interactive debugging.""" - d = {"_frame": frame} # Allow access to frame object. - d.update(frame.f_globals) # Unless shadowed by global - d.update(frame.f_locals) - - i = code.InteractiveConsole(d) - message = "Signal received : entering python shell.\nTraceback:\n" - message += "".join(traceback.format_stack(frame)) - i.interact(message) - - -def listen(): - signal.signal(signal.SIGUSR2, debug) # Register handler - - -def pytest_addoption(parser): - """Internally called to add options to pytest - see pytest_generate_tests(metafunc) on how to check that - Also used to register the SIGUSR2 (12) as decribed in conftest.py - """ - parser.addoption( - "--bitcoind-version", - action="store", - default="v0.20.1", - help="Version of bitcoind (something which works with git checkout ...)", - ) - parser.addoption( - "--bitcoind-log-stdout", - action="store", - default=False, - help="Whether bitcoind should log to stdout (default:False)", - ) - parser.addoption( - "--elementsd-version", - action="store", - default="master", - help="Version of elementsd (something which works with git checkout ...)", - ) - listen() - - -def pytest_generate_tests(metafunc): - # ToDo: use custom compiled version of bitcoind - # E.g. test again bitcoind version [currentRelease] + master-branch - if "docker" in metafunc.fixturenames: - if metafunc.config.getoption("docker"): - # That's a list because we could do both (see above) but currently that doesn't make sense in that context - metafunc.parametrize("docker", [True], scope="session") - else: - metafunc.parametrize("docker", [False], scope="session") - - -def instantiate_bitcoind_controller( - request, rpcport=18543, extra_args=[] -) -> BitcoindPlainController: - # logging.getLogger().setLevel(logging.DEBUG) - requested_version = request.config.getoption("--bitcoind-version") - log_stdout = str2bool(request.config.getoption("--bitcoind-log-stdout")) - if os.path.isfile("tests/bitcoin/src/bitcoind"): - bitcoind_controller = BitcoindPlainController( - bitcoind_path="tests/bitcoin/src/bitcoind", rpcport=rpcport - ) # always prefer the self-compiled bitcoind if existing - elif os.path.isfile("tests/bitcoin/bin/bitcoind"): - bitcoind_controller = BitcoindPlainController( - bitcoind_path="tests/bitcoin/bin/bitcoind", rpcport=rpcport - ) # next take the self-installed binary if existing - else: - bitcoind_controller = BitcoindPlainController( - rpcport=rpcport - ) # Alternatively take the one on the path for now - bitcoind_controller.start_bitcoind( - cleanup_at_exit=True, - cleanup_hard=True, - extra_args=extra_args, - log_stdout=log_stdout, - ) - assert not bitcoind_controller.datadir is None - running_version = bitcoind_controller.version() - requested_version = request.config.getoption("--bitcoind-version") - assert running_version == requested_version, ( - "Please make sure that the Bitcoind-version (%s) matches with the version in pytest.ini (%s)" - % (running_version, requested_version) - ) - return bitcoind_controller - - -def instantiate_elementsd_controller(request, rpcport=18643, extra_args=[]): - if os.path.isfile("tests/elements/src/elementsd"): - elementsd_controller = ElementsPlainController( - elementsd_path="tests/elements/src/elementsd", rpcport=rpcport - ) # always prefer the self-compiled bitcoind if existing - elif os.path.isfile("tests/elements/bin/elementsd"): - elementsd_controller = ElementsPlainController( - elementsd_path="tests/elements/bin/elementsd", rpcport=rpcport - ) # next take the self-installed binary if existing - else: - elementsd_controller = ElementsPlainController( - rpcport=rpcport - ) # Alternatively take the one on the path for now - elementsd_controller.start_elementsd( - cleanup_at_exit=True, cleanup_hard=True, extra_args=extra_args - ) - assert not elementsd_controller.datadir is None - running_version = elementsd_controller.version() - requested_version = request.config.getoption("--elementsd-version") - assert running_version == requested_version, ( - "Please make sure that the elementsd-version (%s) matches with the version in pytest.ini (%s)" - % (running_version, requested_version) - ) - return elementsd_controller - - -# Below this point are fixtures. Fixtures have a scope. Check about scopes here: -# https://docs.pytest.org/en/6.2.x/fixture.html#scope-sharing-fixtures-across-classes-modules-packages-or-session -# possible values: function, class, module, package or session. -# The nodes are of scope session. All else is the default (function) - - -@pytest.fixture(scope="session") -def bitcoind_path(): - if os.path.isfile("tests/bitcoin/src/bitcoind"): - return "tests/bitcoin/src/bitcoind" - elif os.path.isfile("tests/bitcoin/bin/bitcoind"): - return "tests/bitcoin/bin/bitcoind" - else: - return which("bitcoind") - - -@pytest.fixture(scope="session") -def bitcoin_regtest(request) -> BitcoindPlainController: - bitcoind_regtest = instantiate_bitcoind_controller(request, extra_args=None) - try: - assert bitcoind_regtest.get_rpc().test_connection() - assert not bitcoind_regtest.datadir is None - assert bitcoind_regtest.datadir is not "" - yield bitcoind_regtest - finally: - bitcoind_regtest.stop_bitcoind() - - -@pytest.fixture(scope="session") -def bitcoin_regtest2(request) -> BitcoindPlainController: - """If a test needs two nodes ...""" - bitcoind_regtest = instantiate_bitcoind_controller( - request, rpcport=18544, extra_args=None - ) - try: - assert bitcoind_regtest.get_rpc().test_connection() - assert not bitcoind_regtest.datadir is None - yield bitcoind_regtest - finally: - bitcoind_regtest.stop_bitcoind() - - -@pytest.fixture -def node(empty_data_folder, bitcoin_regtest): - nodes_folder = empty_data_folder + "/nodes" - if not os.path.isdir(nodes_folder): - os.makedirs(nodes_folder) - nm = NodeManager(data_folder=nodes_folder) - node = nm.add_external_node( - "BTC", - "Standard node", - False, - bitcoin_regtest.datadir, - bitcoin_regtest.rpcconn.rpcuser, - bitcoin_regtest.rpcconn.rpcpassword, - bitcoin_regtest.rpcconn.rpcport, - bitcoin_regtest.rpcconn._ipaddress, - "http", - # "standard_node", - ) - return node - - -@pytest.fixture -def node_with_different_port(empty_data_folder, bitcoin_regtest): - nodes_folder = empty_data_folder + "/nodes" - if not os.path.isdir(nodes_folder): - os.makedirs(nodes_folder) - nm = NodeManager(data_folder=nodes_folder) - node = nm.add_external_node( - "BTC", - "Node with a different port", - False, - "", - bitcoin_regtest.rpcconn.rpcuser, - bitcoin_regtest.rpcconn.rpcpassword, - 18333, - bitcoin_regtest.rpcconn._ipaddress, - "http", - # "satoshis_node", - ) - return node - - -@pytest.fixture -def node_with_empty_datadir(empty_data_folder, bitcoin_regtest): - nodes_folder = empty_data_folder + "/nodes" - if not os.path.isdir(nodes_folder): - os.makedirs(nodes_folder) - node = Node.from_json( - { - "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", - }, - manager=NodeManager(data_folder=nodes_folder), - default_fullpath=os.path.join(nodes_folder, "node_with_empty_datadir.json"), - ) - return node - - -@pytest.fixture(scope="session") -def elements_elreg(request): - elements_elreg = instantiate_elementsd_controller(request, extra_args=None) - try: - yield elements_elreg - assert not elements_elreg.datadir is None - finally: - elements_elreg.stop_elementsd() - - -@pytest.fixture -def empty_data_folder(): - # Make sure that this folder never ever gets a reasonable non-testing use-case - with tempfile.TemporaryDirectory( - prefix="specter_home_tmp_", ignore_cleanup_errors=False - ) as data_folder: - yield data_folder - - -@pytest.fixture -def devices_filled_data_folder(empty_data_folder): - devices_folder = empty_data_folder + "/devices" - if not os.path.isdir(devices_folder): - os.makedirs(devices_folder) - with open(empty_data_folder + "/devices/trezor.json", "w") as text_file: - text_file.write( - """ -{ - "name": "Trezor", - "type": "trezor", - "keys": [ - { - "derivation": "m/49h/0h/0h", - "original": "ypub6XFn7hfb676MLm6ZsAviuQKXeRDNgT9Bs32KpRDPnkKgKDjKcrhYCXJ88aBfy8co2k9eujugJX5nwq7RPG4sj6yncDEPWN9dQGmFWPy4kFB", - "fingerprint": "1ef4e492", - "type": "sh-wpkh", - "xpub": "xpub6CRWp2zfwRYsVTuT2p96hKE2UT4vjq9gwvW732KWQjwoG7v6NCXyaTdz7NE5yDxsd72rAGK7qrjF4YVrfZervsJBjsXxvTL98Yhc7poBk7K" - }, - { - "derivation": "m/84h/0h/0h", - "original": "zpub6rGoJTXEhKw7hUFkjMqNctTzojkzRPa3VFuUWAirqpuj13mRweRmnYpGD1aQVFpxNfp17zVU9r7F6oR3c4zL3DjXHdewVvA7kjugHSqz5au", - "fingerprint": "1ef4e492", - "type": "wpkh", - "xpub": "xpub6CcGh8BQPxr9zssX4eG8CiGzToU6Y9b3f2s2wNw65p9xtr8ySL6eYRVzAbfEVSX7ZPaPd3JMEXQ9LEBvAgAJSkNKYxG6L6X9DHnPWNQud4H" - }, - { - "derivation": "m/48h/0h/0h/1h", - "original": "Ypub6jtWQ1r2D7EwqNoxERU28MWZH4WdL3pWdN8guFJRBTmGwstJGzMXJe1VaNZEuAAVsZwpKPhs5GzNPEZR77mmX1mjwzEiouxmQYsrxFBNVNN", - "fingerprint": "1ef4e492", - "type": "sh-wsh", - "xpub": "xpub6EA9y7SfVU96ZWTTTQDR6C5FPJKvB59RPyxoCb8zRgYzGbWAFvogbTVRkTeBLpHgETm2hL7BjQFKNnL66CCoaHyUFBRtpbgHF6YLyi7fr6m" - }, - { - "derivation": "m/48h/0h/0h/2h", - "original": "Zpub74imhgWwMnnRkSPkiNavCQtSBu1fGo8RP96h9eT2GHCgN5eFU9mZVPhGphvGnG26A1cwJxtkmbHR6nLeTw4okpCDjZCEj2HRLJoVHAEsch9", - "fingerprint": "1ef4e492", - "type": "wsh", - "xpub": "xpub6EA9y7SfVU96dGr96zYgxAMd8AgWBCTqEeQafbPi8VcWdhStCS4AA9X4yb3dE1VM7GKLwRhWy4BpD3VkjK5q1riMAQgz9oBSu8QKv5S7KzD" - }, - { - "derivation": "m/49h/1h/0h", - "original": "upub5EKoQv21nQNkhdt4yuLyRnWitA3EGhW1ru1Y8VTG8gdys2JZhqiYkhn4LHp2heHnH41kz95bXPvrYVRuFUrdUMik6YdjFV4uL4EubnesttQ", - "fingerprint": "1ef4e492", - "type": "sh-wpkh", - "xpub": "tpubDDCDr9rSwixeXKeGwAgwFy8bjBaE5wya9sAVqEC4ccXWmcQxY34KmLRJdwmaDsCnHsu5r9P9SUpYtXmCoRwukWDqmAUJgkBbjC2FXUzicn6" - }, - { - "derivation": "m/84h/1h/0h", - "original": "vpub5Y35MNUT8sUR2SnRCU9A9S6z1JDACMTuNnM8WHXvuS7hCwuVuoRAWJGpi66Yo8evGPiecN26oLqx19xf57mqVQjiYb9hbb4QzbNmFfsS9ko", - "fingerprint": "1ef4e492", - "type": "wpkh", - "xpub": "tpubDC5EUwdy9WWpzqMWKNhVmXdMgMbi4ywxkdysRdNr1MdM4SCfVLbNtsFvzY6WKSuzsaVAitj6FmP6TugPuNT6yKZDLsHrSwMd816TnqX7kuc" - }, - { - "derivation": "m/48h/1h/0h/1h", - "original": "Upub5Tk9tZtdzVaTGWtygRTKDDmaN5vfB59pn2L5MQyH6BkVpg2Y5J95rtpQndjmXNs3LNFiy8zxpHCTtvxxeePjgipF7moTHQZhe3E5uPzDXh8", - "fingerprint": "1ef4e492", - "type": "sh-wsh", - "xpub": "tpubDFiVCZzdarbyfdVoh2LJDL3eVKRPmxwnkiqN8tSYCLod75a2966anQbjHajqVAZ97j54xZJPr9hf7ogVuNL4pPCfwvXdKGDQ9SjZF7vXQu1" - }, - { - "derivation": "m/48h/1h/0h/2h", - "original": "Vpub5naRCEZZ9B7wCKLWuqoNdg6ddWEx8ruztUygXFZDJtW5LRMqUP5HV2TsNw1nc74Ba3QPDSH7qzauZ8LdfNmnmofpfmztCGPgP7vaaYSmpgN", - "fingerprint": "1ef4e492", - "type": "wsh", - "xpub": "tpubDFiVCZzdarbyk8kE65tjRhHCambEo8iTx4xkXL8b33BKZj66HWsDnUb3rg4GZz6Mwm6vTNyzRCjYtiScCQJ77ENedb2deDDtcoNQXiUouJQ" - } - ] -} -""" - ) - with open(empty_data_folder + "/devices/specter.json", "w") as text_file: - text_file.write( - """ -{ - "name": "Specter", - "type": "specter", - "keys": [ - { - "derivation": "m/48h/1h/0h/2h", - "original": "Vpub5n9kKePTPPGtw3RddeJWJe29epEyBBcoHbbPi5HhpoG2kTVsSCUzsad33RJUt3LktEUUPPofcZczuudnwR7ZgkAkT6N2K2Z7wdyjYrVAkXM", - "fingerprint": "08686ac6", - "type": "wsh", - "xpub": "tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL" - }, - { - "derivation": "m/84h/1h/0h", - "original": "vpub5ZSem3mLXiSJzgDX6pJb2N9L6sJ8m6ejaksLPLSuB53LBzCi2mMsBg19eEUSDkHtyYp75GATjLgt5p3S43WjaVCXAWU9q9H5GhkwJBrMiAb", - "fingerprint": "08686ac6", - "type": "wpkh", - "xpub": "tpubDDUotcvrYMUiy4ncDirveTfhmvggdj8nxcW5JgHpGzYz3UVscJY5aEzFvgUPk4YyajadBnsTBmE2YZmAtJC14Q21xncJgVaHQ7UdqMRVRbU" - }, - { - "derivation": "m/84h/1h/1h", - "original": "vpub5ZSem3mLXiSK55jPzfLVhbHbTEwGzEFZv3xrGFCw1vGHSNw7WcVuJXysJLWcgENQd3iXSNQaeSXUBW55Hy4GAjSTjrWP4vpKKkUN9jiU1Tc", - "fingerprint": "08686ac6", - "type": "wpkh", - "xpub": "tpubDDUotcvrYMUj3UJV7ZtqKgoy8JKprrjdHubbBb3r7qmwHsEH69g7h6xyanWaCYdVEEV3Yu7a6s4ceFnp8DjXeeFxY8eXvH7XTAC4gxfDNEW" - }, - { - "derivation": "m/84h/1h/2h", - "original": "vpub5ZSem3mLXiSK64v64deytnDCoYqbUSYHvmVurUGVMEnXMyEybtF3FEnNuiFDDC6J18a81fv5ptQXaQaaRiYx8MRxahipgxPLdxubpYt1dkD", - "fingerprint": "08686ac6", - "type": "wpkh", - "xpub": "tpubDDUotcvrYMUj4TVBBYDKWsjaUcE9M52MJd8emp7QTAJBDTY9BRRFdomVCAFAjWMNcKLe8Cd5HJwg3AJKFyEDcGFTNyryYJgYmNdJMhwB2RG" - }, - { - "derivation": "m/84h/1h/3h", - "original": "vpub5ZSem3mLXiSK8cKzh4sHxTvN7mgYQA29HfoAZeCDtX1M2zdejN5XVAtVyqhk8eui18JTtZ9M3VD3AiWCz8VwrybhBUh3HxzS8js3mLVybDT", - "fingerprint": "08686ac6", - "type": "wpkh", - "xpub": "tpubDDUotcvrYMUj6zu5oyRdaZSjnq56GnWCfXRuUz38zSWztUvpJuFjsjscGHhheyAncK4z15rLVukBdUDwpPBDLtRBykqC9KHeG9akJWRipKK" - } - ] -} -""" - ) - return empty_data_folder # no longer empty, though - - -@pytest.fixture -def wallets_filled_data_folder(devices_filled_data_folder): - simple_json = """ -{ - "alias": "simple", - "fullpath": "/home/kim/.specter/wallets/regtest/simple.json", - "name": "Simple", - "address_index": 0, - "keypool": 5, - "address": "bcrt1qcatuhg0gll3h7py4cmn53rjjn9xlsqfwj3zcej", - "change_index": 0, - "change_address": "bcrt1qt28v03278lmmxllys89acddp2p5y4zds94944n", - "change_keypool": 5, - "type": "simple", - "description": "Single (Segwit)", - "keys": [{ - "derivation": "m/84h/1h/0h", - "original": "vpub5Y35MNUT8sUR2SnRCU9A9S6z1JDACMTuNnM8WHXvuS7hCwuVuoRAWJGpi66Yo8evGPiecN26oLqx19xf57mqVQjiYb9hbb4QzbNmFfsS9ko", - "fingerprint": "1ef4e492", - "type": "wpkh", - "xpub": "tpubDC5EUwdy9WWpzqMWKNhVmXdMgMbi4ywxkdysRdNr1MdM4SCfVLbNtsFvzY6WKSuzsaVAitj6FmP6TugPuNT6yKZDLsHrSwMd816TnqX7kuc" - }], - "recv_descriptor": "wpkh([1ef4e492/84h/1h/0h]tpubDC5EUwdy9WWpzqMWKNhVmXdMgMbi4ywxkdysRdNr1MdM4SCfVLbNtsFvzY6WKSuzsaVAitj6FmP6TugPuNT6yKZDLsHrSwMd816TnqX7kuc/0/*)#xp8lv5nr", - "change_descriptor": "wpkh([1ef4e492/84h/1h/0h]tpubDC5EUwdy9WWpzqMWKNhVmXdMgMbi4ywxkdysRdNr1MdM4SCfVLbNtsFvzY6WKSuzsaVAitj6FmP6TugPuNT6yKZDLsHrSwMd816TnqX7kuc/1/*)#h4z73prm", - "device": "Trezor", - "device_type": "trezor", - "address_type": "bech32" -} -""" - another_wallet_json = """ -{ - "name": "sdsd", - "alias": "sdsd", - "description": "Single (Segwit)", - "address_type": "bech32", - "address": "bcrt1q4h86vfanswhsle63hw2muv9h5a45cg2878uez5", - "address_index": 0, - "change_address": "bcrt1qxsj28ddr95xvp7xjyzkkfq6qknrn4kap30zkut", - "change_index": 0, - "keypool": 60, - "change_keypool": 20, - "recv_descriptor": "wpkh([41490ec7/84h/1h/0h]tpubDCTPz7KwyetfhQNMSWiK34pPR2zSTsTybrMPgRVAzouNLqtgsv51o81KjccmTbjkWJ8mVhRJM1LxZD6AfRH2635tHpHeCAKW446iwADNv7C/0/*)#rn833s5g", - "change_descriptor": "wpkh([41490ec7/84h/1h/0h]tpubDCTPz7KwyetfhQNMSWiK34pPR2zSTsTybrMPgRVAzouNLqtgsv51o81KjccmTbjkWJ8mVhRJM1LxZD6AfRH2635tHpHeCAKW446iwADNv7C/1/*)#j8zsv9ys", - "keys": [ - { - "original": "vpub5YRErYARy1rFj1oGKc9yQyJ1jybtbEyvDziem5eFttPiVMbXJNtoQZ2DTAcowHUfu7NFPAiJtaop6TNRqAbkc8GPVY9VLp2HveP2PygjuYh", - "fingerprint": "41490ec7", - "derivation": "m/84h/1h/0h", - "type": "wpkh", - "purpose": "#0 Single Sig (Segwit)", - "xpub": "tpubDCTPz7KwyetfhQNMSWiK34pPR2zSTsTybrMPgRVAzouNLqtgsv51o81KjccmTbjkWJ8mVhRJM1LxZD6AfRH2635tHpHeCAKW446iwADNv7C" - } - ], - "devices": [ - "dsds" - ], - "sigs_required": 1, - "blockheight": 0, - "pending_psbts": {}, - "frozen_utxo": [], - "last_block": "187e2db380eb6d901efd87188f00c7074506c9c3813b8ecec7300ecc4e55eb46" -} -""" - - os.makedirs(os.path.join(devices_filled_data_folder, "wallets", "regtest")) - with open( - os.path.join(devices_filled_data_folder, "wallets", "regtest", "simple.json"), - "w", - ) as json_file: - json_file.write(simple_json) - os.makedirs(os.path.join(devices_filled_data_folder, "wallets_someuser", "regtest")) - with open( - os.path.join( - devices_filled_data_folder, "wallets_someuser", "regtest", "simple.json" - ), - "w", - ) as json_file: - json_file.write(another_wallet_json) - return devices_filled_data_folder # and with wallets obviously - - -@pytest.fixture -def device_manager(devices_filled_data_folder): - return DeviceManager(os.path.join(devices_filled_data_folder, "devices")) - - -def create_specter_with_node_and_user( - bitcoin_regtest, devices_filled_data_folder, node, allow_threading_for_testing -): - assert bitcoin_regtest.get_rpc().test_connection() - config = { - "auth": { - "method": "rpcpasswordaspin", - }, - "testing": { - "allow_threading_for_testing": allow_threading_for_testing, - }, - } - specter = Specter( - data_folder=devices_filled_data_folder, config=config, checker_threads=False - ) - node = specter.node_manager.add_external_node( - "BTC", - "Node with a different port", - False, - "", - bitcoin_regtest.rpcconn.rpcuser, - bitcoin_regtest.rpcconn.rpcpassword, - bitcoin_regtest.rpcconn.rpcport, - bitcoin_regtest.rpcconn._ipaddress, - "http", - ) - assert specter.chain == "regtest" - - # Create a User - someuser = specter.user_manager.add_user( - User.from_json( - user_dict={ - "id": "someuser", - "username": "someuser", - "password": hash_password("somepassword"), - "config": {}, - "is_admin": False, - "services": None, - }, - specter=specter, - ) - ) - specter.user_manager.save() - specter.check() - - assert not specter.wallet_manager.working_folder is None - - # "rpc": { - # "autodetect": False, - # "datadir": bitcoin_regtest.datadir, - # "user": bitcoin_regtest.rpcconn.rpcuser, - # "password": bitcoin_regtest.rpcconn.rpcpassword, - # "port": bitcoin_regtest.rpcconn.rpcport, - # "host": bitcoin_regtest.rpcconn.ipaddress, - # "protocol": "http", - - -# @pytest.fixture -# def user_manager(empty_data_folder) -> UserManager: -# """A UserManager having users alice, bob and eve""" -# specter = Specter(data_folder=empty_data_folder) -# user_manager = UserManager(specter=specter) -# config = {} -# user_manager.get_user("admin").decrypt_user_secret("admin") -# user_manager.create_user( -# user_id="alice", -# username="alice", -# plaintext_password="plain_pass_alice", -# config=config, -# ) -# user_manager.create_user( -# user_id="bob", -# username="bob", -# plaintext_password="plain_pass_bob", -# config=config, -# ) -# user_manager.create_user( -# user_id="eve", -# username="eve", -# plaintext_password="plain_pass_eve", -# config=config, -# ) -# return user_manager - - -@pytest.fixture -def specter_regtest_configured(bitcoin_regtest, devices_filled_data_folder, node): - specter = create_specter_with_node_and_user( - bitcoin_regtest, devices_filled_data_folder, node, False - ) - try: - yield specter - finally: - # End all threads - # 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, node) - - -@pytest.fixture -def specter_regtest_configured_with_threading( - bitcoin_regtest, devices_filled_data_folder, node -): - assert bitcoin_regtest.get_rpc().test_connection() - config = { - "rpc": { - "autodetect": False, - "datadir": bitcoin_regtest.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", - }, - "testing": { - "allow_threading_for_testing": True, - }, - } - 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": "someuser", - "password": hash_password("somepassword"), - "config": {}, - "is_admin": False, - "services": None, - }, - specter=specter, - ) - ) - specter.user_manager.save() - specter.check() - - 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, node) - - -def specter_app_with_config(config={}, specter=None): - """helper-function to create SpecterFlasks""" - if isinstance(config, dict): - tempClass = type("tempClass", (TestConfig,), {}) - for key, value in config.items(): - setattr(tempClass, key, value) - # service_manager will expect the class to be defined as a direct property of the module: - if hasattr(sys.modules[__name__], "tempClass"): - delattr(sys.modules[__name__], "tempClass") - assert not hasattr(sys.modules[__name__], "tempClass") - setattr(sys.modules[__name__], "tempClass", tempClass) - assert hasattr(sys.modules[__name__], "tempClass") - assert getattr(sys.modules[__name__], "tempClass") == tempClass - config = tempClass - app = create_app(config=config) - 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 - - -@pytest.fixture -def app(specter_regtest_configured) -> SpecterFlask: - """the Flask-App, but uninitialized""" - return specter_app_with_config( - config="cryptoadvance.specter.config.TestConfig", - specter=specter_regtest_configured, - ) - - -@pytest.fixture -def app_no_node(empty_data_folder) -> SpecterFlask: - specter = Specter(data_folder=empty_data_folder, checker_threads=False) - 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 - - -@pytest.fixture -def client(app): - """a test_client from an initialized Flask-App""" - return app.test_client() From 1da71163fbb7e2396bc0d00b410c2fa184916162 Mon Sep 17 00:00:00 2001 From: Kim Neunert Date: Fri, 27 Jan 2023 13:26:24 +0100 Subject: [PATCH 5/5] better decode and better error_handling --- .../specter/managers/wallet_manager.py | 13 +++----- src/cryptoadvance/specter/txlist.py | 31 ++++++++++++------- .../templates/devhelp/dev-console.jinja | 6 ++-- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/cryptoadvance/specter/managers/wallet_manager.py b/src/cryptoadvance/specter/managers/wallet_manager.py index 8731c53ae3..8c27f531d1 100644 --- a/src/cryptoadvance/specter/managers/wallet_manager.py +++ b/src/cryptoadvance/specter/managers/wallet_manager.py @@ -265,14 +265,11 @@ def _update(self, wallets_update_list: Dict): logger.error(f"Failed updating wallet manager. RPC error: {e}") finally: self.is_loading = False - logger.info("Updating wallet manager done. Result:") - logger.info(f" * loaded_wallets: {len(self.wallets)}") - logger.info(f" * failed_load_wallets: {len(self._failed_load_wallets)}") - for wallet in self._failed_load_wallets: - logger.info(f" * {wallet['name']} : {wallet['loading_error']}") - - wallets_update_list = {} - self.is_loading = False + logger.info("Updating wallet manager done. Result:") + logger.info(f" * loaded_wallets: {len(self.wallets)}") + logger.info(f" * failed_load_wallets: {len(self._failed_load_wallets)}") + for wallet in self._failed_load_wallets: + logger.info(f" * {wallet['name']} : {wallet['loading_error']}") def get_by_alias(self, alias): for wallet_name in self.wallets: diff --git a/src/cryptoadvance/specter/txlist.py b/src/cryptoadvance/specter/txlist.py index 204bb5f4e8..6b8ebe9d47 100644 --- a/src/cryptoadvance/specter/txlist.py +++ b/src/cryptoadvance/specter/txlist.py @@ -330,10 +330,19 @@ def psbt(self) -> SpecterPSBT: def is_taproot(self): return str(self.descriptor).startswith("tr(") - @property - def psbt_decoded(self) -> SpecterPSBT: - """This tx but as a psbt. Need rpc-calls""" - return self.rpc.decodepsbt(str(self.psbt)) + def decode_psbt(self, mode="embit") -> SpecterPSBT: + """Utility function which decodes this tx as psbt + as in the core rpc-call 'decodepsbt'. + However, it uses embit to calculate the details + use mode=core to ask core directly. + embit might support taproot, core might not. + """ + if mode == "core": + return self.rpc.decodepsbt(str(self.psbt)) + elif mode == "embit": + return self.psbt.to_dict() + else: + raise SpecterInternalException("Mode not existing") @property def category(self): @@ -432,13 +441,13 @@ def address(self): self["address"] = addresses[0] return self["address"] - def __dict__(self): - super_dict = dict(self) - super_dict["category"] = self.category - super_dict["flow_amount"] = self.flow_amount - super_dict["utxo_amount"] = self.utxo_amount - super_dict["ismine"] = (self["ismine"] or self.ismine,) - return super_dict + # def __dict__(self): + # super_dict = dict(self) + # super_dict["category"] = self.category + # super_dict["flow_amount"] = self.flow_amount + # super_dict["utxo_amount"] = self.utxo_amount + # super_dict["ismine"] = (self["ismine"] or self.ismine,) + # return super_dict class TxList(dict, AbstractTxListContext): diff --git a/src/cryptoadvance/specterext/devhelp/templates/devhelp/dev-console.jinja b/src/cryptoadvance/specterext/devhelp/templates/devhelp/dev-console.jinja index b3b5dbc64b..2f9389791d 100644 --- a/src/cryptoadvance/specterext/devhelp/templates/devhelp/dev-console.jinja +++ b/src/cryptoadvance/specterext/devhelp/templates/devhelp/dev-console.jinja @@ -27,9 +27,10 @@

- you can also do that in any javascript.console with: + Press F12 to open the dev-console (helpful to see objects)
+ You can also do that in any javascript.console with:
-    await pythonCommand("app.specter") 
+    await pythonCommand('app.specter') 
     
Some usefull idioms:
@@ -109,6 +110,7 @@
 
 
             } else {
+                console.log(result)
                 if (result == "") {
                     result = "(empty string)"
                 }