mirror of
https://github.com/cryptoadvance/specter-desktop.git
synced 2026-08-13 12:33:29 +02:00
feature: Tor settings and tor_only mode (#765)
* Fix #634 * Fix #317 * Reorganize tor pannel * Fix the test * Specify block explorer explicitly on rescan utxo * New Tor settings tab * Tor settings admin only (also open HWI Bridge settings) * Update tor.md * Docs and small fixes * delete leftover code
This commit is contained in:
parent
119a70e1e6
commit
5990f12f33
22 changed files with 479 additions and 287 deletions
|
|
@ -12,8 +12,12 @@ Make sure authentication is enabled to avoid access to your Specter by random st
|
|||
### Security note
|
||||
Tor support, like Specter Desktop as a whole, should be treated as a work-in-progress that is not yet vetted as being fully secure.
|
||||
|
||||
### Setting up Tor using the Tor Browser
|
||||
The easiest way to use Tor with Specter is to have the [Tor Browser](https://www.torproject.org/download/) open. Just install it on your computer, open it and it will expose a Tor proxy Specter can connect to.
|
||||
To set this up, you'll just need to go to Specter's Settings -> Tor tab, then set the Tor Proxy field to: `socks5://127.0.0.1:9150` (you'll see it listed below the field as the Tor Browser default URL). Then click save and Specter should be working with Tor - just make sure to keep your Tor Browser running while using Specter.
|
||||
|
||||
### Install Tor service
|
||||
Install Tor on the same server that you'll be running Specter Desktop:
|
||||
Alternatively, you can setup the Tor service. Install Tor on the same server that you'll be running Specter Desktop:
|
||||
* [Debian / Ubuntu](https://2019.www.torproject.org/docs/debian.html.en)
|
||||
* [macOS](https://2019.www.torproject.org/docs/tor-doc-osx.html.en)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from socket import gethostname
|
|||
import click
|
||||
from OpenSSL import SSL, crypto
|
||||
from stem.control import Controller
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from ..server import create_app, init_app
|
||||
from ..util.tor import start_hidden_service, stop_hidden_services
|
||||
|
|
@ -195,7 +196,15 @@ def server(
|
|||
# debug is false by default
|
||||
def run(debug=debug):
|
||||
try:
|
||||
app.controller = Controller.from_port()
|
||||
tor_control_address = urlparse(app.specter.proxy_url).netloc.split(":")[0]
|
||||
if tor_control_address == "localhost":
|
||||
tor_control_address = "127.0.0.1"
|
||||
app.controller = Controller.from_port(
|
||||
address=tor_control_address,
|
||||
port=int(app.specter.tor_control_port)
|
||||
if app.specter.tor_control_port
|
||||
else "default",
|
||||
)
|
||||
except Exception:
|
||||
app.controller = None
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -296,3 +296,20 @@ def notify_upgrade(app, flash):
|
|||
"info",
|
||||
)
|
||||
return app.specter.version.current
|
||||
|
||||
|
||||
def is_ip_private(ip):
|
||||
# https://en.wikipedia.org/wiki/Private_network
|
||||
priv_lo = re.compile("^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
|
||||
priv_24 = re.compile("^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
|
||||
priv_20 = re.compile("^192\.168\.\d{1,3}.\d{1,3}$")
|
||||
priv_16 = re.compile("^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$")
|
||||
|
||||
res = (
|
||||
ip == "localhost"
|
||||
or priv_lo.match(ip)
|
||||
or priv_24.match(ip)
|
||||
or priv_20.match(ip)
|
||||
or priv_16.match(ip)
|
||||
)
|
||||
return res is not None
|
||||
|
|
|
|||
|
|
@ -93,12 +93,10 @@ def api():
|
|||
500,
|
||||
)
|
||||
data["forwarded_request"] = True
|
||||
requests_session = requests.Session()
|
||||
requests_session = app.specter.requests_session(
|
||||
force_tor=".onion/" in app.specter.hwi_bridge_url
|
||||
)
|
||||
requests_session.headers.update({"origin": request.environ["HTTP_ORIGIN"]})
|
||||
if ".onion/" in app.specter.hwi_bridge_url:
|
||||
requests_session.proxies = {}
|
||||
requests_session.proxies["http"] = "socks5h://localhost:9050"
|
||||
requests_session.proxies["https"] = "socks5h://localhost:9050"
|
||||
forwarded_request = requests_session.post(
|
||||
app.specter.hwi_bridge_url, data=json.dumps(data)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import logging
|
||||
import requests, json, os
|
||||
import os, sys, errno
|
||||
from .helpers import is_ip_private
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -137,7 +138,12 @@ def detect_rpc_confs_via_env():
|
|||
return rpc_arr
|
||||
|
||||
|
||||
def autodetect_rpc_confs(datadir=get_default_datadir(), port=None):
|
||||
def autodetect_rpc_confs(
|
||||
datadir=get_default_datadir(),
|
||||
port=None,
|
||||
proxy_url="socks5h://localhost:9050",
|
||||
only_tor=False,
|
||||
):
|
||||
"""Returns an array of valid and working configurations which
|
||||
got autodetected.
|
||||
autodetection checks env-vars and bitcoin-data-dirs
|
||||
|
|
@ -152,7 +158,9 @@ def autodetect_rpc_confs(datadir=get_default_datadir(), port=None):
|
|||
available_conf_arr = []
|
||||
if len(conf_arr) > 0:
|
||||
for conf in conf_arr:
|
||||
rpc = BitcoinRPC(**conf)
|
||||
rpc = BitcoinRPC(
|
||||
**conf, proxy_url="socks5h://localhost:9050", only_tor=False
|
||||
)
|
||||
if port is not None:
|
||||
if int(rpc.port) != port:
|
||||
continue
|
||||
|
|
@ -209,6 +217,8 @@ class BitcoinRPC:
|
|||
path="",
|
||||
timeout=None,
|
||||
session=None,
|
||||
proxy_url="socks5h://localhost:9050",
|
||||
only_tor=False,
|
||||
**kwargs,
|
||||
):
|
||||
path = path.replace("//", "/") # just in case
|
||||
|
|
@ -219,15 +229,18 @@ class BitcoinRPC:
|
|||
self.host = host
|
||||
self.path = path
|
||||
self.timeout = timeout
|
||||
self.proxy_url = (proxy_url,)
|
||||
self.only_tor = (only_tor,)
|
||||
self.r = None
|
||||
# session reuse speeds up requests
|
||||
if session is None:
|
||||
session = requests.Session()
|
||||
# check if we need to connect over Tor
|
||||
if ".onion" in self.host:
|
||||
# configure Tor proxies
|
||||
session.proxies["http"] = "socks5h://localhost:9050"
|
||||
session.proxies["https"] = "socks5h://localhost:9050"
|
||||
if not is_ip_private(host):
|
||||
if only_tor or ".onion" in self.host:
|
||||
# configure Tor proxies
|
||||
session.proxies["http"] = proxy_url
|
||||
session.proxies["https"] = proxy_url
|
||||
self.session = session
|
||||
|
||||
def wallet(self, name=""):
|
||||
|
|
@ -240,6 +253,8 @@ class BitcoinRPC:
|
|||
path="{}/wallet/{}".format(self.path, name),
|
||||
timeout=self.timeout,
|
||||
session=self.session,
|
||||
proxy_url=self.proxy_url,
|
||||
only_tor=self.only_tor,
|
||||
)
|
||||
|
||||
@property
|
||||
|
|
@ -270,6 +285,8 @@ class BitcoinRPC:
|
|||
self.path,
|
||||
self.timeout,
|
||||
self.session,
|
||||
self.proxy_url,
|
||||
self.only_tor,
|
||||
)
|
||||
|
||||
def multi(self, calls: list, **kwargs):
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ def init_app(app, hwibridge=False, specter=None):
|
|||
|
||||
# version checker
|
||||
# checks for new versions once per hour
|
||||
specter.version = VersionChecker()
|
||||
specter.version = VersionChecker(specter=specter)
|
||||
specter.version.start()
|
||||
|
||||
login_manager = LoginManager()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import random, traceback
|
||||
import random, traceback, socket
|
||||
from datetime import datetime
|
||||
|
||||
from flask import (
|
||||
|
|
@ -74,45 +74,6 @@ def inject_debug():
|
|||
return dict(debug=app.config["DEBUG"])
|
||||
|
||||
|
||||
@app.context_processor
|
||||
def inject_tor():
|
||||
if app.config["DEBUG"]:
|
||||
return dict(tor_service_id="", tor_enabled=False)
|
||||
if (
|
||||
request.args.get("action", "") == "stoptor"
|
||||
or request.args.get("action", "") == "starttor"
|
||||
):
|
||||
if hasattr(current_user, "is_admin") and current_user.is_admin:
|
||||
try:
|
||||
current_hidden_services = (
|
||||
app.controller.list_ephemeral_hidden_services()
|
||||
)
|
||||
except Exception:
|
||||
current_hidden_services = []
|
||||
if (
|
||||
request.args.get("action", "") == "stoptor"
|
||||
and len(current_hidden_services) != 0
|
||||
):
|
||||
stop_hidden_services(app)
|
||||
if (
|
||||
request.args.get("action", "") == "starttor"
|
||||
and len(current_hidden_services) == 0
|
||||
):
|
||||
try:
|
||||
start_hidden_service(app)
|
||||
except Exception as e:
|
||||
flash(
|
||||
"Failed to start Tor hidden service.\
|
||||
Make sure you have Tor running with ControlPort configured and try again.\
|
||||
Error returned: {}".format(
|
||||
e
|
||||
),
|
||||
"error",
|
||||
)
|
||||
return dict(tor_service_id="", tor_enabled=False)
|
||||
return dict(tor_service_id=app.tor_service_id, tor_enabled=app.tor_enabled)
|
||||
|
||||
|
||||
################ Specter global routes ####################
|
||||
@app.route("/")
|
||||
@login_required
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from ..helpers import (
|
|||
)
|
||||
from ..persistence import write_devices, write_wallet
|
||||
from ..user import hash_password
|
||||
from ..util.tor import start_hidden_service, stop_hidden_services
|
||||
|
||||
rand = random.randint(0, 1e32) # to force style refresh
|
||||
|
||||
|
|
@ -233,6 +234,97 @@ This may take a few hours to complete.",
|
|||
)
|
||||
|
||||
|
||||
@settings_endpoint.route("/tor", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def tor():
|
||||
"""
|
||||
controls the tor related settings
|
||||
GET for displaying the page, POST for updates
|
||||
param action might be "save", "test_tor" or "toggle_hidden_service"
|
||||
param proxy_url the Tor deamon url, usually something like socks5h://localhost:9050
|
||||
param only_tor "on" or something else ("off")
|
||||
"""
|
||||
if not current_user.is_admin:
|
||||
flash("Only an admin is allowed to access this page.", "error")
|
||||
return redirect("")
|
||||
current_version = notify_upgrade(app, flash)
|
||||
proxy_url = app.specter.proxy_url
|
||||
only_tor = app.specter.only_tor
|
||||
tor_control_port = app.specter.tor_control_port
|
||||
if request.method == "POST":
|
||||
action = request.form["action"]
|
||||
proxy_url = request.form["proxy_url"]
|
||||
only_tor = request.form.get("only_tor") == "on"
|
||||
tor_control_port = request.form["tor_control_port"]
|
||||
|
||||
if action == "save":
|
||||
app.specter.update_proxy_url(proxy_url, current_user)
|
||||
app.specter.update_only_tor(only_tor, current_user)
|
||||
app.specter.update_tor_control_port(tor_control_port, current_user)
|
||||
app.specter.check()
|
||||
elif action == "test_tor":
|
||||
try:
|
||||
requests_session = requests.Session()
|
||||
requests_session.proxies["http"] = proxy_url
|
||||
requests_session.proxies["https"] = proxy_url
|
||||
res = requests_session.get(
|
||||
"http://expyuzz4wqqyqhjn.onion", # Tor Project onion website
|
||||
)
|
||||
tor_connectable = res.status_code == 200
|
||||
if tor_connectable:
|
||||
flash("Tor requests test completed successfully!", "info")
|
||||
else:
|
||||
flash("Failed to make test request over Tor.", "error")
|
||||
except Exception as e:
|
||||
flash("Failed to make test request over Tor. Error: %s" % e, "error")
|
||||
tor_connectable = False
|
||||
elif action == "toggle_hidden_service":
|
||||
if not app.config["DEBUG"]:
|
||||
if app.specter.config.get("auth", "none") == "none":
|
||||
flash(
|
||||
"Enabling Tor hidden service will expose your Specter for remote access.<br>It is therefore required that you set up authentication tab for Specter first to prevent unauthorized access.<br><br>Please go to Settings -> Authentication and set up an authentication method and retry.",
|
||||
"error",
|
||||
)
|
||||
else:
|
||||
if hasattr(current_user, "is_admin") and current_user.is_admin:
|
||||
try:
|
||||
current_hidden_services = (
|
||||
app.controller.list_ephemeral_hidden_services()
|
||||
)
|
||||
except Exception:
|
||||
current_hidden_services = []
|
||||
if len(current_hidden_services) != 0:
|
||||
stop_hidden_services(app)
|
||||
flash("Tor hidden service turn off successfully", "info")
|
||||
else:
|
||||
try:
|
||||
start_hidden_service(app)
|
||||
flash("Tor hidden service turn on successfully", "info")
|
||||
except Exception as e:
|
||||
flash(
|
||||
"Failed to start Tor hidden service. Make sure you have Tor running with ControlPort configured and try again. Error returned: {}".format(
|
||||
e
|
||||
),
|
||||
"error",
|
||||
)
|
||||
else:
|
||||
flash(
|
||||
"Can't toggle hidden service while Specter is running in DEBUG mode",
|
||||
"error",
|
||||
)
|
||||
|
||||
return render_template(
|
||||
"settings/tor_settings.jinja",
|
||||
proxy_url=proxy_url,
|
||||
only_tor=only_tor,
|
||||
tor_control_port=tor_control_port,
|
||||
tor_service_id=app.tor_service_id,
|
||||
specter=app.specter,
|
||||
current_version=current_version,
|
||||
rand=rand,
|
||||
)
|
||||
|
||||
|
||||
@settings_endpoint.route("/auth", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def auth():
|
||||
|
|
|
|||
|
|
@ -370,8 +370,8 @@ def new_wallet(wallet_type):
|
|||
if "utxo" in request.form.get("full_rescan_option"):
|
||||
explorer = None
|
||||
if "use_explorer" in request.form:
|
||||
explorer = app.specter.get_default_explorer()
|
||||
wallet.rescanutxo(explorer)
|
||||
explorer = request.form["explorer_url"]
|
||||
wallet.rescanutxo(explorer, app.specter.requests_session(explorer))
|
||||
app.specter.info["utxorescan"] = 1
|
||||
app.specter.utxorescanwallet = wallet.alias
|
||||
else:
|
||||
|
|
@ -793,8 +793,8 @@ def settings(wallet_alias):
|
|||
elif action == "rescanutxo":
|
||||
explorer = None
|
||||
if "use_explorer" in request.form:
|
||||
explorer = app.specter.get_default_explorer()
|
||||
wallet.rescanutxo(explorer)
|
||||
explorer = request.form["explorer_url"]
|
||||
wallet.rescanutxo(explorer, app.specter.requests_session(explorer))
|
||||
app.specter.info["utxorescan"] = 1
|
||||
app.specter.utxorescanwallet = wallet.alias
|
||||
elif action == "abortrescanutxo":
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import traceback
|
|||
import random
|
||||
import time
|
||||
import zipfile
|
||||
import requests
|
||||
from io import BytesIO
|
||||
from .helpers import deep_update, clean_psbt, is_testnet
|
||||
from .util.checker import Checker
|
||||
|
|
@ -24,7 +25,13 @@ import threading
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_rpc(conf, old_rpc=None, return_broken_instead_none=False):
|
||||
def get_rpc(
|
||||
conf,
|
||||
old_rpc=None,
|
||||
return_broken_instead_none=False,
|
||||
proxy_url="socks5h://localhost:9050",
|
||||
only_tor=False,
|
||||
):
|
||||
"""
|
||||
Checks if config have changed, compares with old rpc
|
||||
and returns new one if necessary
|
||||
|
|
@ -44,7 +51,7 @@ def get_rpc(conf, old_rpc=None, return_broken_instead_none=False):
|
|||
datadir=os.path.expanduser(conf["datadir"])
|
||||
)
|
||||
if len(rpc_conf_arr) > 0:
|
||||
rpc = BitcoinRPC(**rpc_conf_arr[0])
|
||||
rpc = BitcoinRPC(**rpc_conf_arr[0], proxy_url=proxy_url, only_tor=only_tor)
|
||||
else:
|
||||
# if autodetect is disabled and port is not defined
|
||||
# we use default port 8332
|
||||
|
|
@ -109,6 +116,9 @@ class Specter:
|
|||
},
|
||||
"auth": "none",
|
||||
"explorers": {"main": "", "test": "", "regtest": "", "signet": ""},
|
||||
"proxy_url": "socks5h://localhost:9050", # Tor proxy URL
|
||||
"only_tor": False,
|
||||
"tor_control_port": "",
|
||||
"hwi_bridge_url": "/hwi/api/",
|
||||
# unique id that will be used in wallets path in Bitcoin Core
|
||||
# empty by default for backward-compatibility
|
||||
|
|
@ -150,7 +160,12 @@ class Specter:
|
|||
# update rpc if something doesn't work
|
||||
rpc = self.rpc
|
||||
if rpc is None or not rpc.test_connection():
|
||||
rpc = get_rpc(self.config["rpc"], self.rpc)
|
||||
rpc = get_rpc(
|
||||
self.config["rpc"],
|
||||
self.rpc,
|
||||
proxy_url=self.proxy_url,
|
||||
only_tor=self.only_tor,
|
||||
)
|
||||
|
||||
# if rpc is not available
|
||||
# do checks more often, once in 20 seconds
|
||||
|
|
@ -334,7 +349,13 @@ class Specter:
|
|||
def test_rpc(self, **kwargs):
|
||||
conf = copy.deepcopy(self.config["rpc"])
|
||||
conf.update(kwargs)
|
||||
rpc = get_rpc(conf, return_broken_instead_none=True)
|
||||
|
||||
rpc = get_rpc(
|
||||
conf,
|
||||
return_broken_instead_none=True,
|
||||
proxy_url=self.proxy_url,
|
||||
only_tor=self.only_tor,
|
||||
)
|
||||
if rpc is None:
|
||||
return {"out": "", "err": "autodetect failed", "code": -1}
|
||||
r = {}
|
||||
|
|
@ -399,7 +420,12 @@ class Specter:
|
|||
self.config["rpc"][k] = kwargs[k]
|
||||
need_update = True
|
||||
if need_update:
|
||||
self.rpc = get_rpc(self.config["rpc"], None)
|
||||
self.rpc = get_rpc(
|
||||
self.config["rpc"],
|
||||
None,
|
||||
proxy_url=self.proxy_url,
|
||||
only_tor=self.only_tor,
|
||||
)
|
||||
self._save()
|
||||
self.check(check_all=True)
|
||||
return self.rpc is not None
|
||||
|
|
@ -427,6 +453,24 @@ class Specter:
|
|||
else:
|
||||
user.set_explorer(self, explorer)
|
||||
|
||||
def update_proxy_url(self, proxy_url, user):
|
||||
""" update the Tor proxy url """
|
||||
if self.config["proxy_url"] != proxy_url:
|
||||
self.config["proxy_url"] = proxy_url
|
||||
self._save()
|
||||
|
||||
def update_only_tor(self, only_tor, user):
|
||||
""" switch whatever to use Tor for all calls """
|
||||
if self.config["only_tor"] != only_tor:
|
||||
self.config["only_tor"] = only_tor
|
||||
self._save()
|
||||
|
||||
def update_tor_control_port(self, tor_control_port, user):
|
||||
""" set the control port of the tor daemon """
|
||||
if self.config["tor_control_port"] != tor_control_port:
|
||||
self.config["tor_control_port"] = tor_control_port
|
||||
self._save()
|
||||
|
||||
def update_hwi_bridge_url(self, url, user):
|
||||
""" update the hwi bridge url to use """
|
||||
user = self.user_manager.get_user(user)
|
||||
|
|
@ -587,6 +631,18 @@ class Specter:
|
|||
def explorer(self):
|
||||
return self.user_config.get("explorers", {}).get(self.chain, "")
|
||||
|
||||
@property
|
||||
def proxy_url(self):
|
||||
return self.user_config.get("proxy_url", "socks5h://localhost:9050")
|
||||
|
||||
@property
|
||||
def only_tor(self):
|
||||
return self.user_config.get("only_tor", False)
|
||||
|
||||
@property
|
||||
def tor_control_port(self):
|
||||
return self.user_config.get("tor_control_port", "")
|
||||
|
||||
@property
|
||||
def hwi_bridge_url(self):
|
||||
return self.user_config.get("hwi_bridge_url", "")
|
||||
|
|
@ -629,6 +685,13 @@ class Specter:
|
|||
def wallet_manager(self):
|
||||
return self.user.wallet_manager
|
||||
|
||||
def requests_session(self, force_tor=False):
|
||||
requests_session = requests.Session()
|
||||
if self.only_tor or force_tor:
|
||||
requests_session.proxies["http"] = self.proxy_url
|
||||
requests_session.proxies["https"] = self.proxy_url
|
||||
return requests_session
|
||||
|
||||
def specter_backup_file(self):
|
||||
memory_file = BytesIO()
|
||||
with zipfile.ZipFile(memory_file, "w") as zf:
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
{% include "includes/qr-code.html" %}
|
||||
{% endif %}
|
||||
{% include "includes/message-box.html" %}
|
||||
{% include "includes/tool-tip.html" %}
|
||||
<div class="row holder">
|
||||
{% block sidebar %}
|
||||
{% include "includes/sidebar/sidebar.jinja" %}
|
||||
|
|
@ -31,7 +32,6 @@
|
|||
{% if current_user.is_authenticated and not hwi_bridge %}
|
||||
<div class="row" id="status-bar" style="border-radius: 0 0 0 15px; position: absolute; right: 0; color: #ddd; background-color: #323e50;">
|
||||
{% include "components/price_bar.jinja" %}
|
||||
{% include "components/tor_address.jinja" %}
|
||||
<a class="settings-bar-btn" href="{{ url_for('settings_endpoint.settings') }}">
|
||||
<img src="{{ url_for('static', filename='img/settings-status-bar.svg') }}" style="width: 22px;"/>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -1,154 +0,0 @@
|
|||
<style>
|
||||
#onion-address-container {
|
||||
position: absolute;
|
||||
width: 240px;
|
||||
top: 10px;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
background: #7D4698;
|
||||
border-radius: 10px;
|
||||
z-index: 1;
|
||||
border: 1px solid #aaa;
|
||||
}
|
||||
|
||||
#tor-btn {
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
margin-right: 50px;
|
||||
z-index: 2;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#tor-toggle-btn {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
padding: 10px 40px 10px 20px;
|
||||
}
|
||||
|
||||
.onion-address {
|
||||
word-wrap: break-word;
|
||||
color: #fff;
|
||||
font-size: 0.8em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.onion-address:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dot {
|
||||
cursor: pointer;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
top: 9px;
|
||||
left: 36px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.dot-on {
|
||||
{% if tor_enabled %}
|
||||
background-color: #0f0;
|
||||
{% else %}
|
||||
background-color: #ccc;
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
.dot-off {
|
||||
{% if tor_enabled %}
|
||||
background-color: #f00;
|
||||
{% else %}
|
||||
background-color: #ccc;
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
.tor-btn {
|
||||
margin-top: 10px;
|
||||
width: 90px;
|
||||
border: none;
|
||||
font-size: 0.8em;
|
||||
color: #fff;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #59316B;
|
||||
background: #7D4698;
|
||||
}
|
||||
|
||||
.tor-btn:hover {
|
||||
cursor: pointer;
|
||||
background: #59316B;
|
||||
}
|
||||
</style>
|
||||
<div id="onion-address-container" class="hidden optional">
|
||||
<div class="tool-tip" style="margin: 3px;">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info" style="margin-top: 22px;">
|
||||
<span class="info">
|
||||
<span class="info__title">Accessing Specter with a Tor hidden service<br></span><br>
|
||||
Running a Tor hidden service with Specter allows you to securly access Specter from any remote device and location.<br><br>
|
||||
<b>Please Note:</b><br>Connecting over Tor requires that you first configure Tor on your local machine.<br>
|
||||
You can checkout <a target="_blank" href="https://www.keepitsimplebitcoin.com/how-to-install-tor/" style="color: #fff;">this video tutorial</a>, or read the documentation for <a target="_blank" href="https://github.com/cryptoadvance/specter-desktop/blob/master/docs/tor.md" style="color: #fff;">Mac and Linux</a> or <a target="_blank" href="https://github.com/Fonta1n3/FullyNoded/blob/master/Docs/Tor/Tor.md#windows-10" style="color: #fff;">Window</a><br>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
{% if tor_service_id %}
|
||||
<span style="font-size: 0.95em; color: #fff;">Specter Running on Tor</span><br>
|
||||
<div style="margin-top: 5px;"> <span style="font-size: 0.85em; color: #fff;">Running at:</span></div>
|
||||
<div class="center">
|
||||
<span title="Copy Tor address" class="onion-address centered" onclick="copyText('{{ tor_service_id }}.onion', 'Copied Tor hidden service address: {{ tor_service_id }}.onion')">
|
||||
{{ tor_service_id }}.onion
|
||||
</span>
|
||||
</div>
|
||||
{% if current_user.is_admin and (tor_service_id and tor_service_id + '.onion' not in request.url) %}
|
||||
<form action="?" class="center">
|
||||
<button class="tor-btn" type="submit" name="action" value="stoptor">Stop Tor</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if debug %}
|
||||
<span style="font-size: 0.95em; color: #fff;">Tor is unavailable...</span><br>
|
||||
<div style="margin-top: 5px;">
|
||||
<span style="font-size: 0.85em; color: #fff;">You are running Specter in debug mode, but Tor hidden services are only available in production mode.</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<span style="font-size: 0.95em; color: #fff;">Tor service is down...</span><br>
|
||||
{% if current_user.is_admin and ('.onion' not in request.url) %}
|
||||
<form action="?" class="center">
|
||||
<button class="tor-btn" type="submit" name="action" value="starttor">Start Tor</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="settings-bar-btn" id='tor-toggle-btn'>
|
||||
<img id="tor-btn" src="{{ url_for('static', filename='img/tor.svg') }}"/>
|
||||
<span class="dot {% if tor_service_id %}dot-on{% else %}dot-off{% endif %}" id="tor-symbol-status-dot"></span>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
let torToggleBtn = document.getElementById('tor-toggle-btn');
|
||||
let torDataContainer = document.getElementById('onion-address-container');
|
||||
|
||||
function toggleTorPopup() {
|
||||
torDataContainer.style.display = torDataContainer.style.display == 'block' ? 'none' : 'block';
|
||||
}
|
||||
torToggleBtn.addEventListener("click", toggleTorPopup);
|
||||
let right = 0;
|
||||
for(let el of document.getElementsByClassName('settings-bar-btn')) {
|
||||
if (el.id != 'price-bar-btn') {
|
||||
right += el.offsetWidth;
|
||||
}
|
||||
}
|
||||
torDataContainer.style.right = (right - 41) + 'px';
|
||||
window.addEventListener('click', function(e){
|
||||
if (!torDataContainer.contains(e.target) && !torToggleBtn.contains(e.target)){
|
||||
if (torDataContainer.style.display == 'block') {
|
||||
toggleTorPopup();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
101
src/cryptoadvance/specter/templates/includes/tool-tip.html
Normal file
101
src/cryptoadvance/specter/templates/includes/tool-tip.html
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<template id="tooltip">
|
||||
<style>
|
||||
.tool-tip {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
.tool-tip .tool-tip__icon {
|
||||
color: #fff;
|
||||
background: #27b1f0;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-style: italic;
|
||||
font-family: times new roman;
|
||||
height: 20px;
|
||||
line-height: 1.3em;
|
||||
text-align: center;
|
||||
width: 20px;
|
||||
}
|
||||
.tool-tip .tool-tip__info {
|
||||
display: none;
|
||||
background: #262626;
|
||||
border: 1px solid #27b1f0;
|
||||
border-radius: 3px;
|
||||
padding: 1em;
|
||||
position: absolute;
|
||||
left: 30px;
|
||||
top: -20px;
|
||||
width: 250px;
|
||||
z-index: 2;
|
||||
}
|
||||
.tool-tip .tool-tip__info:before, .tool-tip .tool-tip__info:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -10px;
|
||||
top: 7px;
|
||||
border-style: solid;
|
||||
border-width: 10px 10px 10px 0;
|
||||
border-color: transparent #27b1f0;
|
||||
}
|
||||
.tool-tip .tool-tip__info:after {
|
||||
left: -8px;
|
||||
border-right-color: #262626;
|
||||
}
|
||||
.tool-tip .tool-tip__info .info {
|
||||
display: block;
|
||||
}
|
||||
.tool-tip .tool-tip__info .info__title {
|
||||
color: #4A90E2;
|
||||
}
|
||||
.tool-tip:hover .tool-tip__info, .tool-tip:focus .tool-tip__info {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
a:focus + .tool-tip .tool-tip__info {
|
||||
display: inline-block;
|
||||
}
|
||||
@media (hover: none) {
|
||||
.tool-tip {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="tool-tip">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title"></span><br><br>
|
||||
<slot></slot>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="text/javascript">
|
||||
class TooltipElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
// Create a shadow root
|
||||
var shadow = this.attachShadow({mode: 'open'});
|
||||
var style = document.getElementById('tooltip').content;
|
||||
var clone = style.cloneNode(true);
|
||||
|
||||
this.titleText = clone.querySelector(".info__title");
|
||||
|
||||
// Attach the created element to the shadow dom
|
||||
shadow.appendChild(clone);
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['title'];
|
||||
}
|
||||
|
||||
attributeChangedCallback(attrName, oldValue, newValue) {
|
||||
let title = this.getAttribute('title');
|
||||
this.titleText.innerHTML = title;
|
||||
}
|
||||
}
|
||||
customElements.define('tool-tip', TooltipElement);
|
||||
</script>
|
||||
|
|
@ -12,9 +12,10 @@
|
|||
{{ settings_menu_item('bitcoin_core', 'Bitcoin Core', active_menuitem, isLeft=true) }}
|
||||
{% endif %}
|
||||
{{ settings_menu_item('general', 'General', active_menuitem, isLeft=(not current_user.is_admin)) }}
|
||||
{{ settings_menu_item('auth', 'Authentication', active_menuitem, isRight=not current_user.is_admin) }}
|
||||
{{ settings_menu_item('auth', 'Authentication', active_menuitem, isRight=false) }}
|
||||
{{ settings_menu_item('hwi', 'HWI Bridge', active_menuitem, isRight=(not current_user.is_admin)) }}
|
||||
{% if current_user.is_admin %}
|
||||
{{ settings_menu_item('hwi', 'HWI Bridge', active_menuitem, isRight=true) }}
|
||||
{{ settings_menu_item('tor', 'Tor', active_menuitem, isRight=true) }}
|
||||
{% endif %}
|
||||
<a href="javascript:void(0);" class="mobile-nav-icon" onclick="toggleMobileNav(this, `{{ url_for('static', filename='img/expand-more.svg') }}`, `{{ url_for('static', filename='img/expand-less.svg') }}`)">
|
||||
<img style="width: 36px;" src="{{ url_for('static', filename='img/expand-more.svg') }}"/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
{% extends "base.jinja" %}
|
||||
{% block main %}
|
||||
<form action="?" method="POST" onsubmit="showPacman()">
|
||||
<h1 id="title" class="settings-title">Tor settings - Specter Desktop {{ current_version }}</h1>
|
||||
{% from 'settings/components/settings_menu.jinja' import settings_menu %}
|
||||
{{ settings_menu('tor', current_user) }}
|
||||
<div class="card" style="margin: 20px auto;">
|
||||
<h1>
|
||||
<img style="width: 25px; margin-right: 7px; vertical-align: middle;" src="{{ url_for('static', filename='img/tor.svg') }}"/><span style="vertical-align: bottom; margin-right: 10px;">
|
||||
Tor configurations
|
||||
</h1>
|
||||
Use Tor proxy running at:
|
||||
<input id="proxy-url" name="proxy_url" value="{{ proxy_url }}" type="url" placeholder="i.e. socks5://127.0.0.1:9050" />
|
||||
<br>
|
||||
<p class="note">
|
||||
Default for Tor daemon: socks5://127.0.0.1:9050<br>
|
||||
Default for Tor Browser: socks5://127.0.0.1:9150
|
||||
</p>
|
||||
Tor control port:
|
||||
<p class="note">
|
||||
Restart Specter for change of the control port to take effect.
|
||||
</p>
|
||||
<input name="tor_control_port" value="{{ tor_control_port }}" type="text" placeholder='Set to custom port here, if kept empty the default (9051 and 9151) is used' />
|
||||
<br><br>
|
||||
Do outside calls only over Tor?
|
||||
<tool-tip title="Tor Only mode" style="float: right; margin-bottom: 5px;">
|
||||
Some optional Specter functionalities, like rescanning UTXO on a pruned node, or getting the Bitcoin price, might make calls to external APIs and services.<br><br>
|
||||
Toggle this on to ensure Specter routes all these external calls over Tor proxy.<br><br>
|
||||
Note: Some external sources may stop working if they block Tor call.
|
||||
</tool-tip>
|
||||
<p class="note">
|
||||
Toggle this on to ensure Specter routes all calls external over Tor proxy.
|
||||
</p>
|
||||
<div class="row">
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="only-tor" name="only_tor" {% if only_tor %}checked{% endif %}>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div><br>
|
||||
<div class="row">
|
||||
<button type="submit" class="btn" name="action" value="test_tor">Test Tor Connection</button>
|
||||
<button type="submit" class="btn" name="action" value="save">Save</button>
|
||||
</div><br><br>
|
||||
{% if not debug %}
|
||||
<h2>Tor Hidden Service</h2>
|
||||
<p>Running Specter behind a Tor hidden service allows you to acccess Specter over Tor from anywhere.<br>
|
||||
</p>
|
||||
{% if tor_service_id %}
|
||||
{% if specter.config.auth == "none" %}
|
||||
<p class="warning" style="max-width:700px;">
|
||||
⚠️ Warning!<br>
|
||||
Your are running Specter over Tor with no authentication settings configured.
|
||||
This means your Specter instance is accessible to anyone with the .onion URL.
|
||||
This .onion URL is publicly exposed and indexed on the Tor network - it is not secret!<br><br>
|
||||
It is stronly adviced that you configure proper authentication while running Specter behind a Tor hidden service.
|
||||
Please go to Settings -> Authentication and set up an authentication method.
|
||||
</p>
|
||||
{% endif %}
|
||||
<p><b>
|
||||
Specter hidden service is running at:
|
||||
<span title="Copy Tor address" style="word-break: break-word;" class="explorer-link" onclick="copyText('http:/\/{{ tor_service_id }}.onion', 'Copied Tor hidden service address: {{ tor_service_id }}.onion')">
|
||||
{{ tor_service_id }}.onion
|
||||
</span>
|
||||
</b></p>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<button type="submit" class="btn" name="action" value="toggle_hidden_service">
|
||||
{% if tor_service_id %} Stop {% else %} Start {% endif %}
|
||||
</button>
|
||||
</div><br>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
{% endblock %}
|
||||
|
|
@ -85,23 +85,22 @@
|
|||
<div id="utxo-scan-options" style="display: none">
|
||||
{% if specter.info['pruned'] %}
|
||||
<label for="use_explorer"><input style="width: auto; min-width: auto;" type="checkbox" id="use_explorer" name="use_explorer" checked> fetch missing data from block explorer
|
||||
<div class="tool-tip">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Fetch missing data</span><br><br>
|
||||
You are using a pruned node. Blocks for some transactions may be missing.<br><br>
|
||||
Check this checkbox if you want to fetch missing information from block explorer.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<tool-tip title="Fetch missing data">
|
||||
You are using a pruned node. Blocks for some transactions may be missing.<br><br>
|
||||
Check this checkbox if you want to fetch missing information from block explorer.
|
||||
</tool-tip>
|
||||
</label>
|
||||
<span class="warning" id="utxo-warning" style="display: inline-block; max-width:300px;">
|
||||
<div id="utxo-explorer">
|
||||
<br>
|
||||
Block explorer URL:
|
||||
<input id="explorer-url" name="explorer_url" value="{{ specter.get_default_explorer() }}" type="url" placeholder="Block explorer url" />
|
||||
<span class="warning" id="utxo-warning" style="display: inline-block; max-width:300px;">
|
||||
ⓘ<br>Careful with block explorers!<br>
|
||||
This may have privacy implication!<br>
|
||||
Information fetched from the block explorer contains a list of unspent transactions of your wallet from the old (pruned) blocks.<br>
|
||||
Information fetched from the block explorer contains a list of unspent transactions of your wallet from old (pruned) blocks.<br>
|
||||
"They" will know what transactions you are interested in.
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -238,7 +237,7 @@
|
|||
let utxocheckbox = document.getElementById("use_explorer");
|
||||
if(utxocheckbox){
|
||||
utxocheckbox.addEventListener('change', e=>{
|
||||
let el = document.getElementById("utxo-warning")
|
||||
let el = document.getElementById("utxo-explorer")
|
||||
if(utxocheckbox.checked){
|
||||
el.style.display = "inline-block";
|
||||
}else{
|
||||
|
|
|
|||
|
|
@ -207,31 +207,29 @@
|
|||
{% if specter.info['pruned'] %}
|
||||
<div class="row" style="margin-top: 10px">
|
||||
<label for="use_explorer"><input style="width: auto; min-width: auto;" type="checkbox" id="use_explorer" name="use_explorer" checked> fetch missing data from block explorer
|
||||
<div class="tool-tip">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
<p class="tool-tip__info">
|
||||
<span class="info">
|
||||
<span class="info__title">Fetch missing data</span><br><br>
|
||||
You are using a pruned node. Blocks for some transactions may be missing.<br><br>
|
||||
Check this checkbox if you want to fetch missing information from block explorer.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<tool-tip title="Fetch missing data">
|
||||
You are using a pruned node. Blocks for some transactions may be missing.<br><br>
|
||||
Check this checkbox if you want to fetch missing information from block explorer.
|
||||
</tool-tip>
|
||||
</label>
|
||||
</div>
|
||||
<span class="warning" id="utxo-warning" style="display: inline-block; max-width:300px;">
|
||||
ⓘ<br>Careful with block explorers!<br>
|
||||
This may have privacy implication!<br>
|
||||
Information fetched from the block explorer contains a list of unspent transactions of your wallet from old (pruned) blocks.<br>
|
||||
"They" will know what transactions you are interested in.
|
||||
</span>
|
||||
<div id="utxo-explorer">
|
||||
<br>
|
||||
Block explorer URL:
|
||||
<input id="explorer-url" name="explorer_url" value="{{ specter.get_default_explorer() }}" type="url" placeholder="block explorer url" />
|
||||
<span class="warning" id="utxo-warning" style="display: inline-block; max-width:300px;">
|
||||
ⓘ<br>Careful with block explorers!<br>
|
||||
This may have privacy implication!<br>
|
||||
Information fetched from the block explorer contains a list of unspent transactions of your wallet from old (pruned) blocks.<br>
|
||||
"They" will know what transactions you are interested in.
|
||||
</span>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
let utxocheckbox = document.getElementById("use_explorer");
|
||||
utxocheckbox.addEventListener('change', e=>{
|
||||
let el = document.getElementById("utxo-warning")
|
||||
let el = document.getElementById("utxo-explorer")
|
||||
if(utxocheckbox.checked){
|
||||
el.style.display = "inline-block";
|
||||
el.style.display = "block";
|
||||
}else{
|
||||
el.style.display = "none";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ def update_price(specter, current_user):
|
|||
def get_price_at(specter, current_user, timestamp="now"):
|
||||
try:
|
||||
if specter.price_check:
|
||||
requests_session = requests.Session()
|
||||
requests_session = specter.requests_session(
|
||||
force_tor=".onion/" in specter.price_provider
|
||||
)
|
||||
if specter.price_provider.startswith("bitstamp"):
|
||||
currency = "usd"
|
||||
currency_symbol = "$"
|
||||
|
|
@ -59,8 +61,6 @@ def get_price_at(specter, current_user, timestamp="now"):
|
|||
return False, 0, ""
|
||||
return (True, price, "£")
|
||||
if specter.price_provider.startswith("spotbit"):
|
||||
requests_session.proxies["http"] = "socks5h://localhost:9050"
|
||||
requests_session.proxies["https"] = "socks5h://localhost:9050"
|
||||
currency = "usd"
|
||||
currency_symbol = "$"
|
||||
if specter.price_provider.endswith("_eur"):
|
||||
|
|
|
|||
|
|
@ -40,6 +40,24 @@ def start_hidden_service(app):
|
|||
f.write("%s.onion" % app.tor_service_id)
|
||||
app.tor_service_id = app.tor_service_id
|
||||
app.tor_enabled = True
|
||||
if app.specter.config.get("auth", "none") == "none":
|
||||
print(" * ############################# Warning! #############################")
|
||||
print(
|
||||
" * Your are running Specter over Tor with no authentication settings configured."
|
||||
)
|
||||
print(
|
||||
" * This means your Specter instance is accessible to anyone with the .onion URL."
|
||||
)
|
||||
print(
|
||||
" * This .onion URL is publicly exposed and indexed on the Tor network - it is not secret!"
|
||||
)
|
||||
print(
|
||||
" * It is stronly adviced that you configure proper authentication while running Specter behind a Tor hidden service."
|
||||
)
|
||||
print(
|
||||
" * Please go to Settings -> Authentication and set up an authentication method."
|
||||
)
|
||||
print(" * ####################################################################")
|
||||
|
||||
|
||||
def stop_hidden_services(app):
|
||||
|
|
|
|||
|
|
@ -12,12 +12,13 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class VersionChecker:
|
||||
def __init__(self, name="cryptoadvance.specter"):
|
||||
def __init__(self, name="cryptoadvance.specter", specter=None):
|
||||
self.name = name
|
||||
self.current = self.get_current_version()
|
||||
self.latest = "unknown"
|
||||
self.upgrade = False
|
||||
self.running = False
|
||||
self.specter = specter
|
||||
|
||||
def start(self):
|
||||
if not self.running:
|
||||
|
|
@ -72,7 +73,11 @@ class VersionChecker:
|
|||
with open(version_file) as f:
|
||||
current = f.read().strip()
|
||||
try:
|
||||
releases = requests.get(
|
||||
if self.specter:
|
||||
requests_session = self.specter.requests_session(force_tor=False)
|
||||
else:
|
||||
requests_session = requests.Session()
|
||||
releases = requests_session.get(
|
||||
"https://api.github.com/repos/cryptoadvance/specter-desktop/releases"
|
||||
).json()
|
||||
latest = "unknown"
|
||||
|
|
@ -86,9 +91,13 @@ class VersionChecker:
|
|||
return current, latest
|
||||
|
||||
def get_pip_version(self):
|
||||
if self.specter:
|
||||
requests_session = self.specter.requests_session(force_tor=False)
|
||||
else:
|
||||
requests_session = requests.Session()
|
||||
try:
|
||||
releases = (
|
||||
requests.get("https://pypi.org/pypi/cryptoadvance.specter/json")
|
||||
requests_session.get("https://pypi.org/pypi/cryptoadvance.specter/json")
|
||||
.json()["releases"]
|
||||
.keys()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -674,35 +674,22 @@ class Wallet:
|
|||
logging.error("Exception while processing txlist: {}".format(e))
|
||||
return []
|
||||
|
||||
def full_txlist(self, validate_merkle_proofs=False):
|
||||
tx_list = []
|
||||
idx = 0
|
||||
tx_len = 1
|
||||
while tx_len > 0:
|
||||
transactions = self.txlist(
|
||||
idx, validate_merkle_proofs=validate_merkle_proofs
|
||||
)
|
||||
tx_list.append(transactions)
|
||||
tx_len = len(transactions)
|
||||
idx += 1
|
||||
|
||||
# Flatten the list
|
||||
flat_list = []
|
||||
for element in tx_list:
|
||||
for dic_item in element:
|
||||
flat_list.append(dic_item)
|
||||
return flat_list
|
||||
|
||||
def gettransaction(self, txid, blockheight=None):
|
||||
try:
|
||||
return self._transactions.gettransaction(txid, blockheight)
|
||||
except Exception as e:
|
||||
logger.warning("Could not get transaction {}, error: {}".format(txid, e))
|
||||
|
||||
def rescanutxo(self, explorer=None):
|
||||
def rescanutxo(self, explorer=None, requests_session=None):
|
||||
delete_file(self._transactions.path)
|
||||
self.fetch_transactions()
|
||||
t = threading.Thread(target=self._rescan_utxo_thread, args=(explorer,))
|
||||
t = threading.Thread(
|
||||
target=self._rescan_utxo_thread,
|
||||
args=(
|
||||
explorer,
|
||||
requests_session,
|
||||
),
|
||||
)
|
||||
t.start()
|
||||
|
||||
def export_labels(self):
|
||||
|
|
@ -721,7 +708,7 @@ class Wallet:
|
|||
for address in addresses:
|
||||
self._addresses.set_label(address, label)
|
||||
|
||||
def _rescan_utxo_thread(self, explorer=None):
|
||||
def _rescan_utxo_thread(self, explorer=None, requests_session=None):
|
||||
# rescan utxo is pretty fast,
|
||||
# so we can check large range of addresses
|
||||
# and adjust keypool accordingly
|
||||
|
|
@ -796,13 +783,6 @@ class Wallet:
|
|||
# handle missing transactions now
|
||||
# if Tor is running, requests will be sent over Tor
|
||||
if explorer is not None:
|
||||
try:
|
||||
requests_session = requests.Session()
|
||||
requests_session.proxies["http"] = "socks5h://localhost:9050"
|
||||
requests_session.proxies["https"] = "socks5h://localhost:9050"
|
||||
requests_session.get(explorer)
|
||||
except Exception:
|
||||
requests_session = requests.Session()
|
||||
# make sure there is no trailing /
|
||||
explorer = explorer.rstrip("/")
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -62,6 +62,9 @@ def test_settings_general_restore_wallet(bitcoin_regtest, caplog, client):
|
|||
loglevel="debug",
|
||||
restoredevices=restore_devices,
|
||||
restorewallets=restore_wallets,
|
||||
proxy_url="",
|
||||
only_tor="off",
|
||||
tor_control_port="",
|
||||
),
|
||||
)
|
||||
assert b"Specter data was successfully loaded from backup." in result.data
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue