diff --git a/basicswap/base.py b/basicswap/base.py
index 2f2cd60..891893a 100644
--- a/basicswap/base.py
+++ b/basicswap/base.py
@@ -1,11 +1,14 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2019-2021 tecnovert
+# Copyright (c) 2019-2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import os
import shlex
+import socks
+import socket
+import urllib
import logging
import threading
import subprocess
@@ -25,6 +28,10 @@ from .chainparams import (
)
+def getaddrinfo_tor(*args):
+ return [(socket.AF_INET, socket.SOCK_STREAM, 6, "", (args[0], args[1]))]
+
+
class BaseApp:
def __init__(self, fp, data_dir, settings, chain, log_name='BasicSwap'):
self.log_name = log_name
@@ -44,6 +51,15 @@ class BaseApp:
self.prepareLogging()
self.log.info('Network: {}'.format(self.chain))
+ self.use_tor_proxy = self.settings.get('use_tor', False)
+ self.tor_proxy_host = self.settings.get('tor_proxy_host', '127.0.0.1')
+ self.tor_proxy_port = self.settings.get('tor_proxy_port', 9050)
+ self.tor_control_password = self.settings.get('tor_control_password', None)
+ self.tor_control_port = self.settings.get('tor_control_port', 9051)
+ self.default_socket = socket.socket
+ self.default_socket_timeout = socket.getdefaulttimeout()
+ self.default_socket_getaddrinfo = socket.getaddrinfo
+
def stopRunning(self, with_code=0):
self.fail_code = with_code
with self.mxDB:
@@ -139,3 +155,38 @@ class BaseApp:
return True
str_error = str(ex).lower()
return 'read timed out' in str_error or 'no connection to daemon' in str_error
+
+ def setConnectionParameters(self, timeout=120):
+ opener = urllib.request.build_opener()
+ opener.addheaders = [('User-agent', 'Mozilla/5.0')]
+ urllib.request.install_opener(opener)
+
+ if self.use_tor_proxy:
+ socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, self.tor_proxy_host, self.tor_proxy_port, rdns=True)
+ socket.socket = socks.socksocket
+ socket.getaddrinfo = getaddrinfo_tor # Without this accessing .onion links would fail
+
+ socket.setdefaulttimeout(timeout)
+
+ def popConnectionParameters(self):
+ if self.use_tor_proxy:
+ socket.socket = self.default_socket
+ socket.getaddrinfo = self.default_socket_getaddrinfo
+ socket.setdefaulttimeout(self.default_socket_timeout)
+
+ def torControl(self, query):
+ try:
+ command = 'AUTHENTICATE "{}"\r\n{}\r\nQUIT\r\n'.format(self.tor_control_password, query).encode('utf-8')
+ c = socket.create_connection((self.tor_proxy_host, self.tor_control_port))
+ c.send(command)
+ response = bytearray()
+ while True:
+ rv = c.recv(1024)
+ if not rv:
+ break
+ response += rv
+ c.close()
+ return response
+ except Exception as e:
+ self.log.error(f'torControl {e}')
+ return
diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py
index 7856288..892831d 100644
--- a/basicswap/basicswap.py
+++ b/basicswap/basicswap.py
@@ -5659,74 +5659,78 @@ class BasicSwap(BaseApp):
def lookupRates(self, coin_from, coin_to):
self.log.debug('lookupRates {}, {}'.format(coin_from, coin_to))
- rv = {}
- ci_from = self.ci(int(coin_from))
- ci_to = self.ci(int(coin_to))
+ try:
+ self.setConnectionParameters()
+ rv = {}
+ ci_from = self.ci(int(coin_from))
+ ci_to = self.ci(int(coin_to))
- headers = {'Connection': 'close'}
- name_from = ci_from.chainparams()['name']
- name_to = ci_to.chainparams()['name']
- url = 'https://api.coingecko.com/api/v3/simple/price?ids={},{}&vs_currencies=usd'.format(name_from, name_to)
- start = time.time()
- req = urllib.request.Request(url, headers=headers)
- js = json.loads(urllib.request.urlopen(req, timeout=10).read())
- js['time_taken'] = time.time() - start
- rate = float(js[name_from]['usd']) / float(js[name_to]['usd'])
- js['rate_inferred'] = ci_to.format_amount(rate, conv_int=True, r=1)
- rv['coingecko'] = js
-
- ticker_from = ci_from.chainparams()['ticker']
- ticker_to = ci_to.chainparams()['ticker']
- if ci_from.coin_type() == Coins.BTC:
- pair = '{}-{}'.format(ticker_from, ticker_to)
- url = 'https://api.bittrex.com/api/v1.1/public/getticker?market=' + pair
+ headers = {'Connection': 'close'}
+ name_from = ci_from.chainparams()['name']
+ name_to = ci_to.chainparams()['name']
+ url = 'https://api.coingecko.com/api/v3/simple/price?ids={},{}&vs_currencies=usd'.format(name_from, name_to)
start = time.time()
req = urllib.request.Request(url, headers=headers)
js = json.loads(urllib.request.urlopen(req, timeout=10).read())
js['time_taken'] = time.time() - start
- js['pair'] = pair
+ rate = float(js[name_from]['usd']) / float(js[name_to]['usd'])
+ js['rate_inferred'] = ci_to.format_amount(rate, conv_int=True, r=1)
+ rv['coingecko'] = js
- try:
- rate_inverted = ci_from.make_int(1.0 / float(js['result']['Last']), r=1)
- js['rate_inferred'] = ci_to.format_amount(rate_inverted)
- except Exception as e:
- self.log.warning('lookupRates error: %s', str(e))
- js['rate_inferred'] = 'error'
+ ticker_from = ci_from.chainparams()['ticker']
+ ticker_to = ci_to.chainparams()['ticker']
+ if ci_from.coin_type() == Coins.BTC:
+ pair = '{}-{}'.format(ticker_from, ticker_to)
+ url = 'https://api.bittrex.com/api/v1.1/public/getticker?market=' + pair
+ start = time.time()
+ req = urllib.request.Request(url, headers=headers)
+ js = json.loads(urllib.request.urlopen(req, timeout=10).read())
+ js['time_taken'] = time.time() - start
+ js['pair'] = pair
- rv['bittrex'] = js
- elif ci_to.coin_type() == Coins.BTC:
- pair = '{}-{}'.format(ticker_to, ticker_from)
- url = 'https://api.bittrex.com/api/v1.1/public/getticker?market=' + pair
- start = time.time()
- req = urllib.request.Request(url, headers=headers)
- js = json.loads(urllib.request.urlopen(req, timeout=10).read())
- js['time_taken'] = time.time() - start
- js['pair'] = pair
- js['rate_last'] = js['result']['Last']
- rv['bittrex'] = js
- else:
- pair = 'BTC-{}'.format(ticker_from)
- url = 'https://api.bittrex.com/api/v1.1/public/getticker?market=' + pair
- start = time.time()
- req = urllib.request.Request(url, headers=headers)
- js_from = json.loads(urllib.request.urlopen(req, timeout=10).read())
- js_from['time_taken'] = time.time() - start
- js_from['pair'] = pair
+ try:
+ rate_inverted = ci_from.make_int(1.0 / float(js['result']['Last']), r=1)
+ js['rate_inferred'] = ci_to.format_amount(rate_inverted)
+ except Exception as e:
+ self.log.warning('lookupRates error: %s', str(e))
+ js['rate_inferred'] = 'error'
- pair = 'BTC-{}'.format(ticker_to)
- url = 'https://api.bittrex.com/api/v1.1/public/getticker?market=' + pair
- start = time.time()
- req = urllib.request.Request(url, headers=headers)
- js_to = json.loads(urllib.request.urlopen(req, timeout=10).read())
- js_to['time_taken'] = time.time() - start
- js_to['pair'] = pair
+ rv['bittrex'] = js
+ elif ci_to.coin_type() == Coins.BTC:
+ pair = '{}-{}'.format(ticker_to, ticker_from)
+ url = 'https://api.bittrex.com/api/v1.1/public/getticker?market=' + pair
+ start = time.time()
+ req = urllib.request.Request(url, headers=headers)
+ js = json.loads(urllib.request.urlopen(req, timeout=10).read())
+ js['time_taken'] = time.time() - start
+ js['pair'] = pair
+ js['rate_last'] = js['result']['Last']
+ rv['bittrex'] = js
+ else:
+ pair = 'BTC-{}'.format(ticker_from)
+ url = 'https://api.bittrex.com/api/v1.1/public/getticker?market=' + pair
+ start = time.time()
+ req = urllib.request.Request(url, headers=headers)
+ js_from = json.loads(urllib.request.urlopen(req, timeout=10).read())
+ js_from['time_taken'] = time.time() - start
+ js_from['pair'] = pair
- try:
- rate_inferred = float(js_from['result']['Last']) / float(js_to['result']['Last'])
- rate_inferred = ci_to.format_amount(rate, conv_int=True, r=1)
- except Exception as e:
- rate_inferred = 'error'
+ pair = 'BTC-{}'.format(ticker_to)
+ url = 'https://api.bittrex.com/api/v1.1/public/getticker?market=' + pair
+ start = time.time()
+ req = urllib.request.Request(url, headers=headers)
+ js_to = json.loads(urllib.request.urlopen(req, timeout=10).read())
+ js_to['time_taken'] = time.time() - start
+ js_to['pair'] = pair
- rv['bittrex'] = {'from': js_from, 'to': js_to, 'rate_inferred': rate_inferred}
+ try:
+ rate_inferred = float(js_from['result']['Last']) / float(js_to['result']['Last'])
+ rate_inferred = ci_to.format_amount(rate, conv_int=True, r=1)
+ except Exception as e:
+ rate_inferred = 'error'
- return rv
+ rv['bittrex'] = {'from': js_from, 'to': js_to, 'rate_inferred': rate_inferred}
+
+ return rv
+ finally:
+ self.popConnectionParameters()
diff --git a/basicswap/explorers.py b/basicswap/explorers.py
index 0f090a4..c0fff03 100644
--- a/basicswap/explorers.py
+++ b/basicswap/explorers.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-# Copyright (c) 2019-2021 tecnovert
+# Copyright (c) 2019-2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -17,9 +17,12 @@ class Explorer():
def readURL(self, url):
self.log.debug('Explorer url: {}'.format(url))
- headers = {'User-Agent': 'Mozilla/5.0'}
- req = urllib.request.Request(url, headers=headers)
- return urllib.request.urlopen(req).read()
+ try:
+ self.swapclient.setConnectionParameters()
+ req = urllib.request.Request(url)
+ return urllib.request.urlopen(req).read()
+ finally:
+ self.swapclient.popConnectionParameters()
class ExplorerInsight(Explorer):
diff --git a/basicswap/http_server.py b/basicswap/http_server.py
index 8c3e270..07e5be9 100644
--- a/basicswap/http_server.py
+++ b/basicswap/http_server.py
@@ -49,7 +49,7 @@ from .js_server import (
js_rate,
js_index,
)
-from .ui import (
+from .ui.util import (
PAGE_LIMIT,
inputAmount,
describeBid,
@@ -59,6 +59,7 @@ from .ui import (
have_data_entry,
get_data_entry_or,
)
+from .ui.page_tor import page_tor
env = Environment(loader=PackageLoader('basicswap', 'templates'))
@@ -1421,6 +1422,7 @@ class HttpHandler(BaseHTTPRequestHandler):
h2=self.server.title,
version=__version__,
summary=summary,
+ use_tor_proxy=swap_client.use_tor_proxy,
shutdown_token=shutdown_token
), 'UTF-8')
@@ -1519,6 +1521,8 @@ class HttpHandler(BaseHTTPRequestHandler):
return self.page_smsgaddresses(url_split, post_string)
if url_split[1] == 'identity':
return self.page_identity(url_split, post_string)
+ if url_split[1] == 'tor':
+ return page_tor(self, url_split, post_string)
if url_split[1] == 'shutdown':
return self.page_shutdown(url_split, post_string)
return self.page_index(url_split)
@@ -1562,6 +1566,7 @@ class HttpThread(threading.Thread, HTTPServer):
self.title = 'BasicSwap, ' + self.swap_client.chain
self.last_form_id = dict()
self.session_tokens = dict()
+ self.env = env
self.timeout = 60
HTTPServer.__init__(self, (self.host_name, self.port_no), HttpHandler)
diff --git a/basicswap/js_server.py b/basicswap/js_server.py
index 385f46c..8d7e248 100644
--- a/basicswap/js_server.py
+++ b/basicswap/js_server.py
@@ -17,7 +17,7 @@ from .basicswap_util import (
from .chainparams import (
Coins,
)
-from .ui import (
+from .ui.util import (
PAGE_LIMIT,
getCoinType,
inputAmount,
diff --git a/basicswap/templates/index.html b/basicswap/templates/index.html
index 41c7b57..722ccab 100644
--- a/basicswap/templates/index.html
+++ b/basicswap/templates/index.html
@@ -13,12 +13,13 @@ Version: {{ version }}
Explorers
SMSG Addresses
-Swaps in progress: {{ summary.num_swapping }}
+Swaps in Progress: {{ summary.num_swapping }}
Network Offers: {{ summary.num_network_offers }}
Sent Offers: {{ summary.num_sent_offers }}
Received Bids: {{ summary.num_recv_bids }}
Sent Bids: {{ summary.num_sent_bids }}
Watched Outputs: {{ summary.num_watched_outputs }}
+{% if use_tor_proxy %} TOR Information
{% endif %}
Page Refresh: {{ refresh }} seconds
+{% endif %} + +Circuit Established | {{ data.circuit_established }} |
Bytes Written | {{ data.bytes_written }} |
Bytes Read | {{ data.bytes_read }} |