mirror of
https://github.com/basicswap/basicswap.git
synced 2024-11-16 15:58:17 +00:00
42fa4d49d4
Running BSX in windows is highly discouraged. If you have no choice at least use the WSL docker setup instead.
445 lines
18 KiB
Python
Executable file
445 lines
18 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (c) 2019-2024 tecnovert
|
|
# Distributed under the MIT software license, see the accompanying
|
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import shutil
|
|
import signal
|
|
import logging
|
|
import traceback
|
|
import subprocess
|
|
|
|
import basicswap.config as cfg
|
|
from basicswap import __version__
|
|
from basicswap.ui.util import getCoinName
|
|
from basicswap.basicswap import BasicSwap
|
|
from basicswap.chainparams import chainparams
|
|
from basicswap.http_server import HttpThread
|
|
from basicswap.contrib.websocket_server import WebsocketServer
|
|
|
|
|
|
logger = logging.getLogger()
|
|
logger.level = logging.DEBUG
|
|
if not len(logger.handlers):
|
|
logger.addHandler(logging.StreamHandler(sys.stdout))
|
|
|
|
swap_client = None
|
|
|
|
|
|
class Daemon:
|
|
__slots__ = ('handle', 'files')
|
|
|
|
def __init__(self, handle, files):
|
|
self.handle = handle
|
|
self.files = files
|
|
|
|
|
|
def is_known_coin(coin_name: str) -> bool:
|
|
for k, v in chainparams.items():
|
|
if coin_name == v['name']:
|
|
return True
|
|
return False
|
|
|
|
|
|
def signal_handler(sig, frame):
|
|
global swap_client
|
|
logger.info('Signal %d detected, ending program.' % (sig))
|
|
if swap_client is not None:
|
|
swap_client.stopRunning()
|
|
|
|
|
|
def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}):
|
|
daemon_bin = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
|
|
|
|
datadir_path = os.path.expanduser(node_dir)
|
|
|
|
# Rewrite litecoin.conf for 0.21.3
|
|
ltc_conf_path = os.path.join(datadir_path, 'litecoin.conf')
|
|
if os.path.exists(ltc_conf_path):
|
|
config_to_add = ['blockfilterindex=0', 'peerblockfilters=0']
|
|
with open(ltc_conf_path) as fp:
|
|
for line in fp:
|
|
line = line.strip()
|
|
if line in config_to_add:
|
|
config_to_add.remove(line)
|
|
|
|
if len(config_to_add) > 0:
|
|
logging.info('Rewriting litecoin.conf')
|
|
shutil.copyfile(ltc_conf_path, ltc_conf_path + '.last')
|
|
with open(ltc_conf_path, 'a') as fp:
|
|
for line in config_to_add:
|
|
fp.write(line + '\n')
|
|
|
|
args = [daemon_bin, ]
|
|
add_datadir: bool = extra_config.get('add_datadir', True)
|
|
if add_datadir:
|
|
args.append('-datadir=' + datadir_path)
|
|
args += opts
|
|
logging.info('Starting node ' + daemon_bin + ' ' + (('-datadir=' + node_dir) if add_datadir else ''))
|
|
|
|
opened_files = []
|
|
if extra_config.get('stdout_to_file', False):
|
|
stdout_dest = open(os.path.join(datadir_path, extra_config.get('stdout_filename', 'core_stdout.log')), 'w')
|
|
opened_files.append(stdout_dest)
|
|
stderr_dest = stdout_dest
|
|
else:
|
|
stdout_dest = subprocess.PIPE
|
|
stderr_dest = subprocess.PIPE
|
|
|
|
if extra_config.get('use_shell', False):
|
|
str_args = ' '.join(args)
|
|
return Daemon(subprocess.Popen(str_args, shell=True, stdin=subprocess.PIPE, stdout=stdout_dest, stderr=stderr_dest, cwd=datadir_path), opened_files)
|
|
return Daemon(subprocess.Popen(args, stdin=subprocess.PIPE, stdout=stdout_dest, stderr=stderr_dest, cwd=datadir_path), opened_files)
|
|
|
|
|
|
def startXmrDaemon(node_dir, bin_dir, daemon_bin, opts=[]):
|
|
daemon_bin = os.path.expanduser(os.path.join(bin_dir, daemon_bin))
|
|
|
|
datadir_path = os.path.expanduser(node_dir)
|
|
args = [daemon_bin, '--non-interactive', '--config-file=' + os.path.join(datadir_path, 'monerod.conf')] + opts
|
|
logging.info('Starting node {} --data-dir={}'.format(daemon_bin, node_dir))
|
|
|
|
# return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
file_stdout = open(os.path.join(datadir_path, 'core_stdout.log'), 'w')
|
|
file_stderr = open(os.path.join(datadir_path, 'core_stderr.log'), 'w')
|
|
return Daemon(subprocess.Popen(args, stdin=subprocess.PIPE, stdout=file_stdout, stderr=file_stderr, cwd=datadir_path), [file_stdout, file_stderr])
|
|
|
|
|
|
def startXmrWalletDaemon(node_dir, bin_dir, wallet_bin, opts=[]):
|
|
daemon_bin = os.path.expanduser(os.path.join(bin_dir, wallet_bin))
|
|
args = [daemon_bin, '--non-interactive']
|
|
|
|
needs_rewrite: bool = False
|
|
config_to_remove = ['daemon-address=', 'untrusted-daemon=', 'trusted-daemon=', 'proxy=']
|
|
|
|
data_dir = os.path.expanduser(node_dir)
|
|
config_path = os.path.join(data_dir, 'monero_wallet.conf')
|
|
if os.path.exists(config_path):
|
|
args += ['--config-file=' + config_path]
|
|
with open(config_path) as fp:
|
|
for line in fp:
|
|
if any(line.startswith(config_line) for config_line in config_to_remove):
|
|
logging.warning('Found old config in monero_wallet.conf: {}'.format(line.strip()))
|
|
needs_rewrite = True
|
|
args += opts
|
|
|
|
if needs_rewrite:
|
|
logging.info('Rewriting monero_wallet.conf')
|
|
shutil.copyfile(config_path, config_path + '.last')
|
|
with open(config_path + '.last') as fp_from, open(config_path, 'w') as fp_to:
|
|
for line in fp_from:
|
|
if not any(line.startswith(config_line) for config_line in config_to_remove):
|
|
fp_to.write(line)
|
|
|
|
logging.info('Starting wallet daemon {} --wallet-dir={}'.format(daemon_bin, node_dir))
|
|
|
|
# TODO: return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=data_dir)
|
|
wallet_stdout = open(os.path.join(data_dir, 'wallet_stdout.log'), 'w')
|
|
wallet_stderr = open(os.path.join(data_dir, 'wallet_stderr.log'), 'w')
|
|
return Daemon(subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir), [wallet_stdout, wallet_stderr])
|
|
|
|
|
|
def ws_new_client(client, server):
|
|
if swap_client:
|
|
swap_client.log.debug(f'ws_new_client {client["id"]}')
|
|
|
|
|
|
def ws_client_left(client, server):
|
|
if client is None:
|
|
return
|
|
if swap_client:
|
|
swap_client.log.debug(f'ws_client_left {client["id"]}')
|
|
|
|
|
|
def ws_message_received(client, server, message):
|
|
if len(message) > 200:
|
|
message = message[:200] + '..'
|
|
if swap_client:
|
|
swap_client.log.debug(f'ws_message_received {client["id"]} {message}')
|
|
|
|
|
|
def runClient(fp, data_dir, chain, start_only_coins):
|
|
global swap_client
|
|
daemons = []
|
|
pids = []
|
|
threads = []
|
|
settings_path = os.path.join(data_dir, cfg.CONFIG_FILENAME)
|
|
pids_path = os.path.join(data_dir, '.pids')
|
|
|
|
if os.getenv('WALLET_ENCRYPTION_PWD', '') != '':
|
|
if 'decred' in start_only_coins:
|
|
# Workaround for dcrwallet requiring password for initial startup
|
|
logger.warning('Allowing set WALLET_ENCRYPTION_PWD var with --startonlycoin=decred.')
|
|
else:
|
|
raise ValueError('Please unset the WALLET_ENCRYPTION_PWD environment variable.')
|
|
|
|
if not os.path.exists(settings_path):
|
|
raise ValueError('Settings file not found: ' + str(settings_path))
|
|
|
|
with open(settings_path) as fs:
|
|
settings = json.load(fs)
|
|
|
|
swap_client = BasicSwap(fp, data_dir, settings, chain)
|
|
|
|
if os.path.exists(pids_path):
|
|
with open(pids_path) as fd:
|
|
for ln in fd:
|
|
# TODO: try close
|
|
logger.warning('Found pid for daemon {} '.format(ln.strip()))
|
|
|
|
# Ensure daemons are stopped
|
|
swap_client.stopDaemons()
|
|
|
|
# Settings may have been modified
|
|
settings = swap_client.settings
|
|
try:
|
|
# Try start daemons
|
|
for c, v in settings['chainclients'].items():
|
|
|
|
if len(start_only_coins) > 0 and c not in start_only_coins:
|
|
continue
|
|
try:
|
|
coin_id = swap_client.getCoinIdFromName(c)
|
|
display_name = getCoinName(coin_id)
|
|
except Exception as e:
|
|
logger.warning('Error getting coin display name for {}: {}'.format(c, str(e)))
|
|
display_name = 'Unknown'
|
|
if c == 'monero':
|
|
if v['manage_daemon'] is True:
|
|
swap_client.log.info(f'Starting {display_name} daemon')
|
|
filename = 'monerod' + ('.exe' if os.name == 'nt' else '')
|
|
daemons.append(startXmrDaemon(v['datadir'], v['bindir'], filename))
|
|
pid = daemons[-1].handle.pid
|
|
swap_client.log.info('Started {} {}'.format(filename, pid))
|
|
|
|
if v['manage_wallet_daemon'] is True:
|
|
swap_client.log.info(f'Starting {display_name} wallet daemon')
|
|
daemon_addr = '{}:{}'.format(v['rpchost'], v['rpcport'])
|
|
trusted_daemon: bool = swap_client.getXMRTrustedDaemon(coin_id, v['rpchost'])
|
|
opts = ['--daemon-address', daemon_addr, ]
|
|
|
|
proxy_log_str = ''
|
|
proxy_host, proxy_port = swap_client.getXMRWalletProxy(coin_id, v['rpchost'])
|
|
if proxy_host:
|
|
proxy_log_str = ' through proxy'
|
|
opts += ['--proxy', f'{proxy_host}:{proxy_port}', ]
|
|
|
|
swap_client.log.info('daemon-address: {} ({}){}'.format(daemon_addr, 'trusted' if trusted_daemon else 'untrusted', proxy_log_str))
|
|
|
|
daemon_rpcuser = v.get('rpcuser', '')
|
|
daemon_rpcpass = v.get('rpcpassword', '')
|
|
if daemon_rpcuser != '':
|
|
opts.append('--daemon-login')
|
|
opts.append(daemon_rpcuser + ':' + daemon_rpcpass)
|
|
|
|
opts.append('--trusted-daemon' if trusted_daemon else '--untrusted-daemon')
|
|
filename = 'monero-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
|
|
daemons.append(startXmrWalletDaemon(v['datadir'], v['bindir'], filename, opts))
|
|
pid = daemons[-1].handle.pid
|
|
swap_client.log.info('Started {} {}'.format(filename, pid))
|
|
|
|
continue # /monero
|
|
|
|
if c == 'decred':
|
|
appdata = v['datadir']
|
|
extra_opts = [f'--appdata="{appdata}"', ]
|
|
use_shell: bool = True if os.name == 'nt' else False
|
|
if v['manage_daemon'] is True:
|
|
swap_client.log.info(f'Starting {display_name} daemon')
|
|
filename = 'dcrd' + ('.exe' if os.name == 'nt' else '')
|
|
|
|
extra_config = {'add_datadir': False, 'stdout_to_file': True, 'stdout_filename': 'dcrd_stdout.log', 'use_shell': use_shell}
|
|
daemons.append(startDaemon(appdata, v['bindir'], filename, opts=extra_opts, extra_config=extra_config))
|
|
pid = daemons[-1].handle.pid
|
|
swap_client.log.info('Started {} {}'.format(filename, pid))
|
|
|
|
if v['manage_wallet_daemon'] is True:
|
|
swap_client.log.info(f'Starting {display_name} wallet daemon')
|
|
filename = 'dcrwallet' + ('.exe' if os.name == 'nt' else '')
|
|
|
|
wallet_pwd = v['wallet_pwd']
|
|
if wallet_pwd == '':
|
|
# Only set when in startonlycoin mode
|
|
wallet_pwd = os.getenv('WALLET_ENCRYPTION_PWD', '')
|
|
if wallet_pwd != '':
|
|
extra_opts.append(f'--pass="{wallet_pwd}"')
|
|
extra_config = {'add_datadir': False, 'stdout_to_file': True, 'stdout_filename': 'dcrwallet_stdout.log', 'use_shell': use_shell}
|
|
daemons.append(startDaemon(appdata, v['bindir'], filename, opts=extra_opts, extra_config=extra_config))
|
|
pid = daemons[-1].handle.pid
|
|
swap_client.log.info('Started {} {}'.format(filename, pid))
|
|
|
|
continue # /decred
|
|
|
|
if v['manage_daemon'] is True:
|
|
swap_client.log.info(f'Starting {display_name} daemon')
|
|
|
|
filename = c + 'd' + ('.exe' if os.name == 'nt' else '')
|
|
daemons.append(startDaemon(v['datadir'], v['bindir'], filename))
|
|
pid = daemons[-1].handle.pid
|
|
pids.append((c, pid))
|
|
swap_client.setDaemonPID(c, pid)
|
|
swap_client.log.info('Started {} {}'.format(filename, pid))
|
|
if len(pids) > 0:
|
|
with open(pids_path, 'w') as fd:
|
|
for p in pids:
|
|
fd.write('{}:{}\n'.format(*p))
|
|
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
|
|
if len(start_only_coins) > 0:
|
|
logger.info(f'Only running {start_only_coins}. Manually exit with Ctrl + c when ready.')
|
|
while not swap_client.delay_event.wait(0.5):
|
|
pass
|
|
else:
|
|
swap_client.start()
|
|
if 'htmlhost' in settings:
|
|
swap_client.log.info('Starting http server at http://%s:%d.' % (settings['htmlhost'], settings['htmlport']))
|
|
allow_cors = settings['allowcors'] if 'allowcors' in settings else cfg.DEFAULT_ALLOW_CORS
|
|
thread_http = HttpThread(fp, settings['htmlhost'], settings['htmlport'], allow_cors, swap_client)
|
|
threads.append(thread_http)
|
|
thread_http.start()
|
|
|
|
if 'wshost' in settings:
|
|
ws_url = 'ws://{}:{}'.format(settings['wshost'], settings['wsport'])
|
|
swap_client.log.info(f'Starting ws server at {ws_url}.')
|
|
|
|
swap_client.ws_server = WebsocketServer(host=settings['wshost'], port=settings['wsport'])
|
|
swap_client.ws_server.set_fn_new_client(ws_new_client)
|
|
swap_client.ws_server.set_fn_client_left(ws_client_left)
|
|
swap_client.ws_server.set_fn_message_received(ws_message_received)
|
|
swap_client.ws_server.run_forever(threaded=True)
|
|
|
|
logger.info('Exit with Ctrl + c.')
|
|
while not swap_client.delay_event.wait(0.5):
|
|
swap_client.update()
|
|
|
|
except Exception as ex:
|
|
traceback.print_exc()
|
|
|
|
if swap_client.ws_server:
|
|
try:
|
|
swap_client.log.info('Stopping websocket server.')
|
|
swap_client.ws_server.shutdown_gracefully()
|
|
except Exception as ex:
|
|
traceback.print_exc()
|
|
|
|
swap_client.finalise()
|
|
swap_client.log.info('Stopping HTTP threads.')
|
|
for t in threads:
|
|
try:
|
|
t.stop()
|
|
t.join()
|
|
except Exception as ex:
|
|
traceback.print_exc()
|
|
|
|
closed_pids = []
|
|
for d in daemons:
|
|
swap_client.log.info('Interrupting {}'.format(d.handle.pid))
|
|
try:
|
|
d.handle.send_signal(signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT)
|
|
except Exception as e:
|
|
swap_client.log.info('Interrupting %d, error %s', d.handle.pid, str(e))
|
|
for d in daemons:
|
|
try:
|
|
d.handle.wait(timeout=120)
|
|
for fp in [d.handle.stdout, d.handle.stderr, d.handle.stdin] + d.files:
|
|
if fp:
|
|
fp.close()
|
|
closed_pids.append(d.handle.pid)
|
|
except Exception as ex:
|
|
swap_client.log.error('Error: {}'.format(ex))
|
|
|
|
if os.path.exists(pids_path):
|
|
with open(pids_path) as fd:
|
|
lines = fd.read().split('\n')
|
|
still_running = ''
|
|
for ln in lines:
|
|
try:
|
|
if not int(ln.split(':')[1]) in closed_pids:
|
|
still_running += ln + '\n'
|
|
except Exception:
|
|
pass
|
|
with open(pids_path, 'w') as fd:
|
|
fd.write(still_running)
|
|
|
|
|
|
def printVersion():
|
|
logger.info('Basicswap version: %s', __version__)
|
|
|
|
|
|
def printHelp():
|
|
print('Usage: basicswap-run ')
|
|
print('\n--help, -h Print help.')
|
|
print('--version, -v Print version.')
|
|
print('--datadir=PATH Path to basicswap data directory, default:{}.'.format(cfg.BASICSWAP_DATADIR))
|
|
print('--mainnet Run in mainnet mode.')
|
|
print('--testnet Run in testnet mode.')
|
|
print('--regtest Run in regtest mode.')
|
|
print('--startonlycoin Only start the provides coin daemon/s, use this if a chain requires extra processing.')
|
|
|
|
|
|
def main():
|
|
data_dir = None
|
|
chain = 'mainnet'
|
|
start_only_coins = set()
|
|
|
|
for v in sys.argv[1:]:
|
|
if len(v) < 2 or v[0] != '-':
|
|
logger.warning('Unknown argument %s', v)
|
|
continue
|
|
|
|
s = v.split('=')
|
|
name = s[0].strip()
|
|
|
|
for i in range(2):
|
|
if name[0] == '-':
|
|
name = name[1:]
|
|
|
|
if name == 'v' or name == 'version':
|
|
printVersion()
|
|
return 0
|
|
if name == 'h' or name == 'help':
|
|
printHelp()
|
|
return 0
|
|
|
|
if name in ('mainnet', 'testnet', 'regtest'):
|
|
chain = name
|
|
continue
|
|
|
|
if len(s) == 2:
|
|
if name == 'datadir':
|
|
data_dir = os.path.expanduser(s[1])
|
|
continue
|
|
if name == 'startonlycoin':
|
|
for coin in [s.lower() for s in s[1].split(',')]:
|
|
if is_known_coin(coin) is False:
|
|
raise ValueError(f'Unknown coin: {coin}')
|
|
start_only_coins.add(coin)
|
|
continue
|
|
|
|
logger.warning('Unknown argument %s', v)
|
|
|
|
if data_dir is None:
|
|
data_dir = os.path.join(os.path.expanduser(cfg.BASICSWAP_DATADIR))
|
|
logger.info('Using datadir: %s', data_dir)
|
|
logger.info('Chain: %s', chain)
|
|
|
|
if not os.path.exists(data_dir):
|
|
os.makedirs(data_dir)
|
|
|
|
with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp:
|
|
logger.info(os.path.basename(sys.argv[0]) + ', version: ' + __version__ + '\n\n')
|
|
runClient(fp, data_dir, chain, start_only_coins)
|
|
|
|
logger.info('Done.')
|
|
return swap_client.fail_code if swap_client is not None else 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|