diff --git a/README.md b/README.md index 62020e9e3..55d053dcc 100755 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ - [Specter Desktop](#specter-desktop) - [DISCLAIMER](#disclaimer) - - [Video Walkthrough](#video-walkthrough) + - [Documentation and Video Walkthrough](#documentation-and-video-walkthrough) - [Why?](#why) - [Help wanted: Do you like Specter?](#help-wanted-do-you-like-specter) - [How to run](#how-to-run) @@ -27,7 +27,6 @@ The Crypto Anarchist Manifesto - Timothy C. May - Sun, 22 Nov 92 12:11:24 PST [![Build Status](https://api.cirrus-ci.com/github/cryptoadvance/specter-desktop.svg)](https://cirrus-ci.com/github/cryptoadvance/specter-desktop) -[![Support the project](https://img.shields.io/badge/btcpay-support%20project-orange.svg)](https://donate.specter.solutions/apps/3k77BAT6zshCGNd3i7gw9WKwXQy1/pos) ## DISCLAIMER @@ -35,8 +34,10 @@ This software might be ready to be used but at your own risk. If something doesn't work open an issue here or ask a question in our [Telegram group](https://t.me/spectersupport). -## Video Walkthrough -![video](https://www.youtube.com/embed/v3SEp0SkOWs) [Watch here](https://www.youtube.com/watch?v=v3SEp0SkOWs) +## Documentation and Video Walkthrough + +* ![video](https://www.youtube.com/embed/v3SEp0SkOWs) [Watch here](https://www.youtube.com/watch?v=v3SEp0SkOWs) +* [documentation](https://docs.specter.solutions/desktop/) ## Why? diff --git a/docs/development.md b/docs/development.md index 41e5627d2..0e15b8f47 100644 --- a/docs/development.md +++ b/docs/development.md @@ -65,7 +65,7 @@ sudo yum -y install libusb libudev-devel libffi libffi-devel openssl-devel && su ``` #### Windows -* Install python 3.8.x by downloading from [python.org](https://www.python.org/downloads/windows/) +* Install python 3.10.x by downloading from [python.org](https://www.python.org/downloads/windows/) _Do NOT install python from the Microsoft Store! It runs in a different execution environment that creates enormous headaches!_ @@ -85,8 +85,7 @@ sudo yum -y install libusb libudev-devel libffi libffi-devel openssl-devel && su ### Set up virtualenv -Specter is using `hwi-2.1.0` which by now supports higher Python versions than Specter itself. Specter currently supports Python 3.7-3.9. -If you have Python 3.10 as your global version then be sure to also install an old Python version and pass it to `virtualenv` (e.g. `virtualenv --python=python3.8 .env`) or use pyenv. +Specter is using `hwi-2.1.0` which by now supports higher Python versions than Specter itself. Specter currently supports Python 3.9 and 3.10. ```sh git clone https://github.com/cryptoadvance/specter-desktop.git @@ -113,6 +112,7 @@ Run the server: cd specter-desktop python3 -m cryptoadvance.specter server --config DevelopmentConfig --debug ``` +After that, Specter will be available at http://127.0.0.1:25441/. #### If `pip install` fails on `cryptography==3.4.x` diff --git a/docs/faq.md b/docs/faq.md index 9b1eaee04..a34f462a9 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -380,13 +380,13 @@ To check (before and/or afterwards) your installed version, you can use: `pip3 s ### Raspiblitz You might want to wait until raspiblitz is providing an update. It takes longer but if you're not technically literate, that might be a better option. However, up from Version 1.6.1, Raspiblitz offers an update-possibility in the menu. Prior to that or as a part of troubleshooting-procedure, you can also do something like this: ``` -sudo su - bitcoin -cd .specter/ +sudo su - specter . ./.env/bin/activate pip3 list | grep specter pip3 install cryptoadvance.specter --upgrade pip3 list | grep specter -service cryptoadvance-specter restart +exit +sudo systemctl restart specter ``` ### umbrel diff --git a/package-lock.json b/package-lock.json index 5e9cbb47a..a8a2a84b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3589,9 +3589,9 @@ } }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -8856,9 +8856,9 @@ "dev": true }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } diff --git a/pyinstaller/electron/package-lock.json b/pyinstaller/electron/package-lock.json index d2dea0caa..1bd79874e 100644 --- a/pyinstaller/electron/package-lock.json +++ b/pyinstaller/electron/package-lock.json @@ -1410,9 +1410,9 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -1885,13 +1885,10 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "node_modules/json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, "bin": { "json5": "lib/cli.js" }, @@ -2079,9 +2076,9 @@ } }, "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -4488,9 +4485,9 @@ } }, "minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -4859,13 +4856,10 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true }, "jsonfile": { "version": "4.0.0", @@ -5012,9 +5006,9 @@ "dev": true }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" diff --git a/src/cryptoadvance/specter/server_endpoints/settings.py b/src/cryptoadvance/specter/server_endpoints/settings.py index 735a9064c..5ddd1b94b 100644 --- a/src/cryptoadvance/specter/server_endpoints/settings.py +++ b/src/cryptoadvance/specter/server_endpoints/settings.py @@ -14,6 +14,7 @@ from pathlib import Path import cryptography import pgpy import requests +from cryptoadvance.specter.util.wallet_importer import WalletImporter from flask import Blueprint, Flask from flask import current_app as app from flask import jsonify, redirect, render_template, request, send_file, url_for @@ -29,7 +30,7 @@ from ..helpers import ( from ..persistence import write_devices, write_wallet from ..server_endpoints import flash from ..services.service import callbacks -from ..specter_error import handle_exception +from ..specter_error import SpecterError, handle_exception from ..user import UserSecretException from ..util.sha256sum import sha256sum from ..util.shell import get_last_lines_from_file @@ -149,74 +150,58 @@ def general(): app.specter.device_manager.update() rescanning = False + logger.info(f"Importing {len(restore_wallets)} wallets ...") + counter = { + "success": 0, + "specific_error": 0, + "unspecific_error": 0, + "rescan_error": 0, + } for wallet in restore_wallets: - try: - app.specter.wallet_manager.rpc.createwallet( - os.path.join( - app.specter.wallet_manager.rpc_path, wallet["alias"] - ), - True, - ) - except Exception as e: - # if wallet already exists in Bitcoin Core - # continue with the existing one - if "already exists" not in str(e): - flash( - _("Failed to import wallet {}, error: {}").format( - wallet["name"], e - ), - "error", - ) - handle_exception(e) - continue - logger.debug( - f"Wallet {wallet['alias']} already exists, skipping creation" - ) - write_wallet(wallet) - app.specter.wallet_manager.update(use_threading=False) try: - wallet_obj = app.specter.wallet_manager.get_by_alias( - wallet["alias"] + logger.info(f"Importing wallet {wallet['name']}") + wallet_importer = WalletImporter(json.dumps(wallet), app.specter) + wallet_importer.create_wallet(app.specter.wallet_manager) + wallet_importer.rescan_as_needed(app.specter) + counter["success"] += 1 + except SpecterError as se: + error_type = ( + "rescan_error" if "rescan" in str(se) else "specific_error" ) - wallet_obj.keypoolrefill(0, wallet_obj.IMPORT_KEYPOOL, change=False) - wallet_obj.keypoolrefill(0, wallet_obj.IMPORT_KEYPOOL, change=True) - wallet_obj.import_labels(wallet.get("labels", {})) - try: - wallet_obj.rpc.rescanblockchain( - wallet["blockheight"] - if "blockheight" in wallet - else get_startblock_by_chain(app.specter), - no_wait=True, - ) - app.logger.info("Rescanning Blockchain ...") - rescanning = True - except Exception as e: - app.logger.exception( - "Exception while rescanning blockchain for wallet {}: {}".format( - wallet["alias"], e - ), - e, - ) - flash( - _("Failed to perform rescan for wallet: {}").format(e), - "error", - ) - wallet_obj.getdata() + flash(f"Wallet '{wallet.get('name')}': {se} (skipped)", "error") + counter[error_type] += 1 except Exception as e: flash( - _("Failed to import wallet {}").format(wallet["name"]), "error" + f"Error while importing wallet {wallet['name']}, check logs for details! (skipped)", + "error", ) - handle_exception(e) - flash(_("Specter data was successfully loaded from backup"), "info") + logger.exception( + f"Error while importing wallet {wallet['name']}: {e}" + ) + counter["unspecific_error"] += 1 + + counter["errors_sum"] = ( + counter["rescan_error"] + + counter["specific_error"] + + counter["unspecific_error"] + ) + if counter["success"] > 0: + message = f"{counter['success']} wallets successfully restored. " + + else: + message = f"Sorry, this doesn't went that well. " + if counter["errors_sum"] > 0: + message += "however, we had " if counter["success"] > 0 else "We had " + message += f"""
+ Successful imports: {counter["success"]}
+ Specific errors: {counter["specific_error"]}
+ Unspecific errors: {counter["unspecific_error"]} (create an issue about it on github)
+ Rescan issues: {counter["rescan_error"]}
+ """ if rescanning: - flash( - _( - "Wallets are rescanning for transactions history.\n\ -This may take a few hours to complete." - ), - "info", - ) + message += "Wallets are rescanning for transactions history. This may take a few hours to complete." + flash(message, "info") return render_template( "settings/general_settings.jinja", diff --git a/src/cryptoadvance/specter/templates/settings/general_settings.jinja b/src/cryptoadvance/specter/templates/settings/general_settings.jinja index edbc6b706..82a3a20ac 100644 --- a/src/cryptoadvance/specter/templates/settings/general_settings.jinja +++ b/src/cryptoadvance/specter/templates/settings/general_settings.jinja @@ -197,13 +197,21 @@ reader.onload = function(e) { if (files[i].webkitRelativePath.indexOf('devices/') != -1) { console.log(files[i].name); - devices.push(JSON.parse(reader.result)); + try { + devices.push(JSON.parse(reader.result)); + } catch { + showError(`Could not load Device ${files[i].name}: not valid json! This Device will be skipped for import which might affect corresponding Wallets.`) + } document.getElementById('restoredevices').value = JSON.stringify(devices); document.getElementById('filesloaded').innerHTML = `
Found ${devices.length} devices and ${wallets.length} wallets:`; document.getElementById('restore').style.display = 'block'; } else if (files[i].webkitRelativePath.indexOf('wallets/') != -1) { console.log(files[i].name); - wallets.push(JSON.parse(reader.result)); + try { + wallets.push(JSON.parse(reader.result)); + } catch { + showError(`Could not load Wallet ${files[i].name}: not valid json! This wallet will be skipped for import.`) + } document.getElementById('restorewallets').value = JSON.stringify(wallets); document.getElementById('filesloaded').innerHTML = `
Found ${devices.length} devices and ${wallets.length} wallets:`; document.getElementById('restore').style.display = 'block'; diff --git a/src/cryptoadvance/specter/util/wallet_importer.py b/src/cryptoadvance/specter/util/wallet_importer.py index 6651c6dee..e3e406e1d 100644 --- a/src/cryptoadvance/specter/util/wallet_importer.py +++ b/src/cryptoadvance/specter/util/wallet_importer.py @@ -50,8 +50,8 @@ class WalletImporter: try: self.descriptor = DescriptorCls.from_string(recv_descriptor) self.check_descriptor() - except Exception as e: - raise SpecterError(f"Invalid wallet descriptor: {e}") + except ValueError as e: + raise SpecterError(f"{e}: {recv_descriptor}") if self.wallet_name in specter.wallet_manager.wallets_names: raise SpecterError(f"Wallet with the same name already exists") ( @@ -242,7 +242,7 @@ class WalletImporter: ) except Exception as e: logger.exception(e) - raise SpecterError(f"Failed to create wallet: {e}") + raise SpecterError(f"Failed to create wallet: {e} (check logs for details)") logger.info(f"Created Wallet {self.wallet}") self.wallet.keypoolrefill(0, self.wallet.IMPORT_KEYPOOL, change=False) self.wallet.keypoolrefill(0, self.wallet.IMPORT_KEYPOOL, change=True) @@ -333,7 +333,7 @@ class WalletImporter: wallet_data["x1/"]["xpub"] ) else: - raise Exception('"xpub" not found in "x1/" in Electrum backup json') + raise SpecterError('"xpub" not found in "x1/" in Electrum backup json') required_sigs = int(wallet_data.get("wallet_type").split("of")[0]) recv_descriptor = "{}(sortedmulti({},{}))".format( @@ -482,7 +482,7 @@ class WalletImporter: wallet_data["keystore"]["xpub"], is_multisig=False ) else: - raise Exception( + raise SpecterError( '"xpub" not found in "keystore" in Electrum backup json' ) recv_descriptor = "{}({})".format( @@ -517,6 +517,13 @@ class WalletImporter: wallet_name = wallet_data.get("label", "Imported Wallet") recv_descriptor = wallet_data.get("descriptor", None) + if wallet_name is None: + raise SpecterError( + f"Couldn't find 'name' in wallet json (alias: {wallet_data.get('alias','also not existing')})." + ) + if recv_descriptor is None: + raise SpecterError("Couldn't find 'recv_descriptor' in wallet json.") + return (wallet_name, recv_descriptor, cosigners_types) @classmethod diff --git a/src/cryptoadvance/specter/wallet.py b/src/cryptoadvance/specter/wallet.py index 3e7037ffe..fba8f08b1 100644 --- a/src/cryptoadvance/specter/wallet.py +++ b/src/cryptoadvance/specter/wallet.py @@ -1454,6 +1454,7 @@ class Wallet: else self.rpc.getbalances()["watchonly"] ) except Exception as e: + handle_exception(e) raise SpecterError(f"was not able to get wallet_balance because {e}") self.balance = balance return self.balance