Chore: Porting chore/rid_of_defaultnode to uiux-revamp (#2098)

* Chore: Remove default node (#2072)

* change passwordaspin auth implementation

* env-var for RASPIBLITZ_SPECTER_RPC_LOGIN_BITCOIN_CONF_LOCATION

* remove default

* fix tests

* fix the tests part2

* fix cypress tests

* docstrings and other default removals

* improve error_handling

* do not raise exception

* fixed debugging artifact

* Spinning refresh icon if node is still syncing

* Cypress test for sync status

* remove notification of fully synced node

* Set timeout for sync check to 5min

---------

Co-authored-by: moneymanolis <moneymanolis@protonmail.com>
Co-authored-by: Manolis Mandrapilias <70536101+moneymanolis@users.noreply.github.com>
This commit is contained in:
k9ert 2023-01-31 11:06:29 +01:00 committed by GitHub
parent 02b95af61f
commit 3ee881805e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 946 additions and 356 deletions

View file

@ -45,9 +45,16 @@ describe('Connecting nodes', () => {
// TODO: For testing the deletion we could delete the Liquid connection here if we don't run the Liquid tests
it('Select Bitcoin Core connection', () => {
cy.get('#node-switch-icon').click()
cy.contains('Bitcoin Core').click()
cy.contains('Switched to use Bitcoin Core as connection')
cy.get('#node-switch-icon').click()
cy.contains('Bitcoin Core').click()
cy.contains('Switched to use Bitcoin Core as connection')
})
it('Check sync status of Bitcoin Core node', () => {
cy.intercept("GET", "/nodes/sync_status/", {'fullySynced': false});
cy.visit('/')
cy.contains('Your Bitcoin node is syncing.')
cy.get('[data-cy="unfinished-sync-indicator"]').should('be.visible')
})
})

View file

@ -77,6 +77,13 @@ class BaseConfig(object):
# CERT and KEY is for running self-signed-ssl-certs. Check cli_server for details
CERT = os.getenv("CERT", None)
KEY = os.getenv("KEY", None)
# This will be used to search for a bitcoin.conf in order to enable the
# auth method "RPC password as pin"
RASPIBLITZ_SPECTER_RPC_LOGIN_BITCOIN_CONF_LOCATION = os.getenv(
"RASPIBLITZ_SPECTER_RPC_LOGIN_BITCOIN_CONF_LOCATION", "/mnt/hdd/bitcoin"
)
# This will get passed to initialize the specter-object
DEFAULT_SPECTER_CONFIG = {}

View file

@ -3,6 +3,7 @@ from typing import Type
from cryptoadvance.specter.util.reflection import get_subclasses_for_clazz
from .key import Key
from typing import List
from .persistence import read_json_file, write_json_file
import logging
from .helpers import is_testnet, is_liquid
@ -44,7 +45,7 @@ class Device:
"""
self.name = name
self.alias = alias
self.keys = keys
self.keys: List[Key] = keys
self.fullpath = fullpath
self.blinding_key = blinding_key
self.manager = manager

View file

@ -46,7 +46,7 @@ class ConfigManager(GenericDataManager):
"asset_labels": {
"liquidv1": {},
},
"active_node_alias": "default",
"active_node_alias": None,
"proxy_url": "socks5h://localhost:9050", # Tor proxy URL
"only_tor": False,
"tor_control_port": "",

View file

@ -8,7 +8,7 @@ from ..rpc import get_default_datadir, RPC_PORTS
from ..specter_error import SpecterError, SpecterInternalException
from ..persistence import PersistentObject, write_node, delete_file
from ..helpers import alias, calc_fullpath, load_jsons
from ..node import Node
from ..node import Node, NonExistingNode
from ..internal_node import InternalNode
from ..services import callbacks
from ..managers.service_manager import ServiceManager
@ -18,14 +18,11 @@ logger = logging.getLogger(__name__)
class NodeManager:
# chain is required to manage wallets when bitcoind is not running
DEFAULT_ALIAS = "default"
def __init__(
self,
proxy_url="socks5h://localhost:9050",
only_tor=False,
active_node="default",
active_node=None,
bitcoind_path="",
internal_bitcoind_version="",
data_folder="",
@ -56,6 +53,7 @@ class NodeManager:
if not os.path.isdir(data_folder):
os.mkdir(data_folder)
nodes_files = load_jsons(self.data_folder, key="alias")
logger.debug(nodes_files)
for node_alias in nodes_files:
try:
valid_node = True
@ -101,49 +99,15 @@ class NodeManager:
port=7041,
host="localhost",
protocol="http",
default_alias=self.DEFAULT_ALIAS,
)
logger.debug(
"Creating an external BTC node with the initial configuration."
)
self.add_external_node(
node_type="BTC",
name="",
autodetect=False,
datadir=get_default_datadir(),
user="",
password="",
port=8332,
host="localhost",
protocol="http",
default_alias=self.DEFAULT_ALIAS,
)
# Make sure we always have the default node
# (needed for the rpc-as-pin-authentication used on Raspiblitz)
has_default_node = False
for node in self.nodes.values():
if node.alias == self.DEFAULT_ALIAS:
has_default_node = True
# Recreate the default node if it doesn't exist anymore
if not has_default_node:
logger.debug("Recreating the default node.")
self.add_external_node(
node_type="BTC",
name="",
autodetect=False,
datadir=get_default_datadir(),
user="",
password="",
port=8332,
host="localhost",
protocol="http",
default_alias=self.DEFAULT_ALIAS,
)
@property
def active_node(self) -> Node:
return self.get_by_alias(self._active_node)
"""returns the current active node or a NonExistingNode
if no node is active, currently.
"""
active_node = self.get_by_alias(self._active_node)
return active_node if active_node else NonExistingNode()
@property
def nodes_names(self) -> list:
@ -155,26 +119,37 @@ class NodeManager:
return [node for node in self.nodes.values() if node.chain == chain]
def switch_node(self, node_alias: str):
# This will throw an error if the node doesn't exist
"""This will throw an SpecterError if the node doesn't exist.
It won't persist anything! Use specter.update_active_node to persist!
"""
new_node = self.get_by_alias(node_alias)
if not new_node:
raise SpecterError(f"Node alias {node_alias} does not exist!")
logger.debug(f"Switching from {self._active_node} to {node_alias}.")
self._active_node = self.get_by_alias(node_alias).alias
def default_node(self) -> Node:
return self.get_by_alias(self.DEFAULT_ALIAS)
self._active_node = node_alias
def get_by_alias(self, alias: str) -> Node:
"""Returns a Node instance for the given alias.
None if a node with that alias doesn't exist
"""
for node in self.nodes.values():
if node.alias == alias:
return node
raise SpecterError("Node alias %s does not exist!" % alias)
return None
def get_by_name(self, name: str) -> Node:
"""Returns a Node instance for the given alias.
raises an SpecterError if it doesn't exist
"""
for node in self.nodes.values():
if node.name == name:
return node
raise SpecterError("Node name %s does not exist!" % name)
def get_name_from_alias(self, alias: str) -> str:
"""Returns the name for a specific node alias
raises an SpecterError if it doesn't exist
"""
for node in self.nodes.values():
if node.alias == alias:
return node.name
@ -200,26 +175,25 @@ class NodeManager:
def add_external_node(
self,
node_type,
name,
autodetect,
node_type: str,
name: str,
autodetect: bool,
datadir,
user,
password,
port,
host,
protocol,
default_alias=None,
user: str,
password: str,
port: str,
host: str,
protocol: str,
):
"""Adding a node. Params:
:param node_type: Either BTC or ELM, used to distinguish between Bitcoin and Liquid nodes.
"""Adding a node and saves it to disk as well. Params:
* node_type: only valid for autodetect. Either BTC or ELM
* name: A nice name for this node. The alias will get calculated out of that
* autodetect (boolean): whether this node should get autodetected
* datadir: questionable! Why is that here needed?!
This should only be used for an external node. Use add_internal_node for internal node
and if you have defined your own node type, use save_node directly to save the node (and create it yourself)
"""
if not default_alias:
node_alias = alias(name)
else:
node_alias = default_alias
node_alias = alias(name)
fullpath = os.path.join(self.data_folder, "%s.json" % node_alias)
i = 2
while os.path.isfile(fullpath):
@ -247,6 +221,8 @@ class NodeManager:
return node
def save_node(self, node):
"""writes the node to disk. Will also apply a fullpath based on the datadir of the
NodeManager if the node doesn't have one."""
if not hasattr(node, "fullpath"):
node.fullpath = calc_fullpath(self.data_folder, node.alias)
write_node(node, node.fullpath)
@ -254,20 +230,16 @@ class NodeManager:
def add_internal_node(
self,
name,
name: str,
network="main",
port=None,
default_alias=None,
port: str = None,
datadir=None,
):
"""Adding an internal node. Params:
This should only be used for internal nodes. Use add__External_node for external nodes
and if you have defined your own node-type, use save_node directly. to save the node (and create it yourself)
"""
if not default_alias:
node_alias = alias(name)
else:
node_alias = default_alias
node_alias = alias(name)
fullpath = os.path.join(self.data_folder, "%s.json" % node_alias)
i = 2
while os.path.isfile(fullpath):
@ -298,6 +270,7 @@ class NodeManager:
return node
def delete_node(self, node, specter):
"""Deletes the node. Also from the disk."""
logger.info("Deleting {}".format(node.alias))
try:
# Delete from wallet manager
@ -306,7 +279,7 @@ class NodeManager:
delete_file(node.fullpath)
delete_file(node.fullpath + ".bkp")
# Update the active node
if self._active_node == node.alias:
if self._active_node == node.alias and len(self.nodes) > 0:
specter.update_active_node(
next(iter(self.nodes.values())).alias
) # This switches to the first node in the node list, which is usually the default node

View file

@ -13,7 +13,7 @@ from cryptoadvance.specter.key import Key
from ..helpers import add_dicts, alias, is_liquid, load_jsons
from ..liquid.wallet import LWallet
from ..persistence import delete_folder
from ..rpc import RpcError, get_default_datadir
from ..rpc import RpcError, get_default_datadir, BrokenCoreConnectionException
from ..specter_error import SpecterError, SpecterInternalException, handle_exception
from ..util.flask import FlaskThread
from ..wallet import ( # TODO: `purposes` unused here, but other files rely on this import
@ -31,7 +31,6 @@ class WalletManager:
# chain is required to manage wallets when bitcoind is not running
def __init__(
self,
bitcoin_core_version_raw,
data_folder,
rpc,
chain,
@ -50,8 +49,6 @@ class WalletManager:
# key is the name of the wallet, value is the actual instance
self.wallets = {}
# A way to communicate failed wallets to the outside
self.bitcoin_core_version_raw = bitcoin_core_version_raw
self.allow_threading_for_testing = allow_threading_for_testing
# define different wallet classes for liquid and bitcoin
self.WalletClass = LWallet if is_liquid(chain) else Wallet
@ -333,6 +330,16 @@ class WalletManager:
self._wallets[self.chain] = {}
self._wallets[self.chain] = value
@property
def bitcoin_core_version_raw(self):
try:
bitcoin_core_version_raw = self.rpc.getnetworkinfo()["version"]
return bitcoin_core_version_raw or 200000
except BrokenCoreConnectionException:
# In good faith and in order to keep the tests running, we assume
# a reasonable core version
return 200000
def create_wallet(self, name, sigs_required, key_type, keys, devices, **kwargs):
try:
walletsindir = [

View file

@ -28,7 +28,94 @@ from .device import Device
logger = logging.getLogger(__name__)
class AbstractNode(PersistentObject):
class NonExistingNode(PersistentObject):
"""A kind of Null-object as it represents a non-existing Node. It's deriving from PersistentObject but is not meant to be
persisted. Instead, it's be created on the fly from the NodeManager if it doesn't have a reasonable node available.
It also works as some kind of minimal implementation so that specter doesn't fail gracefully.
"""
@property
def info(self):
return {}
@property
def network_info(self):
return {}
@property
def uptime(self):
return -1
@property
def chain(self):
return None
@property
def bitcoin_core_version_raw(self):
return 9999999
def update_rpc(self):
pass
@property
def is_running(self):
return False
@property
def rpc(self):
return None
def check_blockheight(self):
"""check_blockheight is a method which is probably deprecated.
It should return True if there are new blocks available since check_info has been called
(which updates the cached _info[] dict)
"""
raise NotImplemented(
"A Node Implementation need to implement the check_blockheight method"
)
def is_device_supported(self, device_class_or_device_instance):
"""Lets the node deactivate specific devices. The parameter could be a device or a device_type
You have to check yourself if overriding this method.
e.g.
if device_instance_or_device_class.__class__ == type:
device_class = device_instance_or_device_class
else:
device_class = device_instance_or_device_class.__class__
# example:
# if BitcoinCore == device_class:
# return False
return True
"""
return True
def node_info_template(self):
"""This should return the path to a Info template as string"""
return "node/components/bitcoin_core_info.jinja"
def node_logo_template(self):
"""This should return the path to a Logo template as string
The template should contain the logo independent from the
status of the node. It's used in the node-selector
"""
return "includes/sidebar/components/node_logo.jinja"
def node_connection_template(self):
"""This should return the path to a connection template as string"""
return "includes/sidebar/components/node_connection.jinja"
def delete_wallet_file(self, wallet) -> bool:
"""Deleting the wallet file located on the node. This only works if the node is on the same machine as Specter.
Returns True if the wallet file could be deleted, otherwise returns False.
In the case of an Abtract Node, we consider that method as an edge-case anyway and we just return False here.
That is the normal usage if you don't have access to the internals of your Bitcoin Core.
Overwrite as necessary.
"""
return False
class AbstractNode(NonExistingNode):
"""This is a Node class worth deriving from. It tries to define as many attributes as possible which are needed but probably in a very
inefficient way, e.g. without any caching. Feel free to improve that in subclasses and you might get inspired by existing sublasses
"""

View file

@ -501,7 +501,8 @@ class BitcoinRPC:
r = self.multi([(method, *args)], **kwargs)[0]
if r["error"] is not None:
raise RpcError(
f"Request error for method {method}: {r['error']['message']}", r
f"Request error for method {method}{args}: {r['error']['message']}",
r,
)
return r["result"]

View file

@ -7,10 +7,13 @@ from flask import jsonify, redirect, render_template, request, url_for
from flask_babel import lazy_gettext as _
from flask_login import current_user, login_required, logout_user
from cryptoadvance.specter.specter import Specter
from ..helpers import alias
from ..server_endpoints import flash
from ..services import ExtensionException
from ..user import User, hash_password, verify_password
from ..rpc import BitcoinRPC, _detect_rpc_confs_via_datadir
rand = random.randint(0, 1e32) # to force style refresh
last_sensitive_request = 0 # to rate limit sensitive requests
@ -28,60 +31,13 @@ def login():
auth = app.specter.config["auth"]
if auth["method"] == "none":
app.login("admin")
app.logger.info("AUDIT: Successful Login no credentials")
return redirect_login(request)
if auth["method"] == "rpcpasswordaspin":
# TODO: check the password via RPC-call
if (
app.specter.default_node.rpc is None
or not app.specter.default_node.rpc.test_connection()
):
if app.specter.default_node.password == request.form["password"]:
app.login("admin", request.form["password"])
app.logger.info(
"AUDIT: Successfull Login via RPC-credentials (node disconnected)"
)
return redirect_login(request)
flash(
_(
"We could not check your password, maybe Bitcoin Core is not running or not configured?"
),
"error",
)
app.logger.info("AUDIT: Failed to check password")
return (
render_template(
"login.jinja",
specter=app.specter,
data={"controller": "controller.login"},
),
401,
)
rpc = app.specter.default_node.rpc.clone()
rpc.password = request.form["password"]
if rpc.test_connection():
app.login("admin", request.form["password"])
app.logger.info("AUDIT: Successfull Login via RPC-credentials")
return redirect_login(request)
return login_method_none()
elif auth["method"] == "rpcpasswordaspin":
return login_method_rpcpasswordaspin()
elif auth["method"] == "passwordonly":
password = request.form["password"]
if verify_password(app.specter.user_manager.admin.password_hash, password):
app.login("admin", request.form["password"])
return redirect_login(request)
return login_method_passwordonly()
elif auth["method"] == "usernamepassword":
# TODO: This way both "User" and "user" will pass as usernames, should there be strict check on that here? Or should we keep it like this?
username = request.form["username"]
password = request.form["password"]
user = app.specter.user_manager.get_user_by_username(username)
if user:
if verify_password(user.password_hash, password):
app.login(user.id, request.form["password"])
return redirect_login(request)
return login_method_usernamepassword()
# Either invalid method or incorrect credentials
flash(_("Invalid username or password"), "error")
@ -103,6 +59,83 @@ def login():
)
def deny_login(comment=""):
flash("Invalid user or password!")
app.logger.info("AUDIT: Invalid password login attempt")
return redirect("login")
def login_method_none():
app.login("admin")
app.logger.info("AUDIT: Successful Login no credentials")
return redirect_login(request)
def login_method_rpcpasswordaspin():
# This authentiaction method has been especially created for Raspiblitz
# We assume here, that no preconfigured node-connection is available
# otherwise, which configured node should we use?
# We want to get rid of the default node, so we can't use that
# Instead we use a default location where we assume the bitcoin.conf
# This can be overridden via ENV_var.
specter: Specter = app.specter
if specter.node and specter.node._get_rpc().test_connection():
rpc = specter.node._get_rpc().clone()
else:
confs = _detect_rpc_confs_via_datadir(
None,
datadir=app.config["RASPIBLITZ_SPECTER_RPC_LOGIN_BITCOIN_CONF_LOCATION"],
)
if len(confs) == 0:
flash(
"No RPC connection to Bitcoin Core found. Cannot Log you in.", "error"
)
return redirect(url_for("login"))
conf = confs[0]
rpc = BitcoinRPC(**conf)
# A bit redundant as this has been checked before but let's be sure
if not rpc.test_connection():
flash(
"It seems that there is no working RPC connection to Bitcoin Core. Cannot Log you in."
)
return redirect(url_for("login"))
orig_password = rpc.password
rpc.password = request.form["password"]
if rpc.password == request.form["password"] and rpc.test_connection():
app.login("admin", request.form["password"])
app.logger.info("AUDIT: Successfull Login via RPC-credentials")
return redirect_login(request)
if orig_password == request.form["password"]:
app.login("admin", request.form["password"])
app.logger.info(
f"AUDIT: Successfull Login via RPC-credentials (node not reachable) {rpc.password}"
)
return redirect_login(request)
return deny_login(comment="Pin")
def login_method_passwordonly():
password = request.form["password"]
if verify_password(app.specter.user_manager.admin.password_hash, password):
app.login("admin", request.form["password"])
return redirect_login(request)
return deny_login(comment="passwordonly")
def login_method_usernamepassword():
# TODO: This way both "User" and "user" will pass as usernames, should there be strict check on that here? Or should we keep it like this?
username = request.form["username"]
password = request.form["password"]
user = app.specter.user_manager.get_user_by_username(username)
if user:
if verify_password(user.password_hash, password):
app.login(user.id, request.form["password"])
return redirect_login(request)
return deny_login(comment="usernamepassword")
@auth_endpoint.route("/register", methods=["GET", "POST"])
def register():
"""register"""

View file

@ -553,3 +553,13 @@ def rename_node():
node.rename(new_name)
response = {"nameChanged": True, "error": None}
return jsonify(response)
@nodes_endpoint.route("sync_status/", methods=["GET"])
@login_required
def check_sync_status():
if app.specter.info.get("initialblockdownload") == True:
response = {"fullySynced": False}
else:
response = {"fullySynced": True}
return jsonify(response)

View file

@ -127,10 +127,8 @@ class Specter:
)
except SpecterError as e:
if str(e).endswith("does not exist!"):
logger.warning(
f"Current Node doesn't exist. Switching over to node {self.node_manager.DEFAULT_ALIAS}."
)
self.update_active_node(self.node_manager.DEFAULT_ALIAS)
if len(self.node_manager.nodes) > 0:
self.update_active_node(next(iter(self.nodes.values())).alias)
else:
raise e
@ -237,13 +235,7 @@ class Specter:
@property
def node(self) -> Node:
try:
return self.node_manager.active_node
except SpecterError as e:
logger.error("SpecterError while accessing active_node")
logger.exception(e)
self.update_active_node(list(self.node_manager.nodes.values())[0].alias)
return self.node_manager.active_node
return self.node_manager.active_node
@property
def default_node(self):
@ -565,7 +557,7 @@ class Specter:
@property
def active_node_alias(self):
return self.user_config.get("active_node_alias", "default")
return self.user_config.get("active_node_alias", None)
@property
def explorer(self):
@ -745,7 +737,7 @@ class Specter:
old_internal_rpc = self.config.get("internal_node", None)
if old_internal_rpc and os.path.isfile(self.bitcoind_path):
internal_node = InternalNode(
"Specter Bitcoin",
"Specter Bitcoin internal",
"specter_bitcoin",
old_internal_rpc.get("autodetect", False),
old_internal_rpc.get("datadir", get_default_datadir()),
@ -755,7 +747,7 @@ class Specter:
old_internal_rpc.get("host", "localhost"),
old_internal_rpc.get("protocol", "http"),
os.path.join(
os.path.join(self.data_folder, "nodes"), "specter_bitcoin.json"
os.path.join(self.data_folder, "nodes"), "bitcoin_core.json"
),
self,
self.bitcoind_path,
@ -776,7 +768,7 @@ class Specter:
if old_rpc:
node = Node(
"Bitcoin Core",
"default",
"bitcoin_core",
old_rpc.get("autodetect", True),
old_rpc.get("datadir", get_default_datadir()),
old_rpc.get("user", ""),
@ -784,16 +776,21 @@ class Specter:
old_rpc.get("port", None),
old_rpc.get("host", "localhost"),
old_rpc.get("protocol", "http"),
os.path.join(os.path.join(self.data_folder, "nodes"), "default.json"),
os.path.join(
os.path.join(self.data_folder, "nodes"), "bitcoin_core.json"
),
"BTC",
self,
)
logger.info(f"persisting {node} in migrate_old_node_format")
write_node(
node,
os.path.join(os.path.join(self.data_folder, "nodes"), "default.json"),
os.path.join(
os.path.join(self.data_folder, "nodes"), "bitcoin_core.json"
),
)
del self.config["rpc"]
self.config_manager.update_active_node("bitcoin_core")
self._save()

View file

@ -48,8 +48,13 @@ class ExtProcTimeoutException(SpecterInternalException):
def handle_exception(exception, user=None):
"""prints the exception and most important the stacktrace"""
if app.config["SPECTER_CONFIGURATION_CLASS_FULLNAME"].endswith("DevelopmentConfig"):
raise exception
try:
if app.config["SPECTER_CONFIGURATION_CLASS_FULLNAME"].endswith(
"DevelopmentConfig"
):
raise exception
except RuntimeError: # Application context might be missing
pass
logger.error("Unexpected error:")
logger.error(
"----START-TRACEBACK-----------------------------------------------------------------"

View file

@ -208,7 +208,7 @@ async function send_request(url, method_str, csrf_token, formData) {
return jsonResponse
}
catch(error) {
showError(`Failed to fetch transactions list: ${error}`)
showError(`Error occured durch fetch call: ${error}`)
return { 'error': error}
}
}

View file

@ -863,6 +863,10 @@ select {
background-color: rgb(27 28 33 / var(--tw-bg-opacity));
}
.animate-spin-slow {
animation: spin 1.5s linear infinite;
}
*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
@ -1114,6 +1118,10 @@ input[type="number"]::-webkit-outer-spin-button,
margin: auto;
}
.m-3 {
margin: 0.75rem;
}
.m-4 {
margin: 1rem;
}
@ -1740,6 +1748,11 @@ input[type="number"]::-webkit-outer-spin-button,
border-radius: 0.375rem;
}
.rounded-l-md {
border-top-left-radius: 0.375rem;
border-bottom-left-radius: 0.375rem;
}
.rounded-t-xl {
border-top-left-radius: 0.75rem;
border-top-right-radius: 0.75rem;
@ -1896,6 +1909,10 @@ input[type="number"]::-webkit-outer-spin-button,
padding: 1.25rem;
}
.p-2 {
padding: 0.5rem;
}
.px-\[calc\(\(100\%-700px\)\/2\)\] {
padding-left: calc((100% - 700px) / 2);
padding-right: calc((100% - 700px) / 2);
@ -2023,6 +2040,11 @@ input[type="number"]::-webkit-outer-spin-button,
line-height: 1.25rem;
}
.text-xs {
font-size: 0.75rem;
line-height: 1rem;
}
.font-medium {
font-weight: 500;
}
@ -2171,10 +2193,18 @@ input[type="number"]::-webkit-outer-spin-button,
transition-duration: 200ms;
}
.duration-1000 {
transition-duration: 1000ms;
}
.ease-out {
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
}
.ease-in-out {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
* {
font-family: "Social";
font-style: normal;

View file

@ -185,6 +185,10 @@
.selection-button {
@apply rounded-lg w-full h-10 bg-dark-800 border-2 border-dark-700 text-center block text-lg py-1 hover:bg-dark-700;
}
.animate-spin-slow {
animation: spin 1.5s linear infinite;
}
}
@layer components {

View file

@ -1,8 +1,13 @@
{% set node = specter.node %}
{% if node.is_running %}
<div class="px-3 py-2 h-full min-w-0 grow flex items-center cursor-pointer" id="active-node" onclick="showPageOverlay('node-info-popup', {top: '64px', left: '16px'});document.getElementById('side-content').classList.remove('active');" >
<svg class="w-7 h-7 text-white" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><!--Generated by IJSVG (https://github.com/iconjar/IJSVG)--><path d="M54.5946,27.4366l5.66603e-08,-5.66066e-08c-0.745305,0.744598 -1.9414,0.780481 -2.73,0.0819001l1.11948e-07,9.86842e-08c-11.3381,-9.99478 -28.3408,-9.99478 -39.6789,-1.97368e-07l4.1375e-08,-3.6659e-08c-0.788564,0.698682 -1.98474,0.662797 -2.73,-0.0818999l-2.831,-2.8295l4.78383e-08,4.78239e-08c-0.782846,-0.782609 -0.783038,-2.05166 -0.000429116,-2.83451c0.0255969,-0.0256047 0.0518829,-0.0505111 0.078829,-0.0746918l-1.08875e-06,9.72979e-07c14.4215,-12.888 36.2226,-12.888 50.6441,-1.94596e-06l-4.18137e-09,-3.75185e-09c0.823858,0.73923 0.892463,2.00636 0.153233,2.83022c-0.0242251,0.0269984 -0.0491787,0.0533343 -0.0748332,0.0789783Zm-17.6311,13.896l1.74829e-07,9.87123e-08c1.00267,0.56613 1.35656,1.8379 0.79043,2.84057c-0.0942659,0.166954 -0.21111,0.320108 -0.34723,0.45513l-3.6928,3.6684l2.2751e-08,-2.25368e-08c-0.943884,0.934995 -2.46482,0.934995 -3.4087,4.50735e-08l-3.6928,-3.6684l-6.02819e-08,-5.98007e-08c-0.8175,-0.810974 -0.82279,-2.13111 -0.0118161,-2.94861c0.134967,-0.136053 0.288047,-0.252849 0.454916,-0.347088l2.96297e-07,-1.66902e-07c3.07549,-1.7324 6.83251,-1.7324 9.908,3.33805e-07Zm-14.8508,-3.792l3.86385e-08,-3.1954e-08c-0.835403,0.690877 -2.05882,0.634623 -2.8273,-0.13l-2.3847,-2.4084c-1.1739,-1.1855 -1.14,-2.6381 -0.1975,-3.4266l9.62086e-07,-8.05987e-07c8.84671,-7.41133 21.7347,-7.41133 30.5814,1.61198e-06l-5.08261e-08,-4.40698e-08c0.88879,0.770643 0.984568,2.11588 0.213926,3.00467c-0.0332999,0.0384052 -0.0679639,0.075606 -0.103925,0.111531l-2.6883,2.7151l2.63897e-08,-2.62774e-08c-0.768944,0.765671 -1.99317,0.823532 -2.8309,0.1338l-1.43462e-07,-1.17058e-07c-5.75168,-4.69307 -14.0109,-4.69307 -19.7626,2.34116e-07Z" fill="currentColor" fill-rule="evenodd"></path></svg>
<div class="px-3 py-2 h-full min-w-0 grow flex items-center cursor-pointer" id="active-node" onclick="showPageOverlay('node-info-popup', {top: '64px', left: '16px'});document.getElementById('side-content').classList.remove('active');" >
{% if specter.info.get("initialblockdownload") == True %}
<img class="rounded-full bg-dark-700 w-4 h-4 animate-spin-slow" id="unfinished-ibd-indicator" src="{{ url_for('static', filename='img/refresh.svg') }}" data-cy="unfinished-sync-indicator"/>
<svg class="w-7 h-7 text-white hidden" id="connected-icon" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><!--Generated by IJSVG (https://github.com/iconjar/IJSVG)--><path d="M54.5946,27.4366l5.66603e-08,-5.66066e-08c-0.745305,0.744598 -1.9414,0.780481 -2.73,0.0819001l1.11948e-07,9.86842e-08c-11.3381,-9.99478 -28.3408,-9.99478 -39.6789,-1.97368e-07l4.1375e-08,-3.6659e-08c-0.788564,0.698682 -1.98474,0.662797 -2.73,-0.0818999l-2.831,-2.8295l4.78383e-08,4.78239e-08c-0.782846,-0.782609 -0.783038,-2.05166 -0.000429116,-2.83451c0.0255969,-0.0256047 0.0518829,-0.0505111 0.078829,-0.0746918l-1.08875e-06,9.72979e-07c14.4215,-12.888 36.2226,-12.888 50.6441,-1.94596e-06l-4.18137e-09,-3.75185e-09c0.823858,0.73923 0.892463,2.00636 0.153233,2.83022c-0.0242251,0.0269984 -0.0491787,0.0533343 -0.0748332,0.0789783Zm-17.6311,13.896l1.74829e-07,9.87123e-08c1.00267,0.56613 1.35656,1.8379 0.79043,2.84057c-0.0942659,0.166954 -0.21111,0.320108 -0.34723,0.45513l-3.6928,3.6684l2.2751e-08,-2.25368e-08c-0.943884,0.934995 -2.46482,0.934995 -3.4087,4.50735e-08l-3.6928,-3.6684l-6.02819e-08,-5.98007e-08c-0.8175,-0.810974 -0.82279,-2.13111 -0.0118161,-2.94861c0.134967,-0.136053 0.288047,-0.252849 0.454916,-0.347088l2.96297e-07,-1.66902e-07c3.07549,-1.7324 6.83251,-1.7324 9.908,3.33805e-07Zm-14.8508,-3.792l3.86385e-08,-3.1954e-08c-0.835403,0.690877 -2.05882,0.634623 -2.8273,-0.13l-2.3847,-2.4084c-1.1739,-1.1855 -1.14,-2.6381 -0.1975,-3.4266l9.62086e-07,-8.05987e-07c8.84671,-7.41133 21.7347,-7.41133 30.5814,1.61198e-06l-5.08261e-08,-4.40698e-08c0.88879,0.770643 0.984568,2.11588 0.213926,3.00467c-0.0332999,0.0384052 -0.0679639,0.075606 -0.103925,0.111531l-2.6883,2.7151l2.63897e-08,-2.62774e-08c-0.768944,0.765671 -1.99317,0.823532 -2.8309,0.1338l-1.43462e-07,-1.17058e-07c-5.75168,-4.69307 -14.0109,-4.69307 -19.7626,2.34116e-07Z" fill="currentColor" fill-rule="evenodd"></path></svg>
{% else %}
<img class="rounded-full bg-dark-700 w-4 h-4 animate-spin-slow hidden" id="unfinished-ibd-indicator" src="{{ url_for('static', filename='img/refresh.svg') }}" data-cy="unfinished-sync-indicator"/>
<svg class="w-7 h-7 text-white" id="connected-icon" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><!--Generated by IJSVG (https://github.com/iconjar/IJSVG)--><path d="M54.5946,27.4366l5.66603e-08,-5.66066e-08c-0.745305,0.744598 -1.9414,0.780481 -2.73,0.0819001l1.11948e-07,9.86842e-08c-11.3381,-9.99478 -28.3408,-9.99478 -39.6789,-1.97368e-07l4.1375e-08,-3.6659e-08c-0.788564,0.698682 -1.98474,0.662797 -2.73,-0.0818999l-2.831,-2.8295l4.78383e-08,4.78239e-08c-0.782846,-0.782609 -0.783038,-2.05166 -0.000429116,-2.83451c0.0255969,-0.0256047 0.0518829,-0.0505111 0.078829,-0.0746918l-1.08875e-06,9.72979e-07c14.4215,-12.888 36.2226,-12.888 50.6441,-1.94596e-06l-4.18137e-09,-3.75185e-09c0.823858,0.73923 0.892463,2.00636 0.153233,2.83022c-0.0242251,0.0269984 -0.0491787,0.0533343 -0.0748332,0.0789783Zm-17.6311,13.896l1.74829e-07,9.87123e-08c1.00267,0.56613 1.35656,1.8379 0.79043,2.84057c-0.0942659,0.166954 -0.21111,0.320108 -0.34723,0.45513l-3.6928,3.6684l2.2751e-08,-2.25368e-08c-0.943884,0.934995 -2.46482,0.934995 -3.4087,4.50735e-08l-3.6928,-3.6684l-6.02819e-08,-5.98007e-08c-0.8175,-0.810974 -0.82279,-2.13111 -0.0118161,-2.94861c0.134967,-0.136053 0.288047,-0.252849 0.454916,-0.347088l2.96297e-07,-1.66902e-07c3.07549,-1.7324 6.83251,-1.7324 9.908,3.33805e-07Zm-14.8508,-3.792l3.86385e-08,-3.1954e-08c-0.835403,0.690877 -2.05882,0.634623 -2.8273,-0.13l-2.3847,-2.4084c-1.1739,-1.1855 -1.14,-2.6381 -0.1975,-3.4266l9.62086e-07,-8.05987e-07c8.84671,-7.41133 21.7347,-7.41133 30.5814,1.61198e-06l-5.08261e-08,-4.40698e-08c0.88879,0.770643 0.984568,2.11588 0.213926,3.00467c-0.0332999,0.0384052 -0.0679639,0.075606 -0.103925,0.111531l-2.6883,2.7151l2.63897e-08,-2.62774e-08c-0.768944,0.765671 -1.99317,0.823532 -2.8309,0.1338l-1.43462e-07,-1.17058e-07c-5.75168,-4.69307 -14.0109,-4.69307 -19.7626,2.34116e-07Z" fill="currentColor" fill-rule="evenodd"></path></svg>
{% endif %}
<div id="sidebar-connection-name" class="ml-2 text-lg min-w-0 truncate">{{ node.name }}</div>
<div class="hidden" id="node-info-popup">{% include node.node_info_template() %}</div>
</div>
@ -20,4 +25,35 @@
document.addEventListener('connectionRenamed', (e) => {
sidebarConnectionNameElement.innerText = e.detail.newName
})
document.addEventListener("DOMContentLoaded", () => {
{% if node.is_running %}
checkSyncStatus()
{% endif %}
})
let count = 0
async function checkSyncStatus() {
let url = `{{ url_for('nodes_endpoint.check_sync_status') }}`;
const response = await send_request(url, 'GET', "{{ csrf_token() }}");
if (response.fullySynced === true) {
console.log('IBD is finished, Node is fully synced.')
document.getElementById('unfinished-ibd-indicator').classList.remove('animate-spin')
document.getElementById("unfinished-ibd-indicator").classList.add('hidden')
document.getElementById('connected-icon').classList.remove('hidden')
}
else {
console.log('IBD is not finished yet.')
document.getElementById('unfinished-ibd-indicator').classList.remove('hidden')
document.getElementById('unfinished-ibd-indicator').classList.add('animate-spin')
document.getElementById('connected-icon').classList.add('hidden')
console.log(count)
// Show message only once
if (count === 0 ) {
showNotification(`{{ _("Your Bitcoin node is syncing. Check the progress by clicking on the name of the node.") }}`)
}
count++
setTimeout(checkSyncStatus, 5*60000);
}
}
</script>

View file

@ -4,14 +4,6 @@
<div id="side-content" class="h-screen bg-dark-900 border-r border-dark-700 border-solid w-[300px] min-w-[300px] text-white select-none">
<nav class="flex flex-col justify-between max-h-screen h-full">
<div class="p-3 bg-dark-900">
{% if specter.info.get("initialblockdownload") %}
<!-- <p class="warning" data-style="margin-top: 30px;"><img src="{{ url_for('static', filename='img/info_sign.svg') }}" data-style="width: 20px;"/><br>{{ _("Bitcoin Core is still syncing...")}}<br>{{ _('(data might be outdated)')}}</p> -->
{% endif %}
{% if specter.network_info.version < 200000 %}
<!-- <p class="warning" data-style="margin-top: 30px;"><img src="{{ url_for('static', filename='img/warning_sign.svg') }}" data-style="width: 20px;"/><br>{{ _("Bitcoin Core version is outdated.")}}<br>{{ _('Some features might not work...')}}<br>{{ _("(minimum required: v20.0.0).") }}</p> -->
{% endif %}
<!-- Connection -->
<div class="h-11 flex flex-row justify-between items-center overflow-hidden mb-3 bg-dark-700 rounded-xl shadow-md hover:bg-dark-800 group">
{% include specter.node.node_connection_template() %}

View file

@ -1,4 +1,13 @@
<div class="bg-dark-800 flex flex-col">
{% if specter.info.get("initialblockdownload") == True %}
{% set verificationprogress = specter.info.get("verificationprogress") * 100 %}
<div class="flex flex-col p-4 mt-3 mb-4">
<p>{{ _("Bitcoin Core is still syncing")}}<p>
<div class="w-full bg-dark-700 rounded-lg">
<div class="bg-dark-600 text-xs text-white text-center p-2 leading-none {% if verificationprogress < 100 %} rounded-l-md {% else %} rounded-md {% endif %}" style="width: {{ verificationprogress | round | int }}%">{{ verificationprogress | round | int }}%</div>
</div>
</div>
{% endif %}
{% if specter.chain %}
<table>
<tr> <td data-style="text-align: left;">{{ _("Network") }}:</td> <td data-style="text-align: right;" id="node-info-specter-chain">{{specter.chain}}</td> </tr>
@ -16,7 +25,16 @@
<tr> <td data-style="text-align: left;">{{ _("Prune target size") }}:</td> <td data-style="text-align: right;">{{specter.info['prune_target_size']}}</td> </tr>
{% endif %}
</table>
{% if specter.bitcoin_core_version_raw < 200000 %}
<div class="flex p-4 m-3 mb-4 bg-dark-600 rounded-lg" role="alert">
<svg class="flex-shrink-0 inline w-6 h-6 mr-3" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><!--Generated by IJSVG (https://github.com/iconjar/IJSVG)--><path d="M32.0022,55.9844l-1.04907e-06,-2.84217e-14c-13.2548,-5.79387e-07 -24,-10.7452 -24,-24c5.79387e-07,-13.2548 10.7452,-24 24,-24c13.2548,5.79387e-07 24,10.7452 24,24l2.13163e-14,-1.04907e-06c0,13.2548 -10.7452,24 -24,24Zm1.5,-32l3.82016e-08,-2.71086e-09c-1.79133,0.127116 -3.35165,-1.21031 -3.5,-3l6.50646e-08,-7.8524e-07c0.148296,-1.78972 1.70866,-3.12717 3.5,-3l-5.85322e-09,4.15529e-10c1.79134,-0.127171 3.3517,1.21028 3.5,3l-5.57104e-08,6.72365e-07c-0.148295,1.78976 -1.70872,3.12723 -3.5001,3Zm1.4206,6.7635l-2.7853,10.5093l-8.37179e-08,3.15667e-07c-0.270677,1.02062 0.262417,2.08281 1.2425,2.4757l-5.91015e-08,-2.36708e-08c0.379505,0.151996 0.628277,0.519688 0.6282,0.9285v0.323l8.52651e-14,4.05e-07c0,0.551933 -0.447167,0.999503 -0.9991,1h-2.9891l-4.04477e-08,1.96948e-10c-1.65938,0.00807986 -3.01111,-1.33056 -3.01919,-2.98993c-0.00127082,-0.260991 0.0314695,-0.521035 0.0973943,-0.773566l2.7852,-10.5094l2.79671e-08,-1.05468e-07c0.270652,-1.02067 -0.262537,-2.08289 -1.2427,-2.4757l-2.20542e-08,-8.83296e-09c-0.379505,-0.151996 -0.628276,-0.519688 -0.6282,-0.9285v-0.3229l-1.77636e-14,-9.40042e-08c-8.33514e-08,-0.552011 0.447289,-0.999614 0.9993,-1h2.9887l1.06497e-07,-5.28143e-10c1.65943,-0.00822943 3.01133,1.33033 3.01956,2.98976c0.00129458,0.261047 -0.0314356,0.52115 -0.0973639,0.773738Z" fill="currentColor" fill-rule="evenodd"></path></svg>
<span class="sr-only">Info</span>
<div>
<span class="font-medium">Info: </span>
{{ _("Your Bitcoin Core version is outdated.")}}<br>{{ _('Some features might not work.')}}<br>{{ _("(minimum required: v20.0.0)") }}
</div>
</div>
{% endif %}
<div class="px-3 pb-4">
<p class="text-center text-sm m-4" id="total_supply"></p>
<div onclick="fetchTotalSupply()" class="button mb-3">

View file

@ -293,7 +293,6 @@ class User(UserMixin):
):
wallet_manager = WalletManager(
self.specter.bitcoin_core_version_raw,
wallets_folder,
self.specter.rpc,
self.specter.chain,

View file

@ -41,8 +41,10 @@ class SpectrumService(Service):
def spectrum_node(self):
"""Iterates all nodes and returns the spectrum Node or None if it doesn't exist"""
for node in app.specter.node_manager.nodes.values():
if (
node.fqcn
hasattr(node, "fqcn")
and node.fqcn
== "cryptoadvance.specterext.spectrum.spectrum_node.SpectrumNode"
):
return node

View file

@ -1,6 +1,9 @@
{% set node = specter.node %}
{% if specter.node.is_running %}
<div class="px-3 py-2 h-full min-w-0 grow flex items-center cursor-pointer" id="active-node" onclick="showPageOverlay('node-info-popup', {top: '64px', left: '16px'});document.getElementById('side-content').classList.remove('active');" >
{% if specter.info.get("initialblockdownload") %}
<p class="text-white">{{ _("Spectrum is still syncing...")}}<br>{{ _('(data might be outdated)')}}</p>
{% endif %}
<svg class="w-7 h-7 text-white" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><!--Generated by IJSVG (https://github.com/iconjar/IJSVG)--><path d="M54.5946,27.4366l5.66603e-08,-5.66066e-08c-0.745305,0.744598 -1.9414,0.780481 -2.73,0.0819001l1.11948e-07,9.86842e-08c-11.3381,-9.99478 -28.3408,-9.99478 -39.6789,-1.97368e-07l4.1375e-08,-3.6659e-08c-0.788564,0.698682 -1.98474,0.662797 -2.73,-0.0818999l-2.831,-2.8295l4.78383e-08,4.78239e-08c-0.782846,-0.782609 -0.783038,-2.05166 -0.000429116,-2.83451c0.0255969,-0.0256047 0.0518829,-0.0505111 0.078829,-0.0746918l-1.08875e-06,9.72979e-07c14.4215,-12.888 36.2226,-12.888 50.6441,-1.94596e-06l-4.18137e-09,-3.75185e-09c0.823858,0.73923 0.892463,2.00636 0.153233,2.83022c-0.0242251,0.0269984 -0.0491787,0.0533343 -0.0748332,0.0789783Zm-17.6311,13.896l1.74829e-07,9.87123e-08c1.00267,0.56613 1.35656,1.8379 0.79043,2.84057c-0.0942659,0.166954 -0.21111,0.320108 -0.34723,0.45513l-3.6928,3.6684l2.2751e-08,-2.25368e-08c-0.943884,0.934995 -2.46482,0.934995 -3.4087,4.50735e-08l-3.6928,-3.6684l-6.02819e-08,-5.98007e-08c-0.8175,-0.810974 -0.82279,-2.13111 -0.0118161,-2.94861c0.134967,-0.136053 0.288047,-0.252849 0.454916,-0.347088l2.96297e-07,-1.66902e-07c3.07549,-1.7324 6.83251,-1.7324 9.908,3.33805e-07Zm-14.8508,-3.792l3.86385e-08,-3.1954e-08c-0.835403,0.690877 -2.05882,0.634623 -2.8273,-0.13l-2.3847,-2.4084c-1.1739,-1.1855 -1.14,-2.6381 -0.1975,-3.4266l9.62086e-07,-8.05987e-07c8.84671,-7.41133 21.7347,-7.41133 30.5814,1.61198e-06l-5.08261e-08,-4.40698e-08c0.88879,0.770643 0.984568,2.11588 0.213926,3.00467c-0.0332999,0.0384052 -0.0679639,0.075606 -0.103925,0.111531l-2.6883,2.7151l2.63897e-08,-2.62774e-08c-0.768944,0.765671 -1.99317,0.823532 -2.8309,0.1338l-1.43462e-07,-1.17058e-07c-5.75168,-4.69307 -14.0109,-4.69307 -19.7626,2.34116e-07Z" fill="currentColor" fill-rule="evenodd"></path></svg>
<div class="ml-2 text-lg min-w-0 truncate">{{ _("Bitcoin Network") }}</div>
<div class="hidden" id="node-info-popup">{% include node.node_info_template() %}</div>

View file

@ -3,17 +3,19 @@ import code
import json
import logging
import os
import shutil
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.node import Node
from cryptoadvance.specter.process_controller.bitcoind_controller import (
BitcoindPlainController,
)
@ -209,7 +211,7 @@ def node(empty_data_folder, bitcoin_regtest):
if not os.path.isdir(nodes_folder):
os.makedirs(nodes_folder)
nm = NodeManager(data_folder=nodes_folder)
node = nm.add_external_node(
node: Node = nm.add_external_node(
"BTC",
"Standard node",
False,
@ -219,8 +221,8 @@ def node(empty_data_folder, bitcoin_regtest):
bitcoin_regtest.rpcconn.rpcport,
bitcoin_regtest.rpcconn._ipaddress,
"http",
"standard_node",
)
assert node.rpc.test_connection()
return node
@ -240,7 +242,6 @@ def node_with_different_port(empty_data_folder, bitcoin_regtest):
18333,
bitcoin_regtest.rpcconn._ipaddress,
"http",
"satoshis_node",
)
return node
@ -290,119 +291,15 @@ 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"
}
]
}
"""
)
shutil.copy2(
"./tests/misc_testdata/trezor_device.json",
empty_data_folder + "/devices/trezor.json",
)
shutil.copy2(
"./tests/misc_testdata/specter_device.json",
empty_data_folder + "/devices/specter.json",
)
return empty_data_folder # no longer empty, though
@ -540,9 +437,11 @@ def specter_regtest_configured(bitcoin_regtest, devices_filled_data_folder, node
"allow_threading_for_testing": False,
},
}
specter = Specter(
specter: Specter = Specter(
data_folder=devices_filled_data_folder, config=config, checker_threads=False
)
assert specter.active_node_alias == "bitcoin_core"
assert specter.node_manager.active_node.alias == "bitcoin_core"
assert specter.chain == "regtest"
# Create a User
someuser = specter.user_manager.add_user(

View file

@ -184,18 +184,88 @@ def funded_taproot_wallet(
return funded_taproot_wallet
@pytest.fixture
def wallet(devices_filled_data_folder, device_manager, node):
def create_trezor_wallet_with_account(
devices_filled_data_folder,
device_manager,
node,
account_number: int,
checkbalance=True,
):
"""An ordinary wallet without private keys"""
wm = WalletManager(
200100,
devices_filled_data_folder,
node._get_rpc(),
"regtest",
device_manager,
)
device = device_manager.get_by_alias("trezor")
device: Device = device_manager.get_by_alias("trezor")
wallet_name = f"test_wallet_{random.randint(0, 999999)}"
wm.create_wallet(wallet_name, 1, "wpkh", [device.keys[5]], [device])
wallet = wm.wallets[wallet_name]
ss_segwit_index = account_number * 4 + 1
assert device.keys[ss_segwit_index].derivation.startswith(
f"m/84h/1h/{account_number}h"
), f"At index ss_segwit_index has weird derivation {device.keys[ss_segwit_index].derivation}"
wm.create_wallet(wallet_name, 1, "wpkh", [device.keys[ss_segwit_index]], [device])
wallet: Wallet = wm.wallets[wallet_name]
if checkbalance:
assert (
wallet.rpc.getbalance() == 0
), f"account {account_number} does have a non-zero balance: {wallet.rpc.getbalance()}"
return wallet
@pytest.fixture
def trezor_wallet_acc0(devices_filled_data_folder, device_manager, node):
"""This wallet might have a nonzero balance"""
return create_trezor_wallet_with_account(
devices_filled_data_folder, device_manager, node, 0, checkbalance=False
)
# raise Exception("Do not use this fixture!")
@pytest.fixture
def trezor_wallet_acc1(devices_filled_data_folder, device_manager, node):
return create_trezor_wallet_with_account(
devices_filled_data_folder, device_manager, node, 1
)
@pytest.fixture
def trezor_wallet_acc2(devices_filled_data_folder, device_manager, node):
return create_trezor_wallet_with_account(
devices_filled_data_folder, device_manager, node, 2
)
@pytest.fixture
def trezor_wallet_acc3(devices_filled_data_folder, device_manager, node):
return create_trezor_wallet_with_account(
devices_filled_data_folder, device_manager, node, 3
)
@pytest.fixture
def trezor_wallet_acc4(devices_filled_data_folder, device_manager, node):
return create_trezor_wallet_with_account(
devices_filled_data_folder, device_manager, node, 4
)
@pytest.fixture
def trezor_wallet_acc5(devices_filled_data_folder, device_manager, node):
return create_trezor_wallet_with_account(
devices_filled_data_folder, device_manager, node, 5
)
@pytest.fixture
def trezor_wallet_acc6(devices_filled_data_folder, device_manager, node):
return create_trezor_wallet_with_account(
devices_filled_data_folder, device_manager, node, 6
)
@pytest.fixture
def trezor_wallet_acc7(devices_filled_data_folder, device_manager, node):
return create_trezor_wallet_with_account(
devices_filled_data_folder, device_manager, node, 7
)

View file

@ -0,0 +1,41 @@
{
"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"
}
]
}

View file

@ -0,0 +1,360 @@
{
"name": "Trezor",
"alias": "mytrezor",
"type": "trezor",
"keys": [
{
"original": "upub5EKoQv21nQNkhdt4yuLyRnWitA3EGhW1ru1Y8VTG8gdys2JZhqiYkhn4LHp2heHnH41kz95bXPvrYVRuFUrdUMik6YdjFV4uL4EubnesttQ",
"fingerprint": "1ef4e492",
"derivation": "m/49h/1h/0h",
"type": "sh-wpkh",
"purpose": "#0 Single Sig (Nested)",
"xpub": "tpubDDCDr9rSwixeXKeGwAgwFy8bjBaE5wya9sAVqEC4ccXWmcQxY34KmLRJdwmaDsCnHsu5r9P9SUpYtXmCoRwukWDqmAUJgkBbjC2FXUzicn6"
},
{
"original": "vpub5Y35MNUT8sUR2SnRCU9A9S6z1JDACMTuNnM8WHXvuS7hCwuVuoRAWJGpi66Yo8evGPiecN26oLqx19xf57mqVQjiYb9hbb4QzbNmFfsS9ko",
"fingerprint": "1ef4e492",
"derivation": "m/84h/1h/0h",
"type": "wpkh",
"purpose": "#0 Single Sig (Segwit)",
"xpub": "tpubDC5EUwdy9WWpzqMWKNhVmXdMgMbi4ywxkdysRdNr1MdM4SCfVLbNtsFvzY6WKSuzsaVAitj6FmP6TugPuNT6yKZDLsHrSwMd816TnqX7kuc"
},
{
"original": "Upub5Tk9tZtdzVaTGWtygRTKDDmaN5vfB59pn2L5MQyH6BkVpg2Y5J95rtpQndjmXNs3LNFiy8zxpHCTtvxxeePjgipF7moTHQZhe3E5uPzDXh8",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/0h/1h",
"type": "sh-wsh",
"purpose": "#0 Multisig Sig (Nested)",
"xpub": "tpubDFiVCZzdarbyfdVoh2LJDL3eVKRPmxwnkiqN8tSYCLod75a2966anQbjHajqVAZ97j54xZJPr9hf7ogVuNL4pPCfwvXdKGDQ9SjZF7vXQu1"
},
{
"original": "Vpub5naRCEZZ9B7wCKLWuqoNdg6ddWEx8ruztUygXFZDJtW5LRMqUP5HV2TsNw1nc74Ba3QPDSH7qzauZ8LdfNmnmofpfmztCGPgP7vaaYSmpgN",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/0h/2h",
"type": "wsh",
"purpose": "#0 Multisig Sig (Segwit)",
"xpub": "tpubDFiVCZzdarbyk8kE65tjRhHCambEo8iTx4xkXL8b33BKZj66HWsDnUb3rg4GZz6Mwm6vTNyzRCjYtiScCQJ77ENedb2deDDtcoNQXiUouJQ"
},
{
"original": "upub5EKoQv21nQNkkbeX7RLSUgcjnR6nTWFudhmGo5Nxq48FqkKxgPBkWiAwKazG3cd1KjENnTbeGJtNB7iqyuH4QXpxFnVvsRtbGkN2Fg9wStD",
"fingerprint": "1ef4e492",
"derivation": "m/49h/1h/1h",
"type": "sh-wpkh",
"purpose": "#1 Single Sig (Nested)",
"xpub": "tpubDDCDr9rSwixeaHQj4ggQJsEcdSdnGkjTvfvEVp7mJz1nkLSMWaXXXLpBdEwoZqY1LZ7heTuCBPn4XA49XrNLggL3vQLWJh1Hft9NBQDrZ29"
},
{
"original": "vpub5Y35MNUT8sUR6SVwH1nkeiUwWky58XopeJaFWLwJfQj2GgcGFbwmkmhp3yBMEVTqw2xfvzpvMqSUzCXiGZdxAiWyvmxyyrErPBwPoKSJzus",
"fingerprint": "1ef4e492",
"derivation": "m/84h/1h/1h",
"type": "wpkh",
"purpose": "#1 Single Sig (Segwit)",
"xpub": "tpubDC5EUwdy9WWq4q52PvM6Gp1KBpMd1AHt2ACzRgnDmLEg8AuRq97z9LgvLRBJkoivYDjC3XXupFydSxFT6pKDedLUj478qCY4Wbf6LZHaEuo"
},
{
"original": "Upub5SNDKzLBXW3QBrbxK14LDuVtxxR2MWYxChNjShS338xKtx1tgwp1tZoM5Prts4J2DXpPKATS1vfntAHrorEXZA7VFBCaUNhemfWYejJRPHg",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/1h/1h",
"type": "sh-wsh",
"purpose": "#1 Multisig Sig (Nested)",
"xpub": "tpubDELYdzSB7s4vayCnKbwKE1my6BukxQLvBPt2EAuJ9J1TBMZNkjmWp5afaLrxpqz7ztdjJaks3oAz731Q4aArgpVv5KvkWEMMH521zaj118Z"
},
{
"original": "Vpub5mCUdf16gBat7LhtqCQne7zV3en2XV2nodPGR85jhHqv9E4A7UXNnZ9UsA5x3H2fxhc38t5gXcp1XvqGp8ASa3ErgXhyFKNAt1cSMdWGXNN",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/1h/2h",
"type": "wsh",
"purpose": "#1 Multisig Sig (Segwit)",
"xpub": "tpubDELYdzSB7s4vfA7c1SW9S9B3zv8KBkqFsDNLRCf7RSXANXnQvcKK61GfLu8S1A4rLRJaNpnZ6pxesWwFM9gkuTwgeLjihGCP7h4GJuCxsd3"
},
{
"original": "upub5EKoQv21nQNknik1KU1syMoEgo8NwkDkYeX6hYSTexRhSdCXqNHHHKsd9W1o8qvqRgqfBP9KmuWBxuMWvmCMqMSicMBqwCJGGa5TVau3eD1",
"fingerprint": "1ef4e492",
"derivation": "m/49h/1h/2h",
"type": "sh-wpkh",
"purpose": "#2 Single Sig (Nested)",
"xpub": "tpubDDCDr9rSwixecQWDGjMqoYR7XpfNkzhJqcg4QHBG8tKEMDJvfZd4HxWsT9yLf4qqSWiz3PSsgzPtJwgpUiHe7VwpGy2RNTQxfhroRPqfBfM"
},
{
"original": "vpub5Y35MNUT8sUR85w9G2bVdZQrHWXLdxPcsi75fgnBWkJNATPfgbv4Qpabv59fMTG5cVdePeveifFk5TPePFdU7jvPm1qb9fVYcxXKXSvUtgz",
"fingerprint": "1ef4e492",
"derivation": "m/84h/1h/2h",
"type": "wpkh",
"purpose": "#2 Single Sig (Segwit)",
"xpub": "tpubDC5EUwdy9WWq6UWENw9qFewDxZutWasgFZjpb2d6cfp21wgqG96GoPZiCX9csmXADgQAWBdeB5ntYD7PDWJjbejtZHyk11nkkNF24i7RnLV"
},
{
"original": "Upub5TVK2qPsm6eMaSm9JpaDCqGACPHtXMmn6reTthgdvpqnV7qcyHD3awVpEawcMxRKDbEyvMLr1pKNWFAQYBhzfwzrAo8BpMefcymgMsYvdw4",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/2h/1h",
"type": "sh-wsh",
"purpose": "#2 Multisig Sig (Nested)",
"xpub": "tpubDFTeLqVsMTfsyZMyKRTCCwYEKcnd8FZk5Z9kgB9u2ytumXP735AYWTH8jXwgKk7Qzx4KumeH3gpZj7swnueKocPGzwrMrDJN8PH9hh7AnfJ"
},
{
"original": "Vpub5nKaLW4nunBqSzRVKf4oTaGySWh8kTJUd3wQHvVMnNp4ksukeC7spEmCyKRfAEHz31UPAkBGQwrEwS5iFpQbuNB4EuigfoTLTSwYmXXP2JK",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/2h/2h",
"type": "wsh",
"purpose": "#2 Multisig Sig (Segwit)",
"xpub": "tpubDFTeLqVsMTfszoqCVuAAFbTYPn3RQj6wgdvUJ14jWXVJzBe1TKup7gtPT4U987LAQjAvQgt8z9ztH2BgnqvvEnstCikS7kHYh8PNimvL3mt"
},
{
"original": "upub5EKoQv21nQNkquuCusEL8ofnQaM4V8rsdzPuWe48FD4rRWvmF61hEqVMwwNje2zxpma7ju39zT1ub1hogbGaKdAvmMfPJtdqCSR5FovbUSF",
"fingerprint": "1ef4e492",
"derivation": "m/49h/1h/3h",
"type": "sh-wpkh",
"purpose": "#3 Single Sig (Nested)",
"xpub": "tpubDDCDr9rSwixefbfQs8aHxzHfFbt4JPLRvxYsDNnvj8xPL73A5HMUFU8cFbLHAFuxqbTSbuLhuXubw437EYMrbmg2RyVxk9kXbaCRBYbHftq"
},
{
"original": "vpub5Y35MNUT8sURA6hs4HJw67NHTFmMsmjDSu9pQS3abpJDrkUdSpXn7n5WEVHvU2uuJWAKHSkdpDYKuFBvhQAJ2xtezVurh2WfBMsP8HorJxH",
"fingerprint": "1ef4e492",
"derivation": "m/84h/1h/3h",
"type": "wpkh",
"purpose": "#3 Single Sig (Segwit)",
"xpub": "tpubDC5EUwdy9WWq8VGxBBsGiCtf8K9ukQDGpknZKmtVhjosiEmo2MhzWM4cWwHszMAyugvqPyTdGe5UMzufXeqZWsi9nn41YNosJmb5fYoCqFy"
},
{
"original": "Upub5TYty5hhC1kpHss1TPBrhzsP7Jy2UScv6VF8HeEN1jbvVhryoPsrN9sBGxWjcvJp4TZLdkaqKmEvLqFP2CVtnSFw4Vv4HcG1pcp66PCAxnr",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/3h/1h",
"type": "sh-wsh",
"purpose": "#3 Multisig Sig (Nested)",
"xpub": "tpubDFXEH5ognNnLgzTqTz4qi79TEYTm5LQt5BkR57hd7tf3n7QTsBqMHfeVmuWoahzuqpNgdAtGMdk7ZhxvGvSDv6eMteeEKTuiL2KZS7oDNBc"
},
{
"original": "Vpub5nPAGkNcLhJJCHZhGRt47wH5mBab9m5PfJmUjSazw1XAkbax3vG9RCJQVMei2Z84PgirRvTUHU93uUXynBUXwiiPVT4VwG45Xas6KWDPh5T",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/3h/2h",
"type": "wsh",
"purpose": "#3 Multisig Sig (Segwit)",
"xpub": "tpubDFXEH5ognNnLk6yQSfyQuxTeiSvsp2sritkYjXANfACQyuKCs445ieRay6hBzSAEmQRPfsALrgHhF4dxKCzrH9RDTG6FPCtHmGJvGi6sSC8"
},
{
"original": "upub5EKoQv21nQNktcVEn7ormrgocRuFrKvU6wum7ZRfXcTKYtfUfG1RXihAgq4cWyTVM4smEV5VTejiCXot2akjMDr6sYCghMnqUSVuNctuCbm",
"fingerprint": "1ef4e492",
"derivation": "m/49h/1h/4h",
"type": "sh-wpkh",
"purpose": "#4 Single Sig (Nested)",
"xpub": "tpubDDCDr9rSwixeiJFSjP9pc3JgTTSFfaQ2Pv4ipJAU1YLrTUmsVTMCYMLQzV2A3CNVMtm66VP3NjdQYa9BaXr1dNMCYA3G8cuXsaHFJMZNVDP"
},
{
"original": "vpub5Y35MNUT8sUREDYApTNH7Y188zckaDj6NHaK8tkgRFLsjF2a8cQRXrSzgvJbMoEgHgqZm8259DGsHUXmUA8P3FKEFkjNeMh7aMaivei27JR",
"fingerprint": "1ef4e492",
"derivation": "m/84h/1h/4h",
"type": "wpkh",
"purpose": "#4 Single Sig (Segwit)",
"xpub": "tpubDC5EUwdy9WWqCc7FwMvcjdXVp41JSrD9k9D44EbbXArXajKji9advRS6yNJYt7Vktsc5sej4bdp1kEFWJQoeXA8j42sXVhzKhmJRTmvjiZL"
},
{
"original": "Upub5TNPzsZrx1ZhKLgEqViJqi6cWUrUyQdPpCwFQ1fbBqtVBGC6CwCscoqnH34TEGGbWnEL4C2eJDPnsqcM2sGnPZXuyH7Dy511H3qnCVK7P5f",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/4h/1h",
"type": "sh-wsh",
"purpose": "#4 Multisig Sig (Nested)",
"xpub": "tpubDFLjJsfrYNbDiTH4r6bHqpNgdiMDaJRMnuSYBV8rHzwcTfjaGjANYKd6mz4XC3xhJ93g3cL5L5tz6iKtHbD7XDvLoRqPzvehnTMFYEJv8rh"
},
{
"original": "Vpub5nCfJYEn6h7BDYF3ZiuhYa6RgEb6FnVb2ewjDpiYEWKyQVuuHuise1Ygbo1r5EggzF1zfyDSCmywMieJKCR3q1Ergim9UQSUZEWrpGkeBvk",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/4h/2h",
"type": "wsh",
"purpose": "#4 Multisig Sig (Segwit)",
"xpub": "tpubDFLjJsfrYNbDmMekjy14LbGzdVwNv4J46EvoDuHuxf1DdoeA73WowTfs5Y4L37isMxiXuuvJmz8ahJkGrDwNARwgeXntvMGgnuxgmXxNsnU"
},
{
"original": "upub5EKoQv21nQNkx9AMEjSYNDSic2p59T2uGrJQYD5URuDZbaF3itK2mjDD9mJutuYmVnzjQuSSCvxE6xjpwXCY8YNpzr3GLqSjWH4n2Cuokvy",
"fingerprint": "1ef4e492",
"derivation": "m/49h/1h/5h",
"type": "sh-wpkh",
"purpose": "#5 Single Sig (Nested)",
"xpub": "tpubDDCDr9rSwixempvZBznWCQ4bT4M4xhWTZpTNEwpGuq76WAMSZ5eonMrTTRGTR8TmWct4Gujz81qvT158VUHpQgsvfTsqn6ZRuQr7x22dD1E"
},
{
"original": "vpub5Y35MNUT8sURH2nXVtZGcoWLn6CRrkNkFCoYpvbeEvxFqigBvaT8ExBqPPNVzrnCjBq9meh2Adci4WmLrmDBTfpztnpxq1Sa3sAcMK3ub5d",
"fingerprint": "1ef4e492",
"derivation": "m/84h/1h/5h",
"type": "wpkh",
"purpose": "#5 Single Sig (Segwit)",
"xpub": "tpubDC5EUwdy9WWqFRMcco7cEu2iT9ayjNrod4SHkGSZLrTuhCyMW7dLdXAwfqNTXB3HLNbftBQ1d49rXGV5h1tSwaeVh4y7gMjnBGtJtRw2UE8"
},
{
"original": "Upub5SbGvWuQjHSN9SNQSCQ7x1EeV5iidqbmjzqR6aBmThDNCQqWSZ3R8HmY8vLTcwYUt5PLSZC5hjp3VHTq9hek56Qkka8pPqFNGKAoJRddAPg",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/5h/1h",
"type": "sh-wsh",
"purpose": "#5 Multisig Sig (Nested)",
"xpub": "tpubDEZcEX1QKeTtYYyESoH6x7WicKDTEjPjihLht3f2ZrGVUpNzWLzv3oYrdsLXajEafSCgRyVWjcKEiABNQRb5CkoBairzRgu4migGeGKXkCR"
},
{
"original": "Vpub5mRYEBaKsxyr2NFZRzrstJ9EKSMsfT7tLWQv6Cm1DNN4XMQJkTmQvVnvp6cEqb9vTvEF8xZTmGSGLevopuuaoCxWnD9m26NTSXdHMTYwU3V",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/5h/2h",
"type": "wsh",
"purpose": "#5 Multisig Sig (Segwit)",
"xpub": "tpubDEZcEX1QKeTtaBfGcExEgKKoGhiAKivMQ6Pz6HLNwX3Jkf8ZZbZMDwv7HqeioUC6qdvnNuGLLUaugF2nMwRu8dfLk2BWU3CfgD57JiADegZ"
},
{
"original": "upub5EKoQv21nQNm1DqJZU9wsGAQHT9vZofiwYVKih5SNWA5TDCKmiPzabATbmeFcvQQ7MKKPFHovRqnnVX9a9KZYQzXiLFKGDLbGoQ8AuuMGc9",
"fingerprint": "1ef4e492",
"derivation": "m/49h/1h/6h",
"type": "sh-wpkh",
"purpose": "#6 Single Sig (Nested)",
"xpub": "tpubDDCDr9rSwixepubWWjVuhSnH8UgvP49HEWeHRRpErS3cMoJibujmbDohuRbo99KQ8BCeFFbMqWjV8XrT86QqpZVdNx5thUTHfwBU6fdP51e"
},
{
"original": "vpub5Y35MNUT8sURKFuKEy9mp1mH9AwEsNMDmF1JzvmD61CkPmsiL2ufvzKTDNNsG7QLKN6RwNSowYVW5g7uJqMFL9thVW9QChJ2KxXagZ2UdJE",
"fingerprint": "1ef4e492",
"derivation": "m/84h/1h/6h",
"type": "wpkh",
"purpose": "#6 Single Sig (Segwit)",
"xpub": "tpubDC5EUwdy9WWqHeUQMsi7S7HepEKnjzqH96e3vGc8BviQFGAsua5tKZJZVpNpnRfQvYrx3u9oPy2eYRqe962Wp4iCHnHZ43bETNFHDfyLoem"
},
{
"original": "Upub5SibFFj1EPmNEfxxZPKFet6rGVDAdiAXStukEA6AywfBgFrhoQAkkVjYgCbk4VXYUEypFmCsuR5QZSnRtF7s3pREQkKkHCkeHy8oM8GiSk5",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/6h/1h",
"type": "sh-wsh",
"purpose": "#6 Multisig Sig (Nested)",
"xpub": "tpubDEgvZFpzpkntdnZnZzCEezNvPihuEbxVRbR31dZS66iJxfQBsC8Fg1WsB9bp2HDeFboAFBWJwHabnKVy8y4CBUofEu3vK4QLoNeGgwgVMZi"
},
{
"original": "Vpub5mYrYvPvP5Jr8draW5U6M7m9xCPukVXQtTCp6h5HKDQRQb2W8Ce2Vsc4KbQRuTuaJhyPSJRB7koQ6sTtM4jj4Mi6GN1vHAXZSdQ8SG55ihw",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/6h/2h",
"type": "wsh",
"purpose": "#6 Multisig Sig (Segwit)",
"xpub": "tpubDEgvZFpzpkntgTGHgKZT98wiuTkCQmKsx3Bt6mef3N5fdtkkwLRxoKjEoLSusLwkgRfvgF83gxx3STZrt6G3PnQvEB3fj7MmgJqxPWbWPxa"
},
{
"original": "upub5EKoQv21nQNm1q8NAC2rewfMBmq19fvRPM7mwo1A7agcwMHGBEfD6RWz5ucCZSpeQXGjLJPFxN7K838YyVtqwzc1DKYfxtv8UatsPoYeTHC",
"fingerprint": "1ef4e492",
"derivation": "m/49h/1h/7h",
"type": "sh-wpkh",
"purpose": "#7 Single Sig (Nested)",
"xpub": "tpubDDCDr9rSwixeqWta7TNpV8HE2oMzxvPygKGjeXjxbWa9qwPf1Rzz74AEPZZk5fjeRMA4CJgosT11U5TrXSz8E976swPFQA2psigDKUJemiv"
},
{
"original": "vpub5Y35MNUT8sURLyMhyndVrEKhoKYv3hzjhYFoMGTdK6nm3jtXkBG1u45WrLf2u5fKRyR64Uao5Xs1DnGNgvrpAVv8xojSLET69caKBg7ypie",
"fingerprint": "1ef4e492",
"derivation": "m/84h/1h/7h",
"type": "wpkh",
"purpose": "#7 Single Sig (Segwit)",
"xpub": "tpubDC5EUwdy9WWqKMvo6hBqUKr5UNwTvLUo5PtYGcJYR2JQuEBhKiSEHd4d8nezRPvQ3ABcB1HnXxQ9gXz7XBY5eQjdm5sbBakJH2J1iq8UvZc"
},
{
"original": "Upub5T4M7Sm4okSNqdSmt1TxbsmWpLczVoot5cmLjw7EbSUoQ13Yh622XmvjTphUgr2MMqWymfGijavxRPo3ZnvRiPteKHSWVFz5KBgMdBVGHMz",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/7h/1h",
"type": "sh-wsh",
"purpose": "#7 Multisig Sig (Nested)",
"xpub": "tpubDF2gRSs4Q7TuEk3btcLwbz3awa7j6hbr4KGdXQaVhbXvgQb2ksyXTHi3xmhYediT9CLKm5a9mTS9eGWapWrkr4H59SAgX7dmpbBpy24rrEq"
},
{
"original": "Vpub5mtcR7RyxRyrhxjb1ZVmHvzadnmMk6k8PvfyvtosdaA1jVrnCGv73wmkKWk3v8SBrBDNz98wkLAVTmeSfp9xPFKeo7KdM5TbZPCzw7vZoTt",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/7h/2h",
"type": "wsh",
"purpose": "#7 Multisig Sig (Segwit)",
"xpub": "tpubDF2gRSs4Q7TuFn9JBob85xB9b47eQNYbTWf3vyPFMiqFxob31Qi3MPtvoFnXt1UNDtuvE5qpKYK8oMkRCqgGig2UkvMNo2Hoo4eptLhCiST"
},
{
"original": "upub5EKoQv21nQNm4XeNALECoMbF21HFcyfrXQb24kQu1LVq8pFpozWp5Uridk19VLT4o6J7Q6RAauT22crjYtDX8zYPYrrreRbcW6X4EpX9Mpn",
"fingerprint": "1ef4e492",
"derivation": "m/49h/1h/8h",
"type": "sh-wpkh",
"purpose": "#8 Single Sig (Nested)",
"xpub": "tpubDDCDr9rSwixetDQa7baAdYD7s2pFSE9QpNjymV9hVGPN3QNDeBrb67VxwPxh1ZN4ovBSG6iiVzLiNfC36qJoR93VDUhS5giJuEJQAZdV1ip"
},
{
"original": "vpub5Y35MNUT8sURPk9bgmUzqMDNXgyTtNUrTMX56xf4MaYLJhSC8uWGsj1QYeemMAEeJ3dCcUbHSRhpdSsH8cVeD559N6tLpwXKQjJD1eKenwu",
"fingerprint": "1ef4e492",
"derivation": "m/84h/1h/8h",
"type": "wpkh",
"purpose": "#8 Single Sig (Segwit)",
"xpub": "tpubDC5EUwdy9WWqN8igog3LTSjkCkN1kzxuqD9p2JVyTW3zABjMiSgVGHzWq6eisUViuEPij1JGtrEy6Cb1xsAugyteAP2VgHpXY91uYpkixb8"
},
{
"original": "Upub5SAJAYu4nm5wC2JZT4Pq67xQ7vLsVLqYJgoVqLoyQvWDWkw1XpbhyezkrSmfs6qM28gJ8hnHVnmvtaA4j5PNBHxG5NeWneL6TLdWxva7533",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/8h/1h",
"type": "sh-wsh",
"purpose": "#8 Multisig Sig (Nested)",
"xpub": "tpubDE8dUZ14P87Tb8uPTfGp6EEUF9qc6EdWHPJncpHEX5ZLoAUVbcZCuAn5MPmjptXSoVVe885iXfH87SsbyoKhJxLguXNgpVynxk8zJiSXwxa"
},
{
"original": "Vpub5kzZUDZywSdR7T4wKcXz61aTTjHQP6Qd19qGsgwjzg4Gi7Wzf26wpq38tX2Kx9qLvUVZbsvULrLSTuFsKHcMhh5YHTmhF48m2qhqiakyF6e",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/8h/2h",
"type": "wsh",
"purpose": "#8 Multisig Sig (Segwit)",
"xpub": "tpubDE8dUZ14P87TfGUeVrdLt2m2Qzdh3ND64jpLsmX7ipjWwRFFU9tt8HAKNG4ov2sXJCC6qpdLv4V5oVMqrK8g37nNFGoSgzxyGX9ffsxL8Cz"
},
{
"original": "upub5EKoQv21nQNm6Y8Na9Ep6m8H7A6z9PhAoW8dF1BfLBAvuVYb1xgEqaXRYrRpkxe7vXYw1AaRSB1JE9WuKAxvRMoSCRcpae1CRsSRaWbh43y",
"fingerprint": "1ef4e492",
"derivation": "m/49h/1h/9h",
"type": "sh-wpkh",
"purpose": "#9 Single Sig (Nested)",
"xpub": "tpubDDCDr9rSwixevDtaXQamvwk9xBdyxeAj6UHawjvTp74Tp5eyrA21rDAfrWPNHBZ7wMSFsAsyMFtzaBrCs84ChWJXs3TQ1u7tq1DmWERYJhX"
},
{
"original": "vpub5Y35MNUT8sURRVraj7fgYjPgp15hqEAjm8jMW6DBMHKfSywQY2GCoAKqUjSzWXzE6KcWdCgKju4vmiPYEa8L3wtdcPSPe1EGi7EgHABMg2o",
"fingerprint": "1ef4e492",
"derivation": "m/84h/1h/9h",
"type": "wpkh",
"purpose": "#9 Single Sig (Segwit)",
"xpub": "tpubDC5EUwdy9WWqPtRfr2E2Apv4V4UFhreo8zN6RS46TCqKJUEa7ZSRBjJwmBSx2rFJhWP2jjPKCKc5EU7H4pobXri8QfaYVMXUqWxNpM4Thp3"
},
{
"original": "Upub5SZqPpGQLXLrZiASDasMaoUae2fRr6frs17aAWyEpKmnrytb3uDK2TyPJawmuayNdpaheEjLjMBxAfg2j9M2zte5iFwjxSo4tvnuKsryBZW",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/9h/1h",
"type": "sh-wsh",
"purpose": "#9 Multisig Sig (Nested)",
"xpub": "tpubDEYAhpNPvtNNxpmGEBkLaukemGAASzTpqhcrwzSVvUpv9PS57hAowykhoXwqsNfURBQ3df2mmDh9PYPZysHN8Z2WYQfuzJSmQLJNfeBysrh"
},
{
"original": "Vpub5mQ6hUwKVCtLSmZFysmVTUeMfkSgYdwFYipCf6zaLfyXdVRGY4LjUuKvM7fi5zJKuA7VoZ5gKkuHUdjkS5HH81V9owruYMvZM5JPHgpU94i",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/9h/2h",
"type": "wsh",
"purpose": "#9 Multisig Sig (Segwit)",
"xpub": "tpubDEYAhpNPvtNNzaxyA7rrFVpvd1nyCujicJoGfBZx4pemro9XMC8fnMT6priC3sLWGsp33VnYty3vpDqiy6obTSBymktezJkmakkDEwVb9e2"
},
{
"original": "upub5EKoQv21nQNmBMp8zqKWqi4exGTsvswkeDNbexHNursybdRAiYoXprZFy6yfZDwKQgHmLxPpKh9BwNa1fJdyjRcE3a51s11UVCLaRkmbE3U",
"fingerprint": "1ef4e492",
"derivation": "m/49h/1h/10h",
"type": "sh-wpkh",
"purpose": "#10 Single Sig (Nested)",
"xpub": "tpubDDCDr9rSwixf13aLx6fUftgXoHzsk8RJwBXZMh2BPnmWWDXZYk9JqVCWGkwD5SrKRWB6CxhNEn2tHQuKDFjG1a7KiBubJG8AtL7vMZ2ccQc"
},
{
"original": "vpub5Y35MNUT8sURVkrRjbTjecBsrupUkvEJMVu7z6xkYoRMMnorxKeBAPbRjAzk3s8qwPJeFbEWi6BFSuVYV46Z1YnEQJ1wuwMpoA1m4LyZ17p",
"fingerprint": "1ef4e492",
"derivation": "m/84h/1h/10h",
"type": "wpkh",
"purpose": "#10 Single Sig (Segwit)",
"xpub": "tpubDC5EUwdy9WWqU9RWrW25GhiFXyD2dYiMjMXruSofeiw1DH72XrpPYxaY1czhaBPvYa5AN7wWAWiPufDHKJmpVTbjCaA6mHf2vZjTbYS944S"
},
{
"original": "Upub5TmeuYoRtjgjZWavZYFmc7rnHgUWw2kVw6N4Y19u5xjXPu8K5ZM3YT6kZyxwAD2tMHa5tbpJJi1B7zkpzTu6cCE3rRppmFy3BU9DPcLU9AS",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/10h/1h",
"type": "sh-wsh",
"purpose": "#10 Multisig Sig (Nested)",
"xpub": "tpubDFjzDYuRV6iFxdBka98kcE8rQuyFXvYTunsMKUdAC7negJfo9MJYTxt54vy17ziz8ePRt27jLaWNLsUNFBqRjrcUgaYzo7cjgsegjT4EYhP"
},
{
"original": "Vpub5nbvDDUM3REDStVR5hGfkf6DtJAFsWhyg9c2ZAFrBx9sTE16sJeoUe1cWG1UaSZVMn9FFf8hzoQtUMMfLK6Li5AiKgDYS6JSxJ3LuZBRA4K",
"fingerprint": "1ef4e492",
"derivation": "m/48h/1h/10h/2h",
"type": "wsh",
"purpose": "#10 Multisig Sig (Segwit)",
"xpub": "tpubDFjzDYuRV6iFzhu8FwN2YgGnqZWYXnWSjjb6ZEqDv6q7gXjMgSSjn68nz13xYKbfjVqnVbqaa1ZXowTdsLcf3VsYHVFHt38fByVArkpZ5ZP"
}
]
}

View file

@ -178,10 +178,10 @@ def test_addressinfo(caplog, client, funded_ghost_machine_wallet):
res = client.post(
url, data={"address": invalid_address}, follow_redirects=True, headers=headers
)
assert (
res.data.decode()
== '{"error":"Request error for method getaddressinfo: Invalid address format"}\n'
assert res.data.decode().startswith(
'{"error":"Request error for method getaddressinfo'
)
assert res.data.decode().endswith('Invalid address format"}\n')
# send post request with address, not belonging to wallet
# this recreates an edge case, see https://github.com/cryptoadvance/specter-desktop/issues/2000

View file

@ -126,7 +126,6 @@ def test_device_wallets(
):
caplog.set_level(logging.DEBUG)
wm = WalletManager(
200100,
devices_filled_data_folder,
bitcoin_regtest.get_rpc(),
"regtest",

View file

@ -23,8 +23,8 @@ def test_node_manager_basics(
nm = specter_regtest_configured.node_manager
# # Load from disk to get the other two nodes
assert sorted(list(nm.nodes.keys())) == [
"default",
"satoshis_node",
"bitcoin_core",
"node_with_a_different_port",
"standard_node",
]
assert nm.nodes_names == [
@ -39,23 +39,25 @@ def test_node_manager_basics(
"Bitcoin Core",
]
# Checking some standard methods and properties
assert nm.get_by_alias("satoshis_node") == nm.get_by_name(
assert nm.get_by_alias("node_with_a_different_port") == nm.get_by_name(
"Node with a different port"
)
default_node = nm.get_by_alias("default")
satoshis_node = nm.get_by_alias("satoshis_node")
assert nm.default_node() == default_node
default_node = nm.get_by_alias("bitcoin_core")
node_with_a_different_port = nm.get_by_alias("node_with_a_different_port")
assert nm.active_node == default_node
assert specter_regtest_configured.config["active_node_alias"] == "default"
assert specter_regtest_configured.config["active_node_alias"] == "bitcoin_core"
# Switching the node via the node manager does not change the active_node_alias in the config, only specter.update_active_node() does
nm.switch_node("satoshis_node")
assert nm.active_node == satoshis_node
assert specter_regtest_configured.config["active_node_alias"] == "default"
specter_regtest_configured.update_active_node("satoshis_node")
assert specter_regtest_configured.config["active_node_alias"] == "satoshis_node"
assert nm.active_node == satoshis_node
nm.switch_node("node_with_a_different_port")
assert nm.active_node == node_with_a_different_port
assert specter_regtest_configured.config["active_node_alias"] == "bitcoin_core"
specter_regtest_configured.update_active_node("node_with_a_different_port")
assert (
specter_regtest_configured.config["active_node_alias"]
== "node_with_a_different_port"
)
assert nm.active_node == node_with_a_different_port
# Deleting a node
nm.delete_node(satoshis_node, specter_regtest_configured)
nm.delete_node(node_with_a_different_port, specter_regtest_configured)
assert nm.nodes_names == ["Standard node", "Bitcoin Core"]
# Check that with the deletion of the active node the switch to the next node work, the first node in the list, here the Standard node, is switched to
assert specter_regtest_configured.config["active_node_alias"] == "standard_node"
@ -65,9 +67,11 @@ def test_node_manager_basics(
SpecterError,
match="Node with a different port not found, node could not be deleted.",
):
nm.delete_node(satoshis_node, specter_regtest_configured)
with pytest.raises(SpecterError, match="Node alias satoshis_node does not exist!"):
nm.switch_node("satoshis_node")
nm.delete_node(node_with_a_different_port, specter_regtest_configured)
with pytest.raises(
SpecterError, match="Node alias node_with_a_different_port does not exist!"
):
nm.switch_node("node_with_a_different_port")
@pytest.mark.elm
@ -89,10 +93,9 @@ def test_switch_nodes_across_chains(
bitcoin_regtest.rpcconn.rpcport,
bitcoin_regtest.rpcconn._ipaddress,
"http",
"bitcoin_regtest_alias",
)
assert nm.nodes_names == ["", "bitcoin_regtest"]
nm.switch_node("bitcoin_regtest_alias")
assert nm.nodes_names == ["bitcoin_regtest"]
nm.switch_node("bitcoin_regtest")
assert nm.active_node.rpc.getblockchaininfo()["chain"] == "regtest"
nm.add_external_node(
"ELM",
@ -105,6 +108,6 @@ def test_switch_nodes_across_chains(
elements_elreg.rpcconn._ipaddress,
"http",
)
assert nm.nodes_names == ["", "bitcoin_regtest", "elements_elreg"]
assert nm.nodes_names == ["bitcoin_regtest", "elements_elreg"]
nm.switch_node("elements_elreg")
assert nm.active_node.rpc.getblockchaininfo()["chain"] == "elreg"

View file

@ -31,7 +31,6 @@ def test_WalletManager(
node_with_empty_datadir,
):
wm = WalletManager(
200100,
devices_filled_data_folder,
bitcoin_regtest.get_rpc(),
"regtest",
@ -127,7 +126,6 @@ def test_WalletManager_2_nodes(
):
caplog.set_level(logging.INFO)
wm = WalletManager(
200100,
devices_filled_data_folder,
bitcoin_regtest.get_rpc(),
"regtest",
@ -144,27 +142,30 @@ def test_WalletManager_2_nodes(
assert wm.chain == "regtest"
assert wm.working_folder.endswith("regtest")
assert wm.rpc.port == 18543
# Change the rpc - this only works with a different chain!
wm.update(rpc=bitcoin_regtest2.get_rpc(), chain="regtest2")
# Change the rpc - this works differently with a different chain!
# If we use something different that regtest, unfortunately a liquid address gets
# generated.
# So we don't test that scanrio here of different chains. We test the scenario with the same chain.
# but different node
wm.update(rpc=bitcoin_regtest2.get_rpc(), chain="regtest")
# A WalletManager uses the chain as an index
assert list(wm.rpcs.keys()) == [
"regtest",
"regtest2",
] # wm.rpcs looks like this: {'regtest': <BitcoinRpc http://localhost:18543>, 'regtest2': <BitcoinRpc http://localhost:18544>}
assert wm.rpc.port == 18544
assert wm.wallets_names == []
assert wm.chain == "regtest2"
assert wm.working_folder.endswith("regtest2")
assert wm.wallets_names == ["a_test_wallet"]
assert wm.chain == "regtest"
assert wm.working_folder.endswith("test")
second_wallet = wm.create_wallet(
"a_regtest2_test_wallet", 1, "wpkh", [device.keys[5]], [device]
)
# Note: "regtest2" is recognised by the get_network() from embit as Liquid, that is why there is an error in the logs saying the Bitcoin address is not valid since a Liquid address is derived.
assert wm.wallets_names == ["a_regtest2_test_wallet"]
assert len(wm.wallets_names) == 2
assert wm.wallets_names == ["a_regtest2_test_wallet", "a_test_wallet"]
def test_WalletManager_check_duplicate_keys(empty_data_folder):
wm = WalletManager(
200100,
empty_data_folder,
MagicMock(), # needs rpc
"regtest",
@ -231,7 +232,6 @@ def test_wallet_sortedmulti(
bitcoin_regtest, devices_filled_data_folder, device_manager
):
wm = WalletManager(
200100,
devices_filled_data_folder,
bitcoin_regtest.get_rpc(),
"regtest",
@ -285,7 +285,6 @@ def test_wallet_sortedmulti(
def test_wallet_labeling(bitcoin_regtest, devices_filled_data_folder, device_manager):
wm = WalletManager(
200100,
devices_filled_data_folder,
bitcoin_regtest.get_rpc(),
"regtest",
@ -344,7 +343,6 @@ def test_wallet_change_addresses(
bitcoin_regtest, devices_filled_data_folder, device_manager
):
wm = WalletManager(
200100,
devices_filled_data_folder,
bitcoin_regtest.get_rpc(),
"regtest",
@ -354,14 +352,14 @@ def test_wallet_change_addresses(
device = device_manager.get_by_alias("specter")
key = Key.from_json(
{
"derivation": "m/48h/1h/0h/2h",
"original": "Vpub5n9kKePTPPGtw3RddeJWJe29epEyBBcoHbbPi5HhpoG2kTVsSCUzsad33RJUt3LktEUUPPofcZczuudnwR7ZgkAkT6N2K2Z7wdyjYrVAkXM",
"derivation": "m/84h/1h/0h",
"original": "vpub5ZSem3mLXiSJzgDX6pJb2N9L6sJ8m6ejaksLPLSuB53LBzCi2mMsBg19eEUSDkHtyYp75GATjLgt5p3S43WjaVCXAWU9q9H5GhkwJBrMiAb",
"fingerprint": "08686ac6",
"type": "wsh",
"xpub": "tpubDFHpKypXq4kwUrqLotPs6fCic5bFqTRGMBaTi9s5YwwGymE8FLGwB2kDXALxqvNwFxB1dLWYBmmeFVjmUSdt2AsaQuPmkyPLBKRZW8BGCiL",
"type": "wpkh",
"xpub": "tpubDDUotcvrYMUiy4ncDirveTfhmvggdj8nxcW5JgHpGzYz3UVscJY5aEzFvgUPk4YyajadBnsTBmE2YZmAtJC14Q21xncJgVaHQ7UdqMRVRbU",
}
)
wallet = wm.create_wallet("a_second_test_wallet", 1, "wpkh", [key], [device])
wallet: wallet = wm.create_wallet("a_third_test_wallet", 1, "wpkh", [key], [device])
address = wallet.address
change_address = wallet.change_address

View file

@ -10,7 +10,8 @@ from cryptoadvance.specter.specter_error import SpecterError
from mock import MagicMock, call, patch
def test_Node_btc(bitcoin_regtest, wallet):
def test_Node_btc(bitcoin_regtest, trezor_wallet_acc2):
wallet = trezor_wallet_acc2
with tempfile.TemporaryDirectory("_some_datafolder_tmp") as data_folder:
node = Node.from_json(
{

View file

@ -417,29 +417,30 @@ def test_reserve_addresses_with_mocks(empty_data_folder, caplog):
assert addresses == ["a", "b"]
def test_reserve_addresses_with_an_actual_wallet(wallet):
def test_reserve_addresses_with_an_actual_wallet(trezor_wallet_acc6):
wallet = trezor_wallet_acc6
specter_mock = MagicMock()
test_service = MyTestService(True, specter_mock)
# Reserve first address
test_service.reserve_address(
wallet, "bcrt1qcatuhg0gll3h7py4cmn53rjjn9xlsqfwj3zcej", "reserved_for_john_nash"
wallet, "bcrt1qqvqdt5nsjhzrxcvhyz3m29f8qwyd4lrcpmtzy9", "reserved_for_john_nash"
)
first_address_address_obj = wallet.get_address_obj(
"bcrt1qcatuhg0gll3h7py4cmn53rjjn9xlsqfwj3zcej"
"bcrt1qqvqdt5nsjhzrxcvhyz3m29f8qwyd4lrcpmtzy9"
)
# Check that labeling works
assert first_address_address_obj["label"] == "reserved_for_john_nash"
# Simulating that the address has been used (for the definition of "usage" see the check_unused() method in wallet.py)
wallet._addresses.set_used(["bcrt1qcatuhg0gll3h7py4cmn53rjjn9xlsqfwj3zcej"])
wallet._addresses.set_used(["bcrt1qqvqdt5nsjhzrxcvhyz3m29f8qwyd4lrcpmtzy9"])
wallet.getnewaddress()
assert wallet.address_index == 1
assert first_address_address_obj["used"] == True
# Check that the correct addresses are reserved, should be #2, #4, #6 - since #0 has been used and there is a gap of one address in between
addresses = test_service.reserve_addresses(wallet, "satoshi_dice", 3)
assert addresses == [
"bcrt1qxak08ykhf7r4js9yncysy5p05xp0fwxhamewc8",
"bcrt1q2zv9963acq3g7a62mdjgj60rr3hgmyykaccca7",
"bcrt1qpys58dndrn9sxnk0z7ngm6wsxskpvs9jsjq7q6",
"bcrt1qyqta5muj054x43cmk8rv43up84kefz9ej3y27n",
"bcrt1qcswaeygm5w0y7xysqkn4uy9d6u0x6yxtlyntn6",
"bcrt1qwjz0ez6g763cfty6pf984247yngfs75h2n6ccz",
]
address_obj_list = wallet.get_associated_addresses("test_service")
# Reserving 3 addresses results in an empty list since we already have 3 unused addresses (the first one was used) reserved

View file

@ -91,7 +91,6 @@ def test_abandon_purged_tx(caplog, request, devices_filled_data_folder, device_m
# TODO: Make a test fixture in conftest.py that sets up already funded wallets
# for a bitcoin core hot wallet.
wallet_manager = WalletManager(
210100,
devices_filled_data_folder,
rpc,
"regtest",

View file

@ -88,7 +88,7 @@ def _check_port_free(port=8332):
def test_SpecterMigrator(empty_data_folder, caplog):
caplog.set_level(logging.DEBUG)
assert _check_port_free()
assert _check_port_free(), "You probably have a bitcoind running on port 8332"
assert MigDataManager.initial_data()["events"] == []
assert MigDataManager.initial_data()["migration_executions"] == []
assert len(os.listdir(empty_data_folder)) == 0

View file

@ -1,5 +1,6 @@
from mock import patch
from cryptoadvance.specterext.swan.service import SwanService
from cryptoadvance.specter.wallet import Wallet
import json
@ -11,7 +12,10 @@ class SwanServiceNoEncryption(SwanService):
@patch(
"cryptoadvance.specterext.swan.client.SwanClient.update_autowithdrawal_addresses"
)
def test_reserve_addresses(mocked_update_autowithdrawal_addresses, app_no_node, wallet):
def test_reserve_addresses(
mocked_update_autowithdrawal_addresses, app_no_node, trezor_wallet_acc5: Wallet
):
wallet = trezor_wallet_acc5
mocked_update_autowithdrawal_addresses.return_value = "some_id"
specter = app_no_node.specter
storage_manager = specter.service_unencrypted_storage_manager
@ -25,11 +29,11 @@ def test_reserve_addresses(mocked_update_autowithdrawal_addresses, app_no_node,
swan.reserve_addresses(wallet, label="Swan withdrawals", num_addresses=5)
# Check that the correct addresse list was passed to the client's update_autowithdrawal_addresses-method, should be addresses #1, #3, #5, #7, #9 from the test_wallet (the first address is skipped)
addresses = [
"bcrt1qsqnuk9hulcfta7kj7687favjv66d5e9yy0lr7t",
"bcrt1qee494mauu3fv5aje0t4p6e52hvwq6d5hcqfxqt",
"bcrt1qpnem6p9vr8rmjsf7k49p9sleu0h020g34ggn6k",
"bcrt1q8534jsqkympwaelaqxhvfr6hc3g8y4kjtgr6d6",
"bcrt1qxd6ndd7mt7jqut7797l84675fz4kqhs4fcfgny",
"bcrt1qrfdnsdhmp5chxxexywdz37ppre7s5f0y4z4ykn",
"bcrt1qp8hq4ngf0uy4r5atackw9ak5ngl8vfd54dz226",
"bcrt1qsdzhz4q8y32maeay899jyfdw03pdlrrktx838g",
"bcrt1qc65gchplxw57e7hdzdxcudjq90y0y3mxm9m96l",
"bcrt1qasuqqj5u7t5e8zr3ug68yzfr2fjj4eg4u4ucj4",
]
assert (
mocked_update_autowithdrawal_addresses.call_args_list[0].kwargs["addresses"]
@ -48,8 +52,8 @@ def test_reserve_addresses(mocked_update_autowithdrawal_addresses, app_no_node,
swan.reserve_addresses(wallet, label="Swan withdrawals", num_addresses=7)
# Adding address #11 and #13
additional_addresses = [
"bcrt1q32gd5s7rk9ptkv8e74q4c64ntf48u4sza6c9d9",
"bcrt1q463mg67f3tj5d223vf6387ty30qlx2wep4s5gp",
"bcrt1qa4n6687f53recfcthfu2xpgcwcqvmzz4pdfw98",
"bcrt1qcxq5md4jsnldpc6fswld8edaxgzpstamswt3r6",
]
addresses.extend(additional_addresses)
assert (
@ -81,7 +85,10 @@ class SwanServiceWithMockedMethods(SwanService):
@patch("cryptoadvance.specterext.swan.client.SwanClient.set_autowithdrawal")
def test_set_autowithdrawal_settings(mocked_set_autowithdrawal, app_no_node, wallet):
def test_set_autowithdrawal_settings(
mocked_set_autowithdrawal, app_no_node, trezor_wallet_acc3
):
wallet = trezor_wallet_acc3
autowithdrawal_api_response = """
{
"entity": "automaticWithdrawal",