Tor working for Bitcoin forks.

This commit is contained in:
tecnovert 2022-03-27 00:08:15 +02:00
parent d1e015962c
commit a5b192b931
No known key found for this signature in database
GPG key ID: 8ED6D8750C4E3F93
11 changed files with 220 additions and 92 deletions

View file

@ -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

View file

@ -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()

View file

@ -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):

View file

@ -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)

View file

@ -17,7 +17,7 @@ from .basicswap_util import (
from .chainparams import (
Coins,
)
from .ui import (
from .ui.util import (
PAGE_LIMIT,
getCoinType,
inputAmount,

View file

@ -13,12 +13,13 @@ Version: {{ version }}
<a href="/explorers">Explorers</a><br/>
<a href="/smsgaddresses">SMSG Addresses</a><br/>
<br/>
<a href="/active">Swaps in progress: {{ summary.num_swapping }}</a><br/>
<a href="/active">Swaps in Progress: {{ summary.num_swapping }}</a><br/>
<a href="/offers">Network Offers: {{ summary.num_network_offers }}</a><br/>
<a href="/sentoffers">Sent Offers: {{ summary.num_sent_offers }}</a><br/>
<a href="/bids">Received Bids: {{ summary.num_recv_bids }}</a><br/>
<a href="/sentbids">Sent Bids: {{ summary.num_sent_bids }}</a><br/>
<a href="/watched">Watched Outputs: {{ summary.num_watched_outputs }}</a><br/>
{% if use_tor_proxy %} <a href="/tor">TOR Information</a><br/> {% endif %}
</p>
<p><a href="/newoffer">New Offer</a></p>

View file

@ -0,0 +1,15 @@
{% include 'header.html' %}
<h3>TOR Information</h3>
{% if refresh %}
<p>Page Refresh: {{ refresh }} seconds</p>
{% endif %}
<table>
<tr><td>Circuit Established</td><td>{{ data.circuit_established }}</td></tr>
<tr><td>Bytes Written</td><td>{{ data.bytes_written }}</td></tr>
<tr><td>Bytes Read</td><td>{{ data.bytes_read }}</td></tr>
</table>
<p><a href="/">home</a></p>
</body></html>

0
basicswap/ui/__init__.py Normal file
View file

46
basicswap/ui/page_tor.py Normal file
View file

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import os
def extract_data(bytes_in):
str_in = bytes_in.decode('utf-8')
start = str_in.find('=')
if start < 0:
return None
start += 1
end = str_in.find('\r', start)
if end < 0:
return None
return str_in[start: end]
def page_tor(self, url_split, post_string):
template = self.server.env.get_template('tor.html')
swap_client = self.server.swap_client
page_data = {}
rv = swap_client.torControl('GETINFO status/circuit-established')
page_data['circuit_established'] = extract_data(rv)
rv = swap_client.torControl('GETINFO traffic/read')
page_data['bytes_written'] = extract_data(rv)
rv = swap_client.torControl('GETINFO traffic/written')
page_data['bytes_read'] = extract_data(rv)
messages = []
return bytes(template.render(
title=self.server.title,
h2=self.server.title,
messages=messages,
data=page_data,
form_id=os.urandom(8).hex(),
), 'UTF-8')

View file

@ -1,19 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2021 tecnovert
# Copyright (c) 2020-2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import json
import traceback
from .util import (
from basicswap.util import (
make_int,
format_timestamp,
)
from .chainparams import (
from basicswap.chainparams import (
Coins,
)
from .basicswap_util import (
from basicswap.basicswap_util import (
TxTypes,
TxStates,
BidStates,
@ -26,7 +26,7 @@ from .basicswap_util import (
getLastBidState,
)
from .protocols.xmr_swap_1 import getChainBSplitKey
from basicswap.protocols.xmr_swap_1 import getChainBSplitKey
PAGE_LIMIT = 50

View file

@ -28,6 +28,7 @@ from basicswap.rpc import (
callrpc_cli,
waitForRPC,
)
from basicswap.base import getaddrinfo_tor
from basicswap.basicswap import BasicSwap
from basicswap.chainparams import Coins
from basicswap.util import toBool
@ -84,7 +85,7 @@ BTC_RPC_PORT = int(os.getenv('BTC_RPC_PORT', 19796))
NMC_RPC_PORT = int(os.getenv('NMC_RPC_PORT', 19798))
PART_ONION_PORT = int(os.getenv('PART_ONION_PORT', 51734))
LTC_ONION_PORT = int(os.getenv('LTC_ONION_PORT', 19795)) # Still on 0.18 codebase, same port
LTC_ONION_PORT = int(os.getenv('LTC_ONION_PORT', 9333)) # Still on 0.18 codebase, same port
BTC_ONION_PORT = int(os.getenv('BTC_ONION_PORT', 8334))
PART_RPC_USER = os.getenv('PART_RPC_USER', '')
@ -129,10 +130,6 @@ def make_reporthook():
return reporthook
def getaddrinfo(*args):
return [(socket.AF_INET, socket.SOCK_STREAM, 6, "", (args[0], args[1]))]
def setConnectionParameters():
opener = urllib.request.build_opener()
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
@ -141,7 +138,7 @@ def setConnectionParameters():
if use_tor_proxy:
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, TOR_PROXY_HOST, TOR_PROXY_PORT, rdns=True)
socket.socket = socks.socksocket
socket.getaddrinfo = getaddrinfo # Without this accessing .onion links would fail
socket.getaddrinfo = getaddrinfo_tor # Without this accessing .onion links would fail
# Set low timeout for urlretrieve connections
socket.setdefaulttimeout(5)
@ -386,6 +383,21 @@ def prepareCore(coin, version_pair, settings, data_dir):
extractCore(coin, version_pair, settings, bin_dir, release_path)
def writeTorSettings(fp, coin, coin_settings, tor_control_password):
onionport = coin_settings['onionport']
fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
if coin == 'particl':
# TODO: lookuptorcontrolhost is default behaviour in later BTC versions
fp.write(f'torpassword={tor_control_password}\n')
fp.write(f'torcontrol={TOR_PROXY_HOST}:{TOR_CONTROL_PORT}\n')
fp.write('lookuptorcontrolhost=any\n') # Particl only option
if coin == 'litecoin':
fp.write(f'bind=0.0.0.0:{onionport}\n')
else:
fp.write(f'bind=0.0.0.0:{onionport}=onion\n')
def prepareDataDir(coin, settings, chain, particl_mnemonic, use_containers=False, tor_control_password=None):
core_settings = settings['chainclients'][coin]
bin_dir = core_settings['bindir']
@ -467,12 +479,7 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, use_containers=False
fp.write('wallet=wallet.dat\n')
if tor_control_password is not None:
onionport = core_settings['onionport']
fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
fp.write(f'torpassword={tor_control_password}\n')
fp.write(f'torcontrol={TOR_PROXY_HOST}:{TOR_CONTROL_PORT}\n')
# -listen is automatically set in InitParameterInteraction when bind is set
fp.write(f'bind=0.0.0.0:{onionport}=onion\n')
writeTorSettings(fp, coin, core_settings, tor_control_password)
salt = generate_salt(16)
if coin == 'particl':
@ -523,10 +530,10 @@ def write_torrc(data_dir, tor_control_password):
def addTorSettings(settings, tor_control_password):
settings['tor_control_password'] = tor_control_password
settings['use_tor'] = True
settings['tor_proxy_host'] = TOR_PROXY_HOST
settings['tor_proxy_port'] = TOR_PROXY_PORT
settings['tor_control_password'] = tor_control_password
settings['tor_control_port'] = TOR_CONTROL_PORT
@ -547,7 +554,7 @@ def modify_tor_config(settings, coin, tor_control_password=None, enable=False):
shutil.copyfile(core_conf_path, core_conf_path + '.last')
shutil.copyfile(wallet_conf_path, wallet_conf_path + '.last')
daemon_tor_settings = ('proxy=', 'proxy-allow-dns-leak=', 'no-igd=')
daemon_tor_settings = ('proxy=', 'proxy-allow-dns-leaks=', 'no-igd=')
with open(core_conf_path, 'w') as fp:
with open(core_conf_path + '.last') as fp_in:
# Disable tor first
@ -613,11 +620,7 @@ def modify_tor_config(settings, coin, tor_control_password=None, enable=False):
if not skip_line:
fp.write(line)
if enable:
onionport = coin_settings['onionport']
fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
fp.write(f'torpassword={tor_control_password}\n')
fp.write(f'torcontrol={TOR_PROXY_HOST}:{TOR_CONTROL_PORT}\n')
fp.write(f'bind=0.0.0.0:{onionport}=onion\n')
writeTorSettings(fp, coin, coin_settings, tor_control_password)
def make_rpc_func(bin_dir, data_dir, chain):