mirror of
https://github.com/basicswap/basicswap.git
synced 2024-11-16 15:58:17 +00:00
b077561a6f
When importing from seed dcrwallet creates two accounts, one each on coin_id 20 and 42. Can't see how to force slip44 upgrade to run in dcrwallet, checking for either key instead.
2030 lines
86 KiB
Python
Executable file
2030 lines
86 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 contextlib
|
|
import gnupg
|
|
import hashlib
|
|
import json
|
|
import logging
|
|
import mmap
|
|
import os
|
|
import platform
|
|
import random
|
|
import shutil
|
|
import signal
|
|
import socket
|
|
import socks
|
|
import stat
|
|
import sys
|
|
import tarfile
|
|
import threading
|
|
import time
|
|
import urllib.parse
|
|
import zipfile
|
|
|
|
from urllib.error import ContentTooShortError
|
|
from urllib.parse import _splittype
|
|
from urllib.request import Request, urlopen
|
|
|
|
import basicswap.config as cfg
|
|
from basicswap.base import getaddrinfo_tor
|
|
from basicswap.basicswap import BasicSwap
|
|
from basicswap.chainparams import Coins
|
|
from basicswap.contrib.rpcauth import generate_salt, password_to_hmac
|
|
from basicswap import __version__
|
|
from basicswap.ui.util import getCoinName
|
|
from basicswap.util import toBool
|
|
from basicswap.util.rfc2440 import rfc2440_hash_password
|
|
from bin.basicswap_run import startDaemon, startXmrWalletDaemon
|
|
|
|
PARTICL_VERSION = os.getenv('PARTICL_VERSION', '23.2.7.0')
|
|
PARTICL_VERSION_TAG = os.getenv('PARTICL_VERSION_TAG', '')
|
|
PARTICL_LINUX_EXTRA = os.getenv('PARTICL_LINUX_EXTRA', 'nousb')
|
|
|
|
LITECOIN_VERSION = os.getenv('LITECOIN_VERSION', '0.21.3')
|
|
LITECOIN_VERSION_TAG = os.getenv('LITECOIN_VERSION_TAG', '')
|
|
|
|
BITCOIN_VERSION = os.getenv('BITCOIN_VERSION', '26.0')
|
|
BITCOIN_VERSION_TAG = os.getenv('BITCOIN_VERSION_TAG', '')
|
|
|
|
MONERO_VERSION = os.getenv('MONERO_VERSION', '0.18.3.3')
|
|
MONERO_VERSION_TAG = os.getenv('MONERO_VERSION_TAG', '')
|
|
XMR_SITE_COMMIT = 'd00169a6decd9470ebdf6a75e3351df4ebcd260a' # Lock hashes.txt to monero version
|
|
|
|
PIVX_VERSION = os.getenv('PIVX_VERSION', '5.6.1')
|
|
PIVX_VERSION_TAG = os.getenv('PIVX_VERSION_TAG', '')
|
|
|
|
DASH_VERSION = os.getenv('DASH_VERSION', '20.0.2')
|
|
DASH_VERSION_TAG = os.getenv('DASH_VERSION_TAG', '')
|
|
|
|
FIRO_VERSION = os.getenv('FIRO_VERSION', '0.14.13.2')
|
|
FIRO_VERSION_TAG = os.getenv('FIRO_VERSION_TAG', '')
|
|
|
|
NAV_VERSION = os.getenv('NAV_VERSION', '7.0.3')
|
|
NAV_VERSION_TAG = os.getenv('NAV_VERSION_TAG', '')
|
|
|
|
DCR_VERSION = os.getenv('DCR_VERSION', '1.8.1')
|
|
DCR_VERSION_TAG = os.getenv('DCR_VERSION_TAG', '')
|
|
|
|
GUIX_SSL_CERT_DIR = None
|
|
|
|
ADD_PUBKEY_URL = os.getenv('ADD_PUBKEY_URL', '')
|
|
OVERRIDE_DISABLED_COINS = toBool(os.getenv('OVERRIDE_DISABLED_COINS', 'false'))
|
|
|
|
# If SKIP_GPG_VALIDATION is set to true the script will check hashes but not signatures
|
|
SKIP_GPG_VALIDATION = toBool(os.getenv('SKIP_GPG_VALIDATION', 'false'))
|
|
|
|
|
|
known_coins = {
|
|
'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)),
|
|
'litecoin': (LITECOIN_VERSION, LITECOIN_VERSION_TAG, ('davidburkett38',)),
|
|
'bitcoin': (BITCOIN_VERSION, BITCOIN_VERSION_TAG, ('laanwj',)),
|
|
'namecoin': ('0.18.0', '', ('JeremyRand',)),
|
|
'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)),
|
|
'pivx': (PIVX_VERSION, PIVX_VERSION_TAG, ('fuzzbawls',)),
|
|
'dash': (DASH_VERSION, DASH_VERSION_TAG, ('pasta',)),
|
|
'firo': (FIRO_VERSION, FIRO_VERSION_TAG, ('reuben',)),
|
|
'navcoin': (NAV_VERSION, NAV_VERSION_TAG, ('nav_builder',)),
|
|
'decred': (DCR_VERSION, DCR_VERSION_TAG, ('decred_release',)),
|
|
}
|
|
|
|
disabled_coins = [
|
|
'navcoin',
|
|
'namecoin', # Needs update
|
|
]
|
|
|
|
expected_key_ids = {
|
|
'tecnovert': ('13F13651C9CF0D6B',),
|
|
'thrasher': ('FE3348877809386C',),
|
|
'laanwj': ('1E4AED62986CD25D',),
|
|
'JeremyRand': ('2DBE339E29F6294C',),
|
|
'binaryfate': ('F0AF4D462A0BDF92',),
|
|
'davidburkett38': ('3620E9D387E55666',),
|
|
'fuzzbawls': ('C1ABA64407731FD9',),
|
|
'pasta': ('52527BEDABE87984',),
|
|
'reuben': ('1290A1D0FA7EE109',),
|
|
'nav_builder': ('2782262BF6E7FADB',),
|
|
'decred_release': ('6D897EDF518A031D',),
|
|
}
|
|
|
|
USE_PLATFORM = os.getenv('USE_PLATFORM', platform.system())
|
|
if USE_PLATFORM == 'Darwin':
|
|
BIN_ARCH = 'osx64'
|
|
FILE_EXT = 'tar.gz'
|
|
elif USE_PLATFORM == 'Windows':
|
|
BIN_ARCH = 'win64'
|
|
FILE_EXT = 'zip'
|
|
else:
|
|
machine: str = platform.machine()
|
|
if 'arm' in machine:
|
|
BIN_ARCH = 'arm-linux-gnueabihf'
|
|
else:
|
|
BIN_ARCH = machine + '-linux-gnu'
|
|
FILE_EXT = 'tar.gz'
|
|
|
|
# Allow manually overriding the arch tag
|
|
BIN_ARCH = os.getenv('BIN_ARCH', BIN_ARCH)
|
|
FILE_EXT = os.getenv('FILE_EXT', FILE_EXT)
|
|
|
|
logger = logging.getLogger()
|
|
logger.level = logging.INFO
|
|
if not len(logger.handlers):
|
|
logger.addHandler(logging.StreamHandler(sys.stdout))
|
|
|
|
UI_HTML_PORT = int(os.getenv('UI_HTML_PORT', 12700))
|
|
UI_WS_PORT = int(os.getenv('UI_WS_PORT', 11700))
|
|
COINS_RPCBIND_IP = os.getenv('COINS_RPCBIND_IP', '127.0.0.1')
|
|
|
|
PART_ZMQ_PORT = int(os.getenv('PART_ZMQ_PORT', 20792))
|
|
PART_RPC_HOST = os.getenv('PART_RPC_HOST', '127.0.0.1')
|
|
PART_RPC_PORT = int(os.getenv('PART_RPC_PORT', 19792))
|
|
PART_ONION_PORT = int(os.getenv('PART_ONION_PORT', 51734))
|
|
PART_RPC_USER = os.getenv('PART_RPC_USER', '')
|
|
PART_RPC_PWD = os.getenv('PART_RPC_PWD', '')
|
|
|
|
XMR_RPC_HOST = os.getenv('XMR_RPC_HOST', '127.0.0.1')
|
|
BASE_XMR_RPC_PORT = int(os.getenv('BASE_XMR_RPC_PORT', 29798))
|
|
BASE_XMR_ZMQ_PORT = int(os.getenv('BASE_XMR_ZMQ_PORT', 30898))
|
|
BASE_XMR_WALLET_PORT = int(os.getenv('BASE_XMR_WALLET_PORT', 29998))
|
|
XMR_WALLET_RPC_HOST = os.getenv('XMR_WALLET_RPC_HOST', '127.0.0.1')
|
|
XMR_WALLET_RPC_USER = os.getenv('XMR_WALLET_RPC_USER', 'xmr_wallet_user')
|
|
XMR_WALLET_RPC_PWD = os.getenv('XMR_WALLET_RPC_PWD', 'xmr_wallet_pwd')
|
|
XMR_RPC_USER = os.getenv('XMR_RPC_USER', '')
|
|
XMR_RPC_PWD = os.getenv('XMR_RPC_PWD', '')
|
|
DEFAULT_XMR_RESTORE_HEIGHT = int(os.getenv('DEFAULT_XMR_RESTORE_HEIGHT', 2245107))
|
|
|
|
LTC_RPC_HOST = os.getenv('LTC_RPC_HOST', '127.0.0.1')
|
|
LTC_RPC_PORT = int(os.getenv('LTC_RPC_PORT', 19895))
|
|
LTC_ONION_PORT = int(os.getenv('LTC_ONION_PORT', 9333))
|
|
LTC_RPC_USER = os.getenv('LTC_RPC_USER', '')
|
|
LTC_RPC_PWD = os.getenv('LTC_RPC_PWD', '')
|
|
|
|
BTC_RPC_HOST = os.getenv('BTC_RPC_HOST', '127.0.0.1')
|
|
BTC_RPC_PORT = int(os.getenv('BTC_RPC_PORT', 19996))
|
|
BTC_ONION_PORT = int(os.getenv('BTC_ONION_PORT', 8334))
|
|
BTC_RPC_USER = os.getenv('BTC_RPC_USER', '')
|
|
BTC_RPC_PWD = os.getenv('BTC_RPC_PWD', '')
|
|
|
|
DCR_RPC_HOST = os.getenv('DCR_RPC_HOST', '127.0.0.1')
|
|
DCR_RPC_PORT = int(os.getenv('DCR_RPC_PORT', 9109))
|
|
DCR_WALLET_RPC_HOST = os.getenv('DCR_WALLET_RPC_HOST', '127.0.0.1')
|
|
DCR_WALLET_RPC_PORT = int(os.getenv('DCR_WALLET_RPC_PORT', 9209))
|
|
DCR_WALLET_PWD = os.getenv('DCR_WALLET_PWD', random.randbytes(random.randint(14, 18)).hex())
|
|
DCR_RPC_USER = os.getenv('DCR_RPC_USER', 'user')
|
|
DCR_RPC_PWD = os.getenv('DCR_RPC_PWD', random.randbytes(random.randint(14, 18)).hex())
|
|
|
|
NMC_RPC_HOST = os.getenv('NMC_RPC_HOST', '127.0.0.1')
|
|
NMC_RPC_PORT = int(os.getenv('NMC_RPC_PORT', 19698))
|
|
|
|
PIVX_RPC_HOST = os.getenv('PIVX_RPC_HOST', '127.0.0.1')
|
|
PIVX_RPC_PORT = int(os.getenv('PIVX_RPC_PORT', 51473))
|
|
PIVX_ONION_PORT = int(os.getenv('PIVX_ONION_PORT', 51472)) # nDefaultPort
|
|
PIVX_RPC_USER = os.getenv('PIVX_RPC_USER', '')
|
|
PIVX_RPC_PWD = os.getenv('PIVX_RPC_PWD', '')
|
|
|
|
DASH_RPC_HOST = os.getenv('DASH_RPC_HOST', '127.0.0.1')
|
|
DASH_RPC_PORT = int(os.getenv('DASH_RPC_PORT', 9998))
|
|
DASH_ONION_PORT = int(os.getenv('DASH_ONION_PORT', 9999)) # nDefaultPort
|
|
DASH_RPC_USER = os.getenv('DASH_RPC_USER', '')
|
|
DASH_RPC_PWD = os.getenv('DASH_RPC_PWD', '')
|
|
|
|
FIRO_RPC_HOST = os.getenv('FIRO_RPC_HOST', '127.0.0.1')
|
|
FIRO_RPC_PORT = int(os.getenv('FIRO_RPC_PORT', 8888))
|
|
FIRO_ONION_PORT = int(os.getenv('FIRO_ONION_PORT', 8168)) # nDefaultPort
|
|
FIRO_RPC_USER = os.getenv('FIRO_RPC_USER', '')
|
|
FIRO_RPC_PWD = os.getenv('FIRO_RPC_PWD', '')
|
|
|
|
NAV_RPC_HOST = os.getenv('NAV_RPC_HOST', '127.0.0.1')
|
|
NAV_RPC_PORT = int(os.getenv('NAV_RPC_PORT', 44444))
|
|
NAV_ONION_PORT = int(os.getenv('NAV_ONION_PORT', 8334)) # TODO?
|
|
NAV_RPC_USER = os.getenv('NAV_RPC_USER', '')
|
|
NAV_RPC_PWD = os.getenv('NAV_RPC_PWD', '')
|
|
|
|
TOR_PROXY_HOST = os.getenv('TOR_PROXY_HOST', '127.0.0.1')
|
|
TOR_PROXY_PORT = int(os.getenv('TOR_PROXY_PORT', 9050))
|
|
TOR_CONTROL_PORT = int(os.getenv('TOR_CONTROL_PORT', 9051))
|
|
TOR_DNS_PORT = int(os.getenv('TOR_DNS_PORT', 5353))
|
|
TEST_TOR_PROXY = toBool(os.getenv('TEST_TOR_PROXY', 'true')) # Expects a known exit node
|
|
TEST_ONION_LINK = toBool(os.getenv('TEST_ONION_LINK', 'false'))
|
|
|
|
BITCOIN_FASTSYNC_URL = os.getenv('BITCOIN_FASTSYNC_URL', 'https://eu2.contabostorage.com/1f50a74c9dc14888a8664415dad3d020:utxosets/')
|
|
BITCOIN_FASTSYNC_FILE = os.getenv('BITCOIN_FASTSYNC_FILE', 'utxo-snapshot-bitcoin-mainnet-769818.tar')
|
|
|
|
# Encrypt new wallets with this password, must match the Particl wallet password when adding coins
|
|
WALLET_ENCRYPTION_PWD = os.getenv('WALLET_ENCRYPTION_PWD', '')
|
|
|
|
use_tor_proxy: bool = False
|
|
|
|
monerod_proxy_config = [
|
|
f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}',
|
|
'proxy-allow-dns-leaks=0',
|
|
'no-igd=1', # Disable UPnP port mapping
|
|
'hide-my-port=1', # Don't share the p2p port
|
|
'p2p-bind-ip=127.0.0.1', # Don't broadcast ip
|
|
'in-peers=0', # Changes "error" in log to "incoming connections disabled"
|
|
]
|
|
|
|
monero_wallet_rpc_proxy_config = [
|
|
'daemon-ssl-allow-any-cert=1',
|
|
]
|
|
|
|
|
|
default_socket = socket.socket
|
|
default_socket_timeout = socket.getdefaulttimeout()
|
|
default_socket_getaddrinfo = socket.getaddrinfo
|
|
|
|
|
|
def exitWithError(error_msg):
|
|
sys.stderr.write('Error: {}, exiting.\n'.format(error_msg))
|
|
sys.exit(1)
|
|
|
|
|
|
def make_reporthook(read_start=0):
|
|
read = read_start # Number of bytes read so far
|
|
last_percent_str = ''
|
|
time_last = time.time()
|
|
read_last = read_start
|
|
display_last = time_last
|
|
abo = 7
|
|
average_buffer = [-1] * 8
|
|
|
|
def reporthook(blocknum, blocksize, totalsize):
|
|
nonlocal read, last_percent_str, time_last, read_last, display_last, read_start
|
|
nonlocal average_buffer, abo
|
|
read += blocksize
|
|
|
|
# totalsize excludes read_start
|
|
use_size = totalsize + read_start
|
|
dl_complete: bool = totalsize > 0 and read >= use_size
|
|
time_now = time.time()
|
|
time_delta = time_now - time_last
|
|
if time_delta < 4.0 and not dl_complete:
|
|
return
|
|
|
|
# Avoid division by zero by picking a value
|
|
if time_delta <= 0.0:
|
|
time_delta = 0.01
|
|
|
|
bytes_delta = read - read_last
|
|
time_last = time_now
|
|
read_last = read
|
|
bits_per_second = (bytes_delta * 8) / time_delta
|
|
|
|
abo = 0 if abo >= 7 else abo + 1
|
|
average_buffer[abo] = bits_per_second
|
|
|
|
samples = 0
|
|
average_bits_per_second = 0
|
|
for sample in average_buffer:
|
|
if sample < 0:
|
|
continue
|
|
average_bits_per_second += sample
|
|
samples += 1
|
|
average_bits_per_second /= samples
|
|
|
|
speed_str: str
|
|
if average_bits_per_second > 1000 ** 3:
|
|
speed_str = '{:.2f} Gbps'.format(average_bits_per_second / (1000 ** 3))
|
|
elif average_bits_per_second > 1000 ** 2:
|
|
speed_str = '{:.2f} Mbps'.format(average_bits_per_second / (1000 ** 2))
|
|
else:
|
|
speed_str = '{:.2f} kbps'.format(average_bits_per_second / 1000)
|
|
|
|
if totalsize > 0:
|
|
percent_str = '%5.0f%%' % (read * 1e2 / use_size)
|
|
if percent_str != last_percent_str or time_now - display_last > 10:
|
|
logger.info(percent_str + ' ' + speed_str)
|
|
last_percent_str = percent_str
|
|
display_last = time_now
|
|
else:
|
|
logger.info(f'Read {read}, {speed_str}')
|
|
return reporthook
|
|
|
|
|
|
def urlretrieve(url, filename, reporthook=None, data=None, resume_from=0):
|
|
# urlretrieve with resume
|
|
url_type, path = _splittype(url)
|
|
|
|
req = Request(url)
|
|
if resume_from > 0:
|
|
logger.info(f'Attempting to resume from byte {resume_from}')
|
|
req.add_header('Range', f'bytes={resume_from}-')
|
|
with contextlib.closing(urlopen(req)) as fp:
|
|
headers = fp.info()
|
|
|
|
# Just return the local path and the "headers" for file://
|
|
# URLs. No sense in performing a copy unless requested.
|
|
if url_type == "file" and not filename:
|
|
return os.path.normpath(path), headers
|
|
|
|
with open(filename, 'ab' if resume_from > 0 else 'wb') as tfp:
|
|
result = filename, headers
|
|
bs = 1024 * 8
|
|
size = -1
|
|
read = resume_from
|
|
blocknum = 0
|
|
range_from = 0
|
|
if "content-length" in headers:
|
|
size = int(headers["Content-Length"])
|
|
if "Content-Range" in headers:
|
|
range_str = headers["Content-Range"]
|
|
offset = range_str.find('-')
|
|
range_from = int(range_str[6:offset])
|
|
if resume_from != range_from:
|
|
raise ValueError('Download is not resuming from the expected byte')
|
|
|
|
if reporthook:
|
|
reporthook(blocknum, bs, size)
|
|
|
|
while True:
|
|
block = fp.read(bs)
|
|
if not block:
|
|
break
|
|
read += len(block)
|
|
tfp.write(block)
|
|
blocknum += 1
|
|
if reporthook:
|
|
reporthook(blocknum, bs, size)
|
|
|
|
if size >= 0 and read < size:
|
|
raise ContentTooShortError(
|
|
"retrieval incomplete: got only %i out of %i bytes"
|
|
% (read, size), result)
|
|
|
|
return result
|
|
|
|
|
|
def setConnectionParameters(timeout: int = 5, allow_set_tor: bool = True):
|
|
opener = urllib.request.build_opener()
|
|
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
|
|
urllib.request.install_opener(opener)
|
|
|
|
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_tor # Without this accessing .onion links would fail
|
|
|
|
# Set low timeout for urlretrieve connections
|
|
socket.setdefaulttimeout(timeout)
|
|
|
|
|
|
def popConnectionParameters() -> None:
|
|
if use_tor_proxy:
|
|
socket.socket = default_socket
|
|
socket.getaddrinfo = default_socket_getaddrinfo
|
|
socket.setdefaulttimeout(default_socket_timeout)
|
|
|
|
|
|
def downloadFile(url: str, path: str, timeout=5, resume_from=0) -> None:
|
|
logger.info(f'Downloading file {url}')
|
|
logger.info(f'To {path}')
|
|
try:
|
|
setConnectionParameters(timeout=timeout)
|
|
urlretrieve(url, path, make_reporthook(resume_from), resume_from=resume_from)
|
|
finally:
|
|
popConnectionParameters()
|
|
|
|
|
|
def downloadBytes(url):
|
|
try:
|
|
setConnectionParameters()
|
|
return urllib.request.urlopen(url).read()
|
|
finally:
|
|
popConnectionParameters()
|
|
|
|
|
|
def importPubkeyFromUrls(gpg, pubkeyurls):
|
|
for url in pubkeyurls:
|
|
try:
|
|
logger.info('Importing public key from url: ' + url)
|
|
rv = gpg.import_keys(downloadBytes(url))
|
|
for key in rv.fingerprints:
|
|
gpg.trust_keys(key, 'TRUST_FULLY')
|
|
break
|
|
except Exception as e:
|
|
logging.warning('Import from url failed: %s', str(e))
|
|
|
|
|
|
def testTorConnection():
|
|
test_url = 'https://check.torproject.org/'
|
|
logger.info('Testing TOR connection at: ' + test_url)
|
|
|
|
test_response = downloadBytes(test_url).decode('utf-8')
|
|
assert ('Congratulations. This browser is configured to use Tor.' in test_response)
|
|
logger.info('TOR is working.')
|
|
|
|
|
|
def testOnionLink():
|
|
test_url = 'http://jqyzxhjk6psc6ul5jnfwloamhtyh7si74b4743k2qgpskwwxrzhsxmad.onion'
|
|
logger.info('Testing onion site: ' + test_url)
|
|
test_response = downloadBytes(test_url).decode('utf-8')
|
|
assert ('The Tor Project\'s free software protects your privacy online.' in test_response)
|
|
logger.info('Onion links work.')
|
|
|
|
|
|
def havePubkey(gpg, key_id):
|
|
for key in gpg.list_keys():
|
|
if key['keyid'] == key_id:
|
|
return True
|
|
return False
|
|
|
|
|
|
def downloadPIVXParams(output_dir):
|
|
# util/fetch-params.sh
|
|
|
|
if os.path.exists(output_dir):
|
|
logger.info(f'Skipping PIVX params download, path exists: {output_dir}')
|
|
return
|
|
os.makedirs(output_dir)
|
|
|
|
source_url = 'https://download.z.cash/downloads/'
|
|
files = {
|
|
'sapling-spend.params': '8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13',
|
|
'sapling-output.params': '2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4',
|
|
}
|
|
|
|
try:
|
|
setConnectionParameters()
|
|
for k, v in files.items():
|
|
url = urllib.parse.urljoin(source_url, k)
|
|
path = os.path.join(output_dir, k)
|
|
downloadFile(url, path)
|
|
hasher = hashlib.sha256()
|
|
with open(path, 'rb') as fp:
|
|
hasher.update(fp.read())
|
|
file_hash = hasher.hexdigest()
|
|
logger.info('%s hash: %s', k, file_hash)
|
|
assert file_hash == v
|
|
finally:
|
|
popConnectionParameters()
|
|
|
|
|
|
def isValidSignature(result):
|
|
if result.valid is False \
|
|
and (result.status == 'signature valid' and result.key_status == 'signing key has expired'):
|
|
return True
|
|
return result.valid
|
|
|
|
|
|
def ensureValidSignatureBy(result, signing_key_name):
|
|
if not isValidSignature(result):
|
|
raise ValueError('Signature verification failed.')
|
|
|
|
if result.key_id not in expected_key_ids[signing_key_name]:
|
|
raise ValueError('Signature made by unexpected keyid: ' + result.key_id)
|
|
|
|
|
|
def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts={}):
|
|
version, version_tag, signers = version_data
|
|
logger.info('extractCore %s v%s%s', coin, version, version_tag)
|
|
extract_core_overwrite = extra_opts.get('extract_core_overwrite', True)
|
|
|
|
if coin in ('monero', 'firo'):
|
|
if coin == 'monero':
|
|
bins = ['monerod', 'monero-wallet-rpc']
|
|
elif coin == 'firo':
|
|
bins = [coin + 'd', coin + '-cli', coin + '-tx']
|
|
else:
|
|
raise ValueError('Unknown coin')
|
|
|
|
if 'win32' in BIN_ARCH or 'win64' in BIN_ARCH:
|
|
with zipfile.ZipFile(release_path) as fz:
|
|
namelist = fz.namelist()
|
|
for b in bins:
|
|
b += '.exe'
|
|
out_path = os.path.join(bin_dir, b)
|
|
if (not os.path.exists(out_path)) or extract_core_overwrite:
|
|
for entry in namelist:
|
|
if entry.endswith(b):
|
|
with open(out_path, 'wb') as fout:
|
|
fout.write(fz.read(entry))
|
|
try:
|
|
os.chmod(out_path, stat.S_IRWXU | stat.S_IXGRP | stat.S_IXOTH)
|
|
except Exception as e:
|
|
logging.warning('Unable to set file permissions: %s, for %s', str(e), out_path)
|
|
break
|
|
return
|
|
|
|
num_exist = 0
|
|
for b in bins:
|
|
out_path = os.path.join(bin_dir, b)
|
|
if os.path.exists(out_path):
|
|
num_exist += 1
|
|
if not extract_core_overwrite and num_exist == len(bins):
|
|
logger.info('Skipping extract, files exist.')
|
|
return
|
|
|
|
with tarfile.open(release_path) as ft:
|
|
for member in ft.getmembers():
|
|
if member.isdir():
|
|
continue
|
|
bin_name = os.path.basename(member.name)
|
|
if bin_name not in bins:
|
|
continue
|
|
out_path = os.path.join(bin_dir, bin_name)
|
|
if (not os.path.exists(out_path)) or extract_core_overwrite:
|
|
with open(out_path, 'wb') as fout, ft.extractfile(member) as fi:
|
|
fout.write(fi.read())
|
|
try:
|
|
os.chmod(out_path, stat.S_IRWXU | stat.S_IXGRP | stat.S_IXOTH)
|
|
except Exception as e:
|
|
logging.warning('Unable to set file permissions: %s, for %s', str(e), out_path)
|
|
return
|
|
|
|
dir_name = 'dashcore' if coin == 'dash' else coin
|
|
if coin == 'decred':
|
|
bins = ['dcrd', 'dcrwallet']
|
|
else:
|
|
bins = [coin + 'd', coin + '-cli', coin + '-tx']
|
|
versions = version.split('.')
|
|
if int(versions[0]) >= 22 or int(versions[1]) >= 19:
|
|
bins.append(coin + '-wallet')
|
|
|
|
def get_archive_path(b):
|
|
if coin == 'pivx':
|
|
return '{}-{}/bin/{}'.format(dir_name, version, b)
|
|
elif coin == 'particl' and '_nousb-' in release_path:
|
|
return '{}-{}_nousb/bin/{}'.format(dir_name, version + version_tag, b)
|
|
elif coin == 'decred':
|
|
return '{}-{}-v{}/{}'.format(dir_name, extra_opts['arch_name'], version, b)
|
|
else:
|
|
return '{}-{}/bin/{}'.format(dir_name, version + version_tag, b)
|
|
|
|
if 'win32' in BIN_ARCH or 'win64' in BIN_ARCH:
|
|
with zipfile.ZipFile(release_path) as fz:
|
|
for b in bins:
|
|
b += '.exe'
|
|
out_path = os.path.join(bin_dir, b)
|
|
if (not os.path.exists(out_path)) or extract_core_overwrite:
|
|
with open(out_path, 'wb') as fout:
|
|
fout.write(fz.read(get_archive_path(b)))
|
|
try:
|
|
os.chmod(out_path, stat.S_IRWXU | stat.S_IXGRP | stat.S_IXOTH)
|
|
except Exception as e:
|
|
logging.warning('Unable to set file permissions: %s, for %s', str(e), out_path)
|
|
else:
|
|
with tarfile.open(release_path) as ft:
|
|
for b in bins:
|
|
out_path = os.path.join(bin_dir, b)
|
|
if not os.path.exists(out_path) or extract_core_overwrite:
|
|
with open(out_path, 'wb') as fout, ft.extractfile(get_archive_path(b)) as fi:
|
|
fout.write(fi.read())
|
|
try:
|
|
os.chmod(out_path, stat.S_IRWXU | stat.S_IXGRP | stat.S_IXOTH)
|
|
except Exception as e:
|
|
logging.warning('Unable to set file permissions: %s, for %s', str(e), out_path)
|
|
|
|
|
|
def prepareCore(coin, version_data, settings, data_dir, extra_opts={}):
|
|
version, version_tag, signers = version_data
|
|
logger.info('prepareCore %s v%s%s', coin, version, version_tag)
|
|
|
|
bin_dir = os.path.expanduser(settings['chainclients'][coin]['bindir'])
|
|
if not os.path.exists(bin_dir):
|
|
os.makedirs(bin_dir)
|
|
|
|
filename_extra = ''
|
|
if 'osx' in BIN_ARCH:
|
|
os_dir_name = 'osx-unsigned'
|
|
os_name = 'osx'
|
|
elif 'win32' in BIN_ARCH or 'win64' in BIN_ARCH:
|
|
os_dir_name = 'win-unsigned'
|
|
os_name = 'win'
|
|
else:
|
|
os_dir_name = 'linux'
|
|
os_name = 'linux'
|
|
if coin == 'particl':
|
|
filename_extra = PARTICL_LINUX_EXTRA
|
|
|
|
signing_key_name = signers[0]
|
|
if coin == 'monero':
|
|
use_file_ext = 'tar.bz2' if FILE_EXT == 'tar.gz' else FILE_EXT
|
|
release_filename = '{}-{}-{}.{}'.format(coin, version, BIN_ARCH, use_file_ext)
|
|
if os_name == 'osx':
|
|
os_name = 'mac'
|
|
|
|
architecture = 'x64'
|
|
if 'aarch64' in BIN_ARCH:
|
|
architecture = 'armv8'
|
|
elif 'arm' in BIN_ARCH:
|
|
architecture = 'armv7'
|
|
|
|
release_url = 'https://downloads.getmonero.org/cli/monero-{}-{}-v{}.{}'.format(os_name, architecture, version, use_file_ext)
|
|
release_path = os.path.join(bin_dir, release_filename)
|
|
if not os.path.exists(release_path):
|
|
downloadFile(release_url, release_path)
|
|
|
|
assert_filename = 'monero-{}-hashes.txt'.format(version)
|
|
# assert_url = 'https://www.getmonero.org/downloads/hashes.txt'
|
|
assert_url = 'https://raw.githubusercontent.com/monero-project/monero-site/{}/downloads/hashes.txt'.format(XMR_SITE_COMMIT)
|
|
assert_path = os.path.join(bin_dir, assert_filename)
|
|
if not os.path.exists(assert_path):
|
|
downloadFile(assert_url, assert_path)
|
|
|
|
elif coin == 'decred':
|
|
arch_name = BIN_ARCH
|
|
if USE_PLATFORM == 'Darwin':
|
|
arch_name = 'darwin-amd64'
|
|
elif USE_PLATFORM == 'Windows':
|
|
arch_name = 'windows-amd64'
|
|
else:
|
|
machine: str = platform.machine()
|
|
if 'arm' in machine:
|
|
arch_name = 'linux-arm'
|
|
else:
|
|
arch_name = 'linux-amd64'
|
|
|
|
extra_opts['arch_name'] = arch_name
|
|
release_filename = '{}-{}-{}.{}'.format(coin, version, arch_name, FILE_EXT)
|
|
release_page_url = 'https://github.com/decred/decred-binaries/releases/download/v{}'.format(version)
|
|
release_url = release_page_url + '/' + 'decred-{}-v{}.{}'.format(arch_name, version, FILE_EXT)
|
|
release_path = os.path.join(bin_dir, release_filename)
|
|
if not os.path.exists(release_path):
|
|
downloadFile(release_url, release_path)
|
|
|
|
assert_filename = 'decred-v{}-manifest.txt'.format(version)
|
|
assert_url = release_page_url + '/' + assert_filename
|
|
assert_path = os.path.join(bin_dir, assert_filename)
|
|
if not os.path.exists(assert_path):
|
|
downloadFile(assert_url, assert_path)
|
|
|
|
assert_sig_filename = assert_filename + '.asc'
|
|
assert_sig_url = assert_url + '.asc'
|
|
assert_sig_path = os.path.join(bin_dir, assert_sig_filename)
|
|
if not os.path.exists(assert_sig_path):
|
|
downloadFile(assert_sig_url, assert_sig_path)
|
|
else:
|
|
major_version = int(version.split('.')[0])
|
|
|
|
use_guix: bool = coin in ('dash', ) or major_version >= 22
|
|
arch_name = BIN_ARCH
|
|
if os_name == 'osx' and use_guix:
|
|
arch_name = 'x86_64-apple-darwin'
|
|
if coin == 'particl':
|
|
arch_name += '18'
|
|
|
|
release_filename = '{}-{}-{}.{}'.format(coin, version + version_tag, arch_name, FILE_EXT)
|
|
if filename_extra != '':
|
|
if use_guix:
|
|
release_filename = '{}-{}_{}-{}.{}'.format(coin, version + version_tag, filename_extra, arch_name, FILE_EXT)
|
|
else:
|
|
release_filename = '{}-{}-{}_{}.{}'.format(coin, version + version_tag, arch_name, filename_extra, FILE_EXT)
|
|
if coin == 'particl':
|
|
release_url = 'https://github.com/particl/particl-core/releases/download/v{}/{}'.format(version + version_tag, release_filename)
|
|
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version)
|
|
if use_guix:
|
|
assert_url = f'https://raw.githubusercontent.com/particl/guix.sigs/master/{version}/{signing_key_name}/all.SHA256SUMS'
|
|
else:
|
|
assert_url = 'https://raw.githubusercontent.com/particl/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name, assert_filename)
|
|
elif coin == 'litecoin':
|
|
release_url = 'https://github.com/litecoin-project/litecoin/releases/download/v{}/{}'.format(version + version_tag, release_filename)
|
|
assert_filename = '{}-core-{}-{}-build.assert'.format(coin, os_name, '.'.join(version.split('.')[:2]))
|
|
assert_url = 'https://raw.githubusercontent.com/litecoin-project/gitian.sigs.ltc/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
|
|
elif coin == 'bitcoin':
|
|
release_url = 'https://bitcoincore.org/bin/bitcoin-core-{}/{}'.format(version, release_filename)
|
|
assert_filename = '{}-core-{}-{}-build.assert'.format(coin, os_name, '.'.join(version.split('.')[:2]))
|
|
if use_guix:
|
|
assert_url = f'https://raw.githubusercontent.com/bitcoin-core/guix.sigs/main/{version}/{signing_key_name}/all.SHA256SUMS'
|
|
else:
|
|
assert_url = 'https://raw.githubusercontent.com/bitcoin-core/gitian.sigs/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
|
|
elif coin == 'namecoin':
|
|
release_url = 'https://beta.namecoin.org/files/namecoin-core/namecoin-core-{}/{}'.format(version, release_filename)
|
|
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0])
|
|
assert_url = 'https://raw.githubusercontent.com/namecoin/gitian.sigs/master/%s-%s/%s/%s' % (version, os_dir_name, signing_key_name, assert_filename)
|
|
elif coin == 'pivx':
|
|
release_filename = '{}-{}-{}.{}'.format(coin, version, BIN_ARCH, FILE_EXT)
|
|
release_url = 'https://github.com/PIVX-Project/PIVX/releases/download/v{}/{}'.format(version + version_tag, release_filename)
|
|
assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0])
|
|
assert_url = 'https://raw.githubusercontent.com/PIVX-Project/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name.capitalize(), assert_filename)
|
|
elif coin == 'dash':
|
|
release_filename = '{}-{}-{}.{}'.format('dashcore', version + version_tag, arch_name, FILE_EXT)
|
|
release_url = 'https://github.com/dashpay/dash/releases/download/v{}/{}'.format(version + version_tag, release_filename)
|
|
assert_filename = '{}-{}-{}-build.assert'.format(coin, arch_name, major_version)
|
|
assert_url = f'https://raw.githubusercontent.com/dashpay/guix.sigs/master/{version}/{signing_key_name}/codesigned.SHA256SUMS'
|
|
elif coin == 'firo':
|
|
arch_name = BIN_ARCH
|
|
if BIN_ARCH == 'x86_64-linux-gnu':
|
|
arch_name = 'linux64'
|
|
elif BIN_ARCH == 'osx64':
|
|
arch_name = 'macos'
|
|
release_filename = '{}-{}-{}{}.{}'.format('firo', version + version_tag, arch_name, filename_extra, FILE_EXT)
|
|
release_url = 'https://github.com/firoorg/firo/releases/download/v{}/{}'.format(version + version_tag, release_filename)
|
|
assert_url = 'https://github.com/firoorg/firo/releases/download/v%s/SHA256SUMS' % (version + version_tag)
|
|
elif coin == 'navcoin':
|
|
release_filename = '{}-{}-{}.{}'.format(coin, version, BIN_ARCH, FILE_EXT)
|
|
release_url = 'https://github.com/navcoin/navcoin-core/releases/download/{}/{}'.format(version + version_tag, release_filename)
|
|
assert_filename = 'SHA256SUM_7.0.3.asc'
|
|
assert_sig_filename = 'SHA256SUM_7.0.3.asc.sig'
|
|
assert_url = 'https://github.com/navcoin/navcoin-core/releases/download/{}/{}'.format(version + version_tag, assert_filename)
|
|
else:
|
|
raise ValueError('Unknown coin')
|
|
|
|
release_path = os.path.join(bin_dir, release_filename)
|
|
if not os.path.exists(release_path):
|
|
downloadFile(release_url, release_path)
|
|
|
|
# Rename assert files with full version
|
|
assert_filename = '{}-{}-{}-build-{}.assert'.format(coin, os_name, version, signing_key_name)
|
|
assert_path = os.path.join(bin_dir, assert_filename)
|
|
if not os.path.exists(assert_path):
|
|
downloadFile(assert_url, assert_path)
|
|
|
|
if coin not in ('firo', ):
|
|
assert_sig_url = assert_url + ('.asc' if use_guix else '.sig')
|
|
if coin not in ('nav', ):
|
|
assert_sig_filename = '{}-{}-{}-build-{}.assert.sig'.format(coin, os_name, version, signing_key_name)
|
|
assert_sig_path = os.path.join(bin_dir, assert_sig_filename)
|
|
if not os.path.exists(assert_sig_path):
|
|
downloadFile(assert_sig_url, assert_sig_path)
|
|
|
|
hasher = hashlib.sha256()
|
|
with open(release_path, 'rb') as fp:
|
|
hasher.update(fp.read())
|
|
release_hash = hasher.digest()
|
|
|
|
logger.info('%s hash: %s', release_filename, release_hash.hex())
|
|
with open(assert_path, 'rb', 0) as fp, mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ) as s:
|
|
if s.find(bytes(release_hash.hex(), 'utf-8')) == -1:
|
|
raise ValueError('Error: release hash %s not found in assert file.' % (release_hash.hex()))
|
|
else:
|
|
logger.info('Found release hash in assert file.')
|
|
|
|
if SKIP_GPG_VALIDATION:
|
|
logger.warning('Skipping binary signature check as SKIP_GPG_VALIDATION env var is set.')
|
|
extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts)
|
|
return
|
|
|
|
"""
|
|
gnupghome = os.path.join(data_dir, 'gpg')
|
|
if not os.path.exists(gnupghome):
|
|
os.makedirs(gnupghome)
|
|
"""
|
|
gpg = gnupg.GPG()
|
|
|
|
keysdirpath = extra_opts.get('keysdirpath', None)
|
|
if keysdirpath is not None:
|
|
logger.info(f'Loading PGP keys from: {keysdirpath}.')
|
|
for path in os.scandir(keysdirpath):
|
|
if path.is_file():
|
|
with open(path, 'rb') as fp:
|
|
rv = gpg.import_keys(fp.read())
|
|
for key in rv.fingerprints:
|
|
gpg.trust_keys(rv.fingerprints[0], 'TRUST_FULLY')
|
|
|
|
if coin in ('navcoin', ):
|
|
pubkey_filename = '{}_builder.pgp'.format(coin)
|
|
elif coin in ('decred', ):
|
|
pubkey_filename = '{}_release.pgp'.format(coin)
|
|
else:
|
|
pubkey_filename = '{}_{}.pgp'.format(coin, signing_key_name)
|
|
pubkeyurls = [
|
|
'https://raw.githubusercontent.com/tecnovert/basicswap/master/pgp/keys/' + pubkey_filename,
|
|
'https://gitlab.com/particl/basicswap/-/raw/master/pgp/keys/' + pubkey_filename,
|
|
]
|
|
if coin == 'dash':
|
|
pubkeyurls.append('https://raw.githubusercontent.com/dashpay/dash/master/contrib/gitian-keys/pasta.pgp')
|
|
if coin == 'monero':
|
|
pubkeyurls.append('https://raw.githubusercontent.com/monero-project/monero/master/utils/gpg_keys/binaryfate.asc')
|
|
if coin == 'firo':
|
|
pubkeyurls.append('https://firo.org/reuben.asc')
|
|
|
|
if ADD_PUBKEY_URL != '':
|
|
pubkeyurls.append(ADD_PUBKEY_URL + '/' + pubkey_filename)
|
|
|
|
if coin in ('monero', 'firo'):
|
|
with open(assert_path, 'rb') as fp:
|
|
verified = gpg.verify_file(fp)
|
|
|
|
if not isValidSignature(verified) and verified.username is None:
|
|
logger.warning('Signature made by unknown key.')
|
|
importPubkeyFromUrls(gpg, pubkeyurls)
|
|
with open(assert_path, 'rb') as fp:
|
|
verified = gpg.verify_file(fp)
|
|
elif coin in ('navcoin'):
|
|
with open(assert_sig_path, 'rb') as fp:
|
|
verified = gpg.verify_file(fp)
|
|
|
|
if not isValidSignature(verified) and verified.username is None:
|
|
logger.warning('Signature made by unknown key.')
|
|
importPubkeyFromUrls(gpg, pubkeyurls)
|
|
with open(assert_sig_path, 'rb') as fp:
|
|
verified = gpg.verify_file(fp)
|
|
|
|
# .sig file is not a detached signature, recheck release hash in decrypted data
|
|
logger.warning('Double checking Navcoin release hash.')
|
|
with open(assert_sig_path, 'rb') as fp:
|
|
decrypted = gpg.decrypt_file(fp)
|
|
assert (release_hash.hex() in str(decrypted))
|
|
else:
|
|
with open(assert_sig_path, 'rb') as fp:
|
|
verified = gpg.verify_file(fp, assert_path)
|
|
|
|
if not isValidSignature(verified) and verified.username is None:
|
|
logger.warning('Signature made by unknown key.')
|
|
importPubkeyFromUrls(gpg, pubkeyurls)
|
|
with open(assert_sig_path, 'rb') as fp:
|
|
verified = gpg.verify_file(fp, assert_path)
|
|
|
|
ensureValidSignatureBy(verified, signing_key_name)
|
|
|
|
extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts)
|
|
|
|
|
|
def writeTorSettings(fp, coin, coin_settings, tor_control_password):
|
|
'''
|
|
TOR_PROXY_HOST must be an ip address.
|
|
BTC versions >21 and Particl with lookuptorcontrolhost=any can accept hostnames, XMR and LTC cannot
|
|
'''
|
|
fp.write(f'proxy={TOR_PROXY_HOST}:{TOR_PROXY_PORT}\n')
|
|
if coin in ('decred',):
|
|
return
|
|
|
|
onionport = coin_settings['onionport']
|
|
fp.write(f'torpassword={tor_control_password}\n')
|
|
fp.write(f'torcontrol={TOR_PROXY_HOST}:{TOR_CONTROL_PORT}\n')
|
|
|
|
if coin_settings['core_version_group'] >= 21:
|
|
fp.write(f'bind=0.0.0.0:{onionport}=onion\n')
|
|
else:
|
|
fp.write(f'bind=0.0.0.0:{onionport}\n')
|
|
|
|
|
|
def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
|
|
core_settings = settings['chainclients'][coin]
|
|
bin_dir = core_settings['bindir']
|
|
data_dir = core_settings['datadir']
|
|
tor_control_password = extra_opts.get('tor_control_password', None)
|
|
|
|
if not os.path.exists(data_dir):
|
|
os.makedirs(data_dir)
|
|
|
|
if coin == 'monero':
|
|
core_conf_path = os.path.join(data_dir, coin + 'd.conf')
|
|
if os.path.exists(core_conf_path):
|
|
exitWithError('{} exists'.format(core_conf_path))
|
|
with open(core_conf_path, 'w') as fp:
|
|
if chain == 'regtest':
|
|
fp.write('regtest=1\n')
|
|
fp.write('keep-fakechain=1\n')
|
|
fp.write('fixed-difficulty=1\n')
|
|
else:
|
|
fp.write('bootstrap-daemon-address=auto\n')
|
|
fp.write('restricted-rpc=1\n')
|
|
if chain == 'testnet':
|
|
fp.write('testnet=1\n')
|
|
config_datadir = data_dir
|
|
if extra_opts.get('use_containers', False) is True:
|
|
config_datadir = '/data'
|
|
fp.write(f'data-dir={config_datadir}\n')
|
|
fp.write('rpc-bind-port={}\n'.format(core_settings['rpcport']))
|
|
fp.write('rpc-bind-ip={}\n'.format(COINS_RPCBIND_IP))
|
|
fp.write('zmq-rpc-bind-port={}\n'.format(core_settings['zmqport']))
|
|
fp.write('zmq-rpc-bind-ip={}\n'.format(COINS_RPCBIND_IP))
|
|
fp.write('prune-blockchain=1\n')
|
|
|
|
if tor_control_password is not None:
|
|
for opt_line in monerod_proxy_config:
|
|
fp.write(opt_line + '\n')
|
|
|
|
if XMR_RPC_USER != '':
|
|
fp.write(f'rpc-login={XMR_RPC_USER}:{XMR_RPC_PWD}\n')
|
|
|
|
wallets_dir = core_settings.get('walletsdir', data_dir)
|
|
if not os.path.exists(wallets_dir):
|
|
os.makedirs(wallets_dir)
|
|
|
|
wallet_conf_path = os.path.join(wallets_dir, coin + '_wallet.conf')
|
|
if os.path.exists(wallet_conf_path):
|
|
exitWithError('{} exists'.format(wallet_conf_path))
|
|
with open(wallet_conf_path, 'w') as fp:
|
|
config_datadir = os.path.join(data_dir, 'wallets')
|
|
if extra_opts.get('use_containers', False) is True:
|
|
fp.write('daemon-address={}:{}\n'.format(core_settings['rpchost'], core_settings['rpcport']))
|
|
config_datadir = '/data'
|
|
|
|
fp.write('no-dns=1\n')
|
|
fp.write('rpc-bind-port={}\n'.format(core_settings['walletrpcport']))
|
|
fp.write('rpc-bind-ip={}\n'.format(COINS_RPCBIND_IP))
|
|
fp.write(f'wallet-dir={config_datadir}\n')
|
|
fp.write('log-file={}\n'.format(os.path.join(config_datadir, 'wallet.log')))
|
|
fp.write('shared-ringdb-dir={}\n'.format(os.path.join(config_datadir, 'shared-ringdb')))
|
|
fp.write('rpc-login={}:{}\n'.format(core_settings['walletrpcuser'], core_settings['walletrpcpassword']))
|
|
|
|
if chain == 'regtest':
|
|
fp.write('allow-mismatched-daemon-version=1\n')
|
|
|
|
if tor_control_password is not None:
|
|
if not core_settings['manage_daemon']:
|
|
for opt_line in monero_wallet_rpc_proxy_config:
|
|
fp.write(opt_line + '\n')
|
|
return
|
|
|
|
if coin == 'decred':
|
|
chainname = 'simnet' if chain == 'regtest' else chain
|
|
core_conf_path = os.path.join(data_dir, 'dcrd.conf')
|
|
if os.path.exists(core_conf_path):
|
|
exitWithError('{} exists'.format(core_conf_path))
|
|
with open(core_conf_path, 'w') as fp:
|
|
if chain != 'mainnet':
|
|
fp.write(chainname + '=1\n')
|
|
fp.write('debuglevel=debug\n')
|
|
fp.write('notls=1\n')
|
|
|
|
fp.write('rpclisten={}:{}\n'.format(core_settings['rpchost'], core_settings['rpcport']))
|
|
|
|
fp.write('rpcuser={}\n'.format(core_settings['rpcuser']))
|
|
fp.write('rpcpass={}\n'.format(core_settings['rpcpassword']))
|
|
|
|
if tor_control_password is not None:
|
|
writeTorSettings(fp, coin, core_settings, tor_control_password)
|
|
|
|
wallet_conf_path = os.path.join(data_dir, 'dcrwallet.conf')
|
|
if os.path.exists(wallet_conf_path):
|
|
exitWithError('{} exists'.format(wallet_conf_path))
|
|
with open(wallet_conf_path, 'w') as fp:
|
|
if chain != 'mainnet':
|
|
fp.write(chainname + '=1\n')
|
|
fp.write('debuglevel=debug\n')
|
|
fp.write('noservertls=1\n')
|
|
fp.write('noclienttls=1\n')
|
|
|
|
fp.write('rpcconnect={}:{}\n'.format(core_settings['rpchost'], core_settings['rpcport']))
|
|
fp.write('rpclisten={}:{}\n'.format(core_settings['walletrpchost'], core_settings['walletrpcport']))
|
|
|
|
fp.write('username={}\n'.format(core_settings['rpcuser']))
|
|
fp.write('password={}\n'.format(core_settings['rpcpassword']))
|
|
|
|
return
|
|
|
|
core_conf_path = os.path.join(data_dir, coin + '.conf')
|
|
if os.path.exists(core_conf_path):
|
|
exitWithError('{} exists'.format(core_conf_path))
|
|
with open(core_conf_path, 'w') as fp:
|
|
if chain != 'mainnet':
|
|
if coin in ('navcoin',):
|
|
chainname = 'devnet' if chain == 'regtest' else chain
|
|
fp.write(chainname + '=1\n')
|
|
else:
|
|
fp.write(chain + '=1\n')
|
|
if coin not in ('firo', 'navcoin'):
|
|
if chain == 'testnet':
|
|
fp.write('[test]\n\n')
|
|
elif chain == 'regtest':
|
|
fp.write('[regtest]\n\n')
|
|
else:
|
|
logger.warning('Unknown chain %s', chain)
|
|
|
|
if COINS_RPCBIND_IP != '127.0.0.1':
|
|
fp.write('rpcallowip=127.0.0.1\n')
|
|
fp.write('rpcallowip=172.0.0.0/8\n') # Allow 172.x.x.x, range used by docker
|
|
fp.write('rpcbind={}\n'.format(COINS_RPCBIND_IP))
|
|
|
|
fp.write('rpcport={}\n'.format(core_settings['rpcport']))
|
|
fp.write('printtoconsole=0\n')
|
|
fp.write('daemon=0\n')
|
|
fp.write('wallet=wallet.dat\n')
|
|
|
|
if tor_control_password is not None:
|
|
writeTorSettings(fp, coin, core_settings, tor_control_password)
|
|
|
|
salt = generate_salt(16)
|
|
if coin == 'particl':
|
|
fp.write('debugexclude=libevent\n')
|
|
fp.write('zmqpubsmsg=tcp://{}:{}\n'.format(COINS_RPCBIND_IP, settings['zmqport']))
|
|
fp.write('spentindex=1\n')
|
|
fp.write('txindex=1\n')
|
|
fp.write('staking=0\n')
|
|
if PART_RPC_USER != '':
|
|
fp.write('rpcauth={}:{}${}\n'.format(PART_RPC_USER, salt, password_to_hmac(salt, PART_RPC_PWD)))
|
|
elif coin == 'litecoin':
|
|
fp.write('prune=4000\n')
|
|
fp.write('blockfilterindex=0\n')
|
|
fp.write('peerblockfilters=0\n')
|
|
if LTC_RPC_USER != '':
|
|
fp.write('rpcauth={}:{}${}\n'.format(LTC_RPC_USER, salt, password_to_hmac(salt, LTC_RPC_PWD)))
|
|
elif coin == 'bitcoin':
|
|
fp.write('deprecatedrpc=create_bdb\n')
|
|
fp.write('prune=2000\n')
|
|
fp.write('fallbackfee=0.0002\n')
|
|
if BTC_RPC_USER != '':
|
|
fp.write('rpcauth={}:{}${}\n'.format(BTC_RPC_USER, salt, password_to_hmac(salt, BTC_RPC_PWD)))
|
|
elif coin == 'namecoin':
|
|
fp.write('prune=2000\n')
|
|
elif coin == 'pivx':
|
|
params_dir = os.path.join(data_dir, 'pivx-params')
|
|
downloadPIVXParams(params_dir)
|
|
PIVX_PARAMSDIR = os.getenv('PIVX_PARAMSDIR', '/data/pivx-params' if extra_opts.get('use_containers', False) else params_dir)
|
|
fp.write(f'paramsdir={PIVX_PARAMSDIR}\n')
|
|
if PIVX_RPC_USER != '':
|
|
fp.write('rpcauth={}:{}${}\n'.format(PIVX_RPC_USER, salt, password_to_hmac(salt, PIVX_RPC_PWD)))
|
|
elif coin == 'dash':
|
|
fp.write('prune=4000\n')
|
|
fp.write('fallbackfee=0.0002\n')
|
|
if DASH_RPC_USER != '':
|
|
fp.write('rpcauth={}:{}${}\n'.format(DASH_RPC_USER, salt, password_to_hmac(salt, DASH_RPC_PWD)))
|
|
elif coin == 'firo':
|
|
fp.write('prune=4000\n')
|
|
fp.write('fallbackfee=0.0002\n')
|
|
fp.write('txindex=0\n')
|
|
fp.write('usehd=1\n')
|
|
if FIRO_RPC_USER != '':
|
|
fp.write('rpcauth={}:{}${}\n'.format(FIRO_RPC_USER, salt, password_to_hmac(salt, FIRO_RPC_PWD)))
|
|
elif coin == 'navcoin':
|
|
fp.write('prune=4000\n')
|
|
fp.write('fallbackfee=0.0002\n')
|
|
if NAV_RPC_USER != '':
|
|
fp.write('rpcauth={}:{}${}\n'.format(NAV_RPC_USER, salt, password_to_hmac(salt, NAV_RPC_PWD)))
|
|
else:
|
|
logger.warning('Unknown coin %s', coin)
|
|
|
|
if coin == 'bitcoin' and extra_opts.get('use_btc_fastsync', False) is True:
|
|
logger.info('Initialising BTC chain with fastsync %s', BITCOIN_FASTSYNC_FILE)
|
|
base_dir = extra_opts['data_dir']
|
|
|
|
for dirname in ('blocks', 'chainstate'):
|
|
if os.path.exists(os.path.join(data_dir, dirname)):
|
|
raise ValueError(f'{dirname} directory already exists, not overwriting.')
|
|
|
|
sync_file_path = os.path.join(base_dir, BITCOIN_FASTSYNC_FILE)
|
|
if not os.path.exists(sync_file_path):
|
|
raise ValueError(f'BTC fastsync file not found: {sync_file_path}')
|
|
|
|
# Double check
|
|
if extra_opts.get('check_btc_fastsync', True):
|
|
check_btc_fastsync_data(base_dir, sync_file_path)
|
|
|
|
with tarfile.open(sync_file_path) as ft:
|
|
ft.extractall(path=data_dir)
|
|
|
|
|
|
def write_torrc(data_dir, tor_control_password):
|
|
tor_dir = os.path.join(data_dir, 'tor')
|
|
if not os.path.exists(tor_dir):
|
|
os.makedirs(tor_dir)
|
|
torrc_path = os.path.join(tor_dir, 'torrc')
|
|
|
|
tor_control_hash = rfc2440_hash_password(tor_control_password)
|
|
with open(torrc_path, 'w') as fp:
|
|
fp.write(f'SocksPort 0.0.0.0:{TOR_PROXY_PORT}\n')
|
|
fp.write(f'ControlPort 0.0.0.0:{TOR_CONTROL_PORT}\n')
|
|
fp.write(f'DNSPort 0.0.0.0:{TOR_DNS_PORT}\n')
|
|
fp.write(f'HashedControlPassword {tor_control_hash}\n')
|
|
|
|
|
|
def addTorSettings(settings, 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
|
|
|
|
|
|
def modify_tor_config(settings, coin, tor_control_password=None, enable=False, extra_opts={}):
|
|
coin_settings = settings['chainclients'][coin]
|
|
data_dir = coin_settings['datadir']
|
|
|
|
if coin == 'monero':
|
|
core_conf_path = os.path.join(data_dir, coin + 'd.conf')
|
|
if not os.path.exists(core_conf_path):
|
|
exitWithError('{} does not exist'.format(core_conf_path))
|
|
wallets_dir = coin_settings.get('walletsdir', data_dir)
|
|
wallet_conf_path = os.path.join(wallets_dir, coin + '_wallet.conf')
|
|
if not os.path.exists(wallet_conf_path):
|
|
exitWithError('{} does not exist'.format(wallet_conf_path))
|
|
|
|
# Backup
|
|
shutil.copyfile(core_conf_path, core_conf_path + '.last')
|
|
shutil.copyfile(wallet_conf_path, wallet_conf_path + '.last')
|
|
|
|
with open(core_conf_path, 'w') as fp:
|
|
with open(core_conf_path + '.last') as fp_in:
|
|
# Disable tor first
|
|
for line in fp_in:
|
|
skip_line: bool = False
|
|
for opt_line in monerod_proxy_config:
|
|
setting: str = opt_line[0: opt_line.find('=') + 1]
|
|
if line.startswith(setting):
|
|
skip_line = True
|
|
break
|
|
if not skip_line:
|
|
fp.write(line)
|
|
if enable:
|
|
for opt_line in monerod_proxy_config:
|
|
fp.write(opt_line + '\n')
|
|
|
|
with open(wallet_conf_path, 'w') as fp:
|
|
with open(wallet_conf_path + '.last') as fp_in:
|
|
# Disable tor first
|
|
for line in fp_in:
|
|
skip_line = False
|
|
for opt_line in monero_wallet_rpc_proxy_config + ['proxy=',]:
|
|
setting: str = opt_line[0: opt_line.find('=') + 1]
|
|
if line.startswith(setting):
|
|
skip_line = True
|
|
break
|
|
if not skip_line:
|
|
fp.write(line)
|
|
if enable:
|
|
if not coin_settings['manage_daemon']:
|
|
for opt_line in monero_wallet_rpc_proxy_config:
|
|
fp.write(opt_line + '\n')
|
|
|
|
coin_settings['trusted_daemon'] = extra_opts.get('trust_remote_node', 'auto')
|
|
return
|
|
|
|
if coin == 'decred':
|
|
config_path = os.path.join(data_dir, 'dcrd.conf')
|
|
else:
|
|
config_path = os.path.join(data_dir, coin + '.conf')
|
|
|
|
if not os.path.exists(config_path):
|
|
exitWithError('{} does not exist'.format(config_path))
|
|
|
|
if 'onionport' not in coin_settings:
|
|
default_onionport = 0
|
|
if coin == 'bitcoin':
|
|
default_onionport = BTC_ONION_PORT
|
|
elif coin == 'particl':
|
|
default_onionport = PART_ONION_PORT
|
|
elif coin == 'litecoin':
|
|
default_onionport = LTC_ONION_PORT
|
|
elif coin in ('decred',):
|
|
pass
|
|
else:
|
|
exitWithError('Unknown default onion listening port for {}'.format(coin))
|
|
if default_onionport > 0:
|
|
coin_settings['onionport'] = default_onionport
|
|
|
|
# Backup
|
|
shutil.copyfile(config_path, config_path + '.last')
|
|
|
|
tor_settings = ('proxy=', 'torpassword=', 'torcontrol=', 'bind=')
|
|
with open(config_path, 'w') as fp:
|
|
with open(config_path + '.last') as fp_in:
|
|
# Disable tor first
|
|
for line in fp_in:
|
|
skip_line = False
|
|
for setting in tor_settings:
|
|
if line.startswith(setting):
|
|
skip_line = True
|
|
break
|
|
if not skip_line:
|
|
fp.write(line)
|
|
if enable:
|
|
writeTorSettings(fp, coin, coin_settings, tor_control_password)
|
|
|
|
|
|
def printVersion():
|
|
logger.info(f'Basicswap version: {__version__}')
|
|
|
|
logger.info('Core versions:')
|
|
for coin, version in known_coins.items():
|
|
postfix = ' (Disabled)' if coin in disabled_coins else ''
|
|
logger.info('\t%s: %s%s%s', coin.capitalize(), version[0], version[1], postfix)
|
|
|
|
|
|
def printHelp():
|
|
print('Usage: basicswap-prepare ')
|
|
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('--bindir=PATH Path to cores directory, default:datadir/bin.')
|
|
print('--mainnet Run in mainnet mode.')
|
|
print('--testnet Run in testnet mode.')
|
|
print('--regtest Run in regtest mode.')
|
|
print('--particl_mnemonic= Recovery phrase to use for the Particl wallet, default is randomly generated,\n'
|
|
+ ' "auto" to create a wallet automatically - No mnemonic.'
|
|
+ ' "none" to disable wallet initialisation.')
|
|
print('--withcoin= Prepare system to run daemon for coin.')
|
|
print('--withoutcoin= Do not prepare system to run daemon for coin.')
|
|
print('--addcoin= Add coin to existing setup.')
|
|
print('--disablecoin= Make coin inactive.')
|
|
print('--preparebinonly Don\'t prepare settings or datadirs.')
|
|
print('--nocores Don\'t download and extract any coin clients.')
|
|
print('--usecontainers Expect each core to run in a unique container.')
|
|
print('--portoffset=n Raise all ports by n.')
|
|
print('--htmlhost= Interface to host html server on, default:127.0.0.1.')
|
|
print('--wshost= Interface to host websocket server on, disable by setting to "none", default\'s to --htmlhost.')
|
|
print('--xmrrestoreheight=n Block height to restore Monero wallet from, default:{}.'.format(DEFAULT_XMR_RESTORE_HEIGHT))
|
|
print('--trustremotenode Set trusted-daemon for XMR, defaults to auto: true when daemon rpchost value is a private ip address else false')
|
|
print('--noextractover Prevent extracting cores if files exist. Speeds up tests')
|
|
print('--usetorproxy Use TOR proxy during setup. Note that some download links may be inaccessible over TOR.')
|
|
print('--notorproxy Force usetorproxy off, usetorproxy is automatically set when tor is enabled')
|
|
print('--enabletor Setup Basicswap instance to use TOR.')
|
|
print('--disabletor Setup Basicswap instance to not use TOR.')
|
|
print('--usebtcfastsync Initialise the BTC chain with a snapshot from btcpayserver FastSync.\n'
|
|
+ ' See https://github.com/btcpayserver/btcpayserver-docker/blob/master/contrib/FastSync/README.md')
|
|
print('--skipbtcfastsyncchecks Use the provided btcfastsync file without checking it\'s size or signature.')
|
|
print('--initwalletsonly Setup coin wallets only.')
|
|
print('--keysdirpath Speed up tests by preloading all PGP keys in directory.')
|
|
|
|
active_coins = []
|
|
for coin_name in known_coins.keys():
|
|
if coin_name not in disabled_coins:
|
|
active_coins.append(coin_name)
|
|
print('\n' + 'Known coins: {}'.format(', '.join(active_coins)))
|
|
|
|
|
|
def finalise_daemon(d):
|
|
logging.info('Interrupting {}'.format(d.handle.pid))
|
|
try:
|
|
d.handle.send_signal(signal.CTRL_C_EVENT if os.name == 'nt' else signal.SIGINT)
|
|
d.handle.wait(timeout=120)
|
|
except Exception as e:
|
|
logging.info(f'Error {e} for process {d.handle.pid}')
|
|
for fp in [d.handle.stdout, d.handle.stderr, d.handle.stdin] + d.files:
|
|
if fp:
|
|
fp.close()
|
|
|
|
|
|
def test_particl_encryption(data_dir, settings, chain, use_tor_proxy):
|
|
swap_client = None
|
|
daemons = []
|
|
daemon_args = ['-noconnect', '-nodnsseed', '-nofindpeers', '-nostaking']
|
|
with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp:
|
|
try:
|
|
swap_client = BasicSwap(fp, data_dir, settings, chain, transient_instance=True)
|
|
if not swap_client.use_tor_proxy:
|
|
# Cannot set -bind or -whitebind together with -listen=0
|
|
daemon_args.append('-nolisten')
|
|
c = Coins.PART
|
|
coin_name = 'particl'
|
|
coin_settings = settings['chainclients'][coin_name]
|
|
if coin_settings['manage_daemon']:
|
|
filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '')
|
|
daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args))
|
|
swap_client.setDaemonPID(c, daemons[-1].handle.pid)
|
|
swap_client.setCoinRunParams(c)
|
|
swap_client.createCoinInterface(c)
|
|
swap_client.waitForDaemonRPC(c, with_wallet=True)
|
|
|
|
if swap_client.ci(c).isWalletEncrypted():
|
|
logger.info('Particl Wallet is encrypted')
|
|
if WALLET_ENCRYPTION_PWD == '':
|
|
raise ValueError('Must set WALLET_ENCRYPTION_PWD to add coin when Particl wallet is encrypted')
|
|
swap_client.ci(c).unlockWallet(WALLET_ENCRYPTION_PWD)
|
|
finally:
|
|
if swap_client:
|
|
swap_client.finalise()
|
|
del swap_client
|
|
for d in daemons:
|
|
finalise_daemon(d)
|
|
|
|
|
|
def encrypt_wallet(swap_client, coin_type) -> None:
|
|
ci = swap_client.ci(coin_type)
|
|
ci.changeWalletPassword('', WALLET_ENCRYPTION_PWD)
|
|
ci.unlockWallet(WALLET_ENCRYPTION_PWD)
|
|
|
|
|
|
def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, chain, use_tor_proxy):
|
|
swap_client = None
|
|
daemons = []
|
|
daemon_args = ['-noconnect', '-nodnsseed']
|
|
generated_mnemonic: bool = False
|
|
|
|
coins_failed_to_initialise = []
|
|
|
|
with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp:
|
|
try:
|
|
swap_client = BasicSwap(fp, data_dir, settings, chain, transient_instance=True)
|
|
if not swap_client.use_tor_proxy:
|
|
# Cannot set -bind or -whitebind together with -listen=0
|
|
daemon_args.append('-nolisten')
|
|
coins_to_create_wallets_for = (Coins.PART, Coins.BTC, Coins.LTC, Coins.DASH)
|
|
# Always start Particl, it must be running to initialise a wallet in addcoin mode
|
|
# Particl must be loaded first as subsequent coins are initialised from the Particl mnemonic
|
|
start_daemons = ['particl', ] + [c for c in with_coins if c != 'particl']
|
|
for coin_name in start_daemons:
|
|
coin_settings = settings['chainclients'][coin_name]
|
|
c = swap_client.getCoinIdFromName(coin_name)
|
|
|
|
if c == Coins.XMR:
|
|
if coin_settings['manage_wallet_daemon']:
|
|
filename = 'monero-wallet-rpc' + ('.exe' if os.name == 'nt' else '')
|
|
daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], filename))
|
|
elif c == Coins.DCR:
|
|
pass
|
|
else:
|
|
if coin_settings['manage_daemon']:
|
|
filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '')
|
|
coin_args = ['-nofindpeers', '-nostaking'] if c == Coins.PART else []
|
|
|
|
if c == Coins.FIRO:
|
|
coin_args += ['-hdseed={}'.format(swap_client.getWalletKey(Coins.FIRO, 1).hex())]
|
|
|
|
daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args))
|
|
swap_client.setDaemonPID(c, daemons[-1].handle.pid)
|
|
swap_client.setCoinRunParams(c)
|
|
swap_client.createCoinInterface(c)
|
|
|
|
if c == Coins.DCR:
|
|
if coin_settings['manage_wallet_daemon']:
|
|
from basicswap.interface.dcr.util import createDCRWallet
|
|
|
|
dcr_password = coin_settings['wallet_pwd'] if WALLET_ENCRYPTION_PWD == '' else WALLET_ENCRYPTION_PWD
|
|
extra_opts = ['--appdata="{}"'.format(coin_settings['datadir']),
|
|
'--pass={}'.format(dcr_password),
|
|
]
|
|
|
|
filename = 'dcrwallet' + ('.exe' if os.name == 'nt' else '')
|
|
args = [os.path.join(coin_settings['bindir'], filename), '--create'] + extra_opts
|
|
hex_seed = swap_client.getWalletKey(Coins.DCR, 1).hex()
|
|
createDCRWallet(args, hex_seed, logger, threading.Event())
|
|
if c in coins_to_create_wallets_for:
|
|
swap_client.waitForDaemonRPC(c, with_wallet=False)
|
|
# Create wallet if it doesn't exist yet
|
|
wallets = swap_client.callcoinrpc(c, 'listwallets')
|
|
if len(wallets) < 1:
|
|
logger.info('Creating wallet.dat for {}.'.format(getCoinName(c)))
|
|
|
|
if c in (Coins.BTC, Coins.LTC):
|
|
# wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors
|
|
swap_client.callcoinrpc(c, 'createwallet', ['wallet.dat', False, True, WALLET_ENCRYPTION_PWD, False, False])
|
|
swap_client.ci(c).unlockWallet(WALLET_ENCRYPTION_PWD)
|
|
else:
|
|
swap_client.callcoinrpc(c, 'createwallet', ['wallet.dat'])
|
|
if WALLET_ENCRYPTION_PWD != '':
|
|
encrypt_wallet(swap_client, c)
|
|
|
|
if c == Coins.LTC:
|
|
password = WALLET_ENCRYPTION_PWD if WALLET_ENCRYPTION_PWD != '' else None
|
|
swap_client.ci(Coins.LTC_MWEB).init_wallet(password)
|
|
|
|
if c == Coins.PART:
|
|
if 'particl' in with_coins:
|
|
logger.info('Loading Particl mnemonic')
|
|
if particl_wallet_mnemonic is None:
|
|
particl_wallet_mnemonic = swap_client.callcoinrpc(Coins.PART, 'mnemonic', ['new'])['mnemonic']
|
|
generated_mnemonic = True
|
|
swap_client.callcoinrpc(Coins.PART, 'extkeyimportmaster', [particl_wallet_mnemonic])
|
|
# Particl wallet must be unlocked to call getWalletKey
|
|
if WALLET_ENCRYPTION_PWD != '':
|
|
swap_client.ci(c).unlockWallet(WALLET_ENCRYPTION_PWD)
|
|
|
|
for coin_name in with_coins:
|
|
c = swap_client.getCoinIdFromName(coin_name)
|
|
if c in (Coins.PART, ):
|
|
continue
|
|
if c not in (Coins.DCR, ):
|
|
# initialiseWallet only sets main_wallet_seedid_
|
|
swap_client.waitForDaemonRPC(c)
|
|
try:
|
|
swap_client.initialiseWallet(c, raise_errors=True)
|
|
except Exception as e:
|
|
coins_failed_to_initialise.append((c, e))
|
|
if WALLET_ENCRYPTION_PWD != '' and c not in coins_to_create_wallets_for:
|
|
try:
|
|
swap_client.ci(c).changeWalletPassword('', WALLET_ENCRYPTION_PWD)
|
|
except Exception as e:
|
|
logger.warning(f'changeWalletPassword failed for {coin_name}.')
|
|
|
|
finally:
|
|
if swap_client:
|
|
swap_client.finalise()
|
|
del swap_client
|
|
for d in daemons:
|
|
finalise_daemon(d)
|
|
|
|
print('')
|
|
for pair in coins_failed_to_initialise:
|
|
c, e = pair
|
|
if c in (Coins.PIVX, ):
|
|
print(f'NOTE - Unable to initialise wallet for {getCoinName(c)}. To complete setup click \'Reseed Wallet\' from the ui page once chain is synced.')
|
|
else:
|
|
print(f'WARNING - Failed to initialise wallet for {getCoinName(c)}: {e}')
|
|
|
|
if 'decred' in with_coins and WALLET_ENCRYPTION_PWD != '':
|
|
print('WARNING - dcrwallet requires the password to be entered at the first startup when encrypted.\nPlease use basicswap-run with --startonlycoin=decred and the WALLET_ENCRYPTION_PWD environment var set for the initial sync.')
|
|
|
|
if particl_wallet_mnemonic is not None:
|
|
if generated_mnemonic:
|
|
# Print directly to stdout for tests
|
|
print('IMPORTANT - Save your particl wallet recovery phrase:\n{}\n'.format(particl_wallet_mnemonic))
|
|
|
|
|
|
def load_config(config_path):
|
|
if not os.path.exists(config_path):
|
|
exitWithError('{} does not exist'.format(config_path))
|
|
with open(config_path) as fs:
|
|
return json.load(fs)
|
|
|
|
|
|
def signal_handler(sig, frame):
|
|
logger.info('Signal %d detected' % (sig))
|
|
|
|
|
|
def check_btc_fastsync_data(base_dir, sync_file_path):
|
|
github_pgp_url = 'https://raw.githubusercontent.com/tecnovert/basicswap/master/pgp'
|
|
gitlab_pgp_url = 'https://gitlab.com/particl/basicswap/-/raw/master/pgp'
|
|
asc_filename = BITCOIN_FASTSYNC_FILE + '.asc'
|
|
asc_file_path = os.path.join(base_dir, asc_filename)
|
|
if not os.path.exists(asc_file_path):
|
|
asc_file_urls = (
|
|
github_pgp_url + '/sigs/' + asc_filename,
|
|
gitlab_pgp_url + '/sigs/' + asc_filename,
|
|
)
|
|
for url in asc_file_urls:
|
|
try:
|
|
downloadFile(url, asc_file_path)
|
|
break
|
|
except Exception as e:
|
|
logging.warning('Download failed: %s', str(e))
|
|
gpg = gnupg.GPG()
|
|
pubkey_filename = '{}_{}.pgp'.format('particl', 'tecnovert')
|
|
pubkeyurls = [
|
|
github_pgp_url + '/keys/' + pubkey_filename,
|
|
gitlab_pgp_url + '/keys/' + pubkey_filename,
|
|
]
|
|
if not havePubkey(gpg, expected_key_ids['tecnovert'][0]):
|
|
importPubkeyFromUrls(gpg, pubkeyurls)
|
|
with open(asc_file_path, 'rb') as fp:
|
|
verified = gpg.verify_file(fp, sync_file_path)
|
|
|
|
ensureValidSignatureBy(verified, 'tecnovert')
|
|
|
|
|
|
def ensure_coin_valid(coin: str, test_disabled: bool = True) -> None:
|
|
if coin not in known_coins:
|
|
exitWithError(f'Unknown coin {coin.capitalize()}')
|
|
if test_disabled and not OVERRIDE_DISABLED_COINS and coin in disabled_coins:
|
|
exitWithError(f'{coin.capitalize()} is disabled')
|
|
|
|
|
|
def main():
|
|
global use_tor_proxy
|
|
data_dir = None
|
|
bin_dir = None
|
|
port_offset = None
|
|
chain = 'mainnet'
|
|
particl_wallet_mnemonic = None
|
|
with_coins = {'particl', }
|
|
add_coin = ''
|
|
disable_coin = ''
|
|
coins_changed = False
|
|
htmlhost = '127.0.0.1'
|
|
xmr_restore_height = DEFAULT_XMR_RESTORE_HEIGHT
|
|
prepare_bin_only = False
|
|
no_cores = False
|
|
enable_tor = False
|
|
disable_tor = False
|
|
initwalletsonly = False
|
|
tor_control_password = None
|
|
extra_opts = {}
|
|
|
|
if os.getenv('SSL_CERT_DIR', '') == '' and GUIX_SSL_CERT_DIR is not None:
|
|
os.environ['SSL_CERT_DIR'] = GUIX_SSL_CERT_DIR
|
|
|
|
if os.name == 'nt':
|
|
# On windows sending signal.CTRL_C_EVENT to a subprocess causes it to be sent to the parent process too
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
|
|
for v in sys.argv[1:]:
|
|
if len(v) < 2 or v[0] != '-':
|
|
exitWithError('Unknown argument {}'.format(v))
|
|
|
|
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 name == 'preparebinonly':
|
|
prepare_bin_only = True
|
|
continue
|
|
if name == 'nocores':
|
|
no_cores = True
|
|
continue
|
|
if name == 'usecontainers':
|
|
extra_opts['use_containers'] = True
|
|
continue
|
|
if name == 'noextractover':
|
|
extra_opts['extract_core_overwrite'] = False
|
|
continue
|
|
if name == 'usetorproxy':
|
|
use_tor_proxy = True
|
|
continue
|
|
if name == 'notorproxy':
|
|
extra_opts['no_tor_proxy'] = True
|
|
continue
|
|
if name == 'enabletor':
|
|
enable_tor = True
|
|
continue
|
|
if name == 'disabletor':
|
|
disable_tor = True
|
|
continue
|
|
if name == 'usebtcfastsync':
|
|
extra_opts['use_btc_fastsync'] = True
|
|
continue
|
|
if name == 'skipbtcfastsyncchecks':
|
|
extra_opts['check_btc_fastsync'] = False
|
|
continue
|
|
if name == 'trustremotenode':
|
|
extra_opts['trust_remote_node'] = True
|
|
continue
|
|
if name == 'initwalletsonly':
|
|
initwalletsonly = True
|
|
continue
|
|
if len(s) == 2:
|
|
if name == 'datadir':
|
|
data_dir = os.path.expanduser(s[1].strip('"'))
|
|
continue
|
|
if name == 'bindir':
|
|
bin_dir = os.path.expanduser(s[1].strip('"'))
|
|
continue
|
|
if name == 'portoffset':
|
|
port_offset = int(s[1])
|
|
continue
|
|
if name == 'particl_mnemonic':
|
|
particl_wallet_mnemonic = s[1].strip('"')
|
|
continue
|
|
if name in ('withcoin', 'withcoins'):
|
|
for coin in [s.lower() for s in s[1].split(',')]:
|
|
ensure_coin_valid(coin)
|
|
with_coins.add(coin)
|
|
coins_changed = True
|
|
continue
|
|
if name in ('withoutcoin', 'withoutcoins'):
|
|
for coin in [s.lower() for s in s[1].split(',')]:
|
|
ensure_coin_valid(coin, test_disabled=False)
|
|
with_coins.discard(coin)
|
|
coins_changed = True
|
|
continue
|
|
if name == 'addcoin':
|
|
add_coin = s[1].lower()
|
|
ensure_coin_valid(add_coin)
|
|
with_coins = {add_coin, }
|
|
continue
|
|
if name == 'disablecoin':
|
|
disable_coin = s[1].lower()
|
|
ensure_coin_valid(disable_coin, test_disabled=False)
|
|
continue
|
|
if name == 'htmlhost':
|
|
htmlhost = s[1].strip('"')
|
|
continue
|
|
if name == 'wshost':
|
|
extra_opts['wshost'] = s[1].strip('"')
|
|
continue
|
|
if name == 'xmrrestoreheight':
|
|
xmr_restore_height = int(s[1])
|
|
continue
|
|
if name == 'keysdirpath':
|
|
extra_opts['keysdirpath'] = os.path.expanduser(s[1].strip('"'))
|
|
continue
|
|
if name == 'trustremotenode':
|
|
extra_opts['trust_remote_node'] = toBool(s[1])
|
|
continue
|
|
|
|
exitWithError('Unknown argument {}'.format(v))
|
|
|
|
if data_dir is None:
|
|
data_dir = os.path.join(os.path.expanduser(cfg.BASICSWAP_DATADIR))
|
|
if bin_dir is None:
|
|
bin_dir = os.path.join(data_dir, 'bin')
|
|
|
|
logger.info(f'BasicSwap prepare script {__version__}\n')
|
|
logger.info(f'Python version: {platform.python_version()}')
|
|
logger.info(f'Data dir: {data_dir}')
|
|
logger.info(f'Bin dir: {bin_dir}')
|
|
logger.info(f'Chain: {chain}')
|
|
logger.info('WALLET_ENCRYPTION_PWD is {}set'.format('not ' if WALLET_ENCRYPTION_PWD == '' else ''))
|
|
|
|
if port_offset is None:
|
|
port_offset = 300 if chain == 'testnet' else 0
|
|
|
|
if not os.path.exists(data_dir):
|
|
os.makedirs(data_dir)
|
|
config_path = os.path.join(data_dir, cfg.CONFIG_FILENAME)
|
|
|
|
if use_tor_proxy and extra_opts.get('no_tor_proxy', False):
|
|
exitWithError('Can\'t use --usetorproxy and --notorproxy together')
|
|
|
|
# Automatically enable usetorproxy for certain commands if it's set in basicswap config
|
|
if not (initwalletsonly or enable_tor or disable_tor or disable_coin) and \
|
|
not use_tor_proxy and os.path.exists(config_path):
|
|
settings = load_config(config_path)
|
|
settings_use_tor = settings.get('use_tor', False)
|
|
if settings_use_tor:
|
|
logger.info('use_tor is set in the config')
|
|
if extra_opts.get('no_tor_proxy', False):
|
|
use_tor_proxy = False
|
|
logger.warning('Not automatically setting --usetorproxy as --notorproxy is set')
|
|
else:
|
|
use_tor_proxy = True
|
|
logger.info('Automatically setting --usetorproxy')
|
|
|
|
setConnectionParameters(allow_set_tor=False)
|
|
|
|
if use_tor_proxy and TEST_TOR_PROXY:
|
|
testTorConnection()
|
|
|
|
if use_tor_proxy and TEST_ONION_LINK:
|
|
testOnionLink()
|
|
|
|
should_download_btc_fastsync = False
|
|
if extra_opts.get('use_btc_fastsync', False) is True:
|
|
if 'bitcoin' in with_coins or add_coin == 'bitcoin':
|
|
should_download_btc_fastsync = True
|
|
else:
|
|
logger.warning('Ignoring usebtcfastsync option without Bitcoin selected.')
|
|
|
|
if should_download_btc_fastsync:
|
|
logger.info(f'Preparing BTC Fastsync file {BITCOIN_FASTSYNC_FILE}')
|
|
sync_file_path = os.path.join(data_dir, BITCOIN_FASTSYNC_FILE)
|
|
sync_file_url = os.path.join(BITCOIN_FASTSYNC_URL, BITCOIN_FASTSYNC_FILE)
|
|
try:
|
|
check_btc_fastsync = extra_opts.get('check_btc_fastsync', True)
|
|
check_sig = False
|
|
if not os.path.exists(sync_file_path):
|
|
downloadFile(sync_file_url, sync_file_path, timeout=50)
|
|
check_sig = check_btc_fastsync
|
|
elif check_btc_fastsync:
|
|
file_size = os.stat(sync_file_path).st_size
|
|
remote_file = urlopen(sync_file_url)
|
|
if file_size < remote_file.length:
|
|
logger.warning(f'{BITCOIN_FASTSYNC_FILE} is an unexpected size, {file_size} < {remote_file.length}')
|
|
downloadFile(sync_file_url, sync_file_path, timeout=50, resume_from=file_size)
|
|
check_sig = True
|
|
|
|
if check_sig:
|
|
check_btc_fastsync_data(data_dir, sync_file_path)
|
|
except Exception as e:
|
|
logger.error(f'Failed to download BTC fastsync file: {e}\nRe-running the command should resume the download or try manually downloading from {sync_file_url}')
|
|
return 1
|
|
|
|
withchainclients = {}
|
|
chainclients = {
|
|
'particl': {
|
|
'connection_type': 'rpc',
|
|
'manage_daemon': True if ('particl' in with_coins and PART_RPC_HOST == '127.0.0.1') else False,
|
|
'rpchost': PART_RPC_HOST,
|
|
'rpcport': PART_RPC_PORT + port_offset,
|
|
'onionport': PART_ONION_PORT + port_offset,
|
|
'datadir': os.getenv('PART_DATA_DIR', os.path.join(data_dir, 'particl')),
|
|
'bindir': os.path.join(bin_dir, 'particl'),
|
|
'blocks_confirmed': 2,
|
|
'override_feerate': 0.002,
|
|
'conf_target': 2,
|
|
'core_version_group': 21,
|
|
},
|
|
'bitcoin': {
|
|
'connection_type': 'rpc' if 'bitcoin' in with_coins else 'none',
|
|
'manage_daemon': True if ('bitcoin' in with_coins and BTC_RPC_HOST == '127.0.0.1') else False,
|
|
'rpchost': BTC_RPC_HOST,
|
|
'rpcport': BTC_RPC_PORT + port_offset,
|
|
'onionport': BTC_ONION_PORT + port_offset,
|
|
'datadir': os.getenv('BTC_DATA_DIR', os.path.join(data_dir, 'bitcoin')),
|
|
'bindir': os.path.join(bin_dir, 'bitcoin'),
|
|
'use_segwit': True,
|
|
'blocks_confirmed': 1,
|
|
'conf_target': 2,
|
|
'core_version_group': 22,
|
|
},
|
|
'litecoin': {
|
|
'connection_type': 'rpc' if 'litecoin' in with_coins else 'none',
|
|
'manage_daemon': True if ('litecoin' in with_coins and LTC_RPC_HOST == '127.0.0.1') else False,
|
|
'rpchost': LTC_RPC_HOST,
|
|
'rpcport': LTC_RPC_PORT + port_offset,
|
|
'onionport': LTC_ONION_PORT + port_offset,
|
|
'datadir': os.getenv('LTC_DATA_DIR', os.path.join(data_dir, 'litecoin')),
|
|
'bindir': os.path.join(bin_dir, 'litecoin'),
|
|
'use_segwit': True,
|
|
'blocks_confirmed': 2,
|
|
'conf_target': 2,
|
|
'core_version_group': 21,
|
|
'min_relay_fee': 0.00001,
|
|
},
|
|
'decred': {
|
|
'connection_type': 'rpc' if 'decred' in with_coins else 'none',
|
|
'manage_daemon': True if ('decred' in with_coins and DCR_RPC_HOST == '127.0.0.1') else False,
|
|
'manage_wallet_daemon': True if ('decred' in with_coins and DCR_WALLET_RPC_HOST == '127.0.0.1') else False,
|
|
'wallet_pwd': DCR_WALLET_PWD if WALLET_ENCRYPTION_PWD == '' else '',
|
|
'rpchost': DCR_RPC_HOST,
|
|
'rpcport': DCR_RPC_PORT + port_offset,
|
|
'walletrpchost': DCR_WALLET_RPC_HOST,
|
|
'walletrpcport': DCR_WALLET_RPC_PORT + port_offset,
|
|
'rpcuser': DCR_RPC_USER,
|
|
'rpcpassword': DCR_RPC_PWD,
|
|
'datadir': os.getenv('DCR_DATA_DIR', os.path.join(data_dir, 'decred')),
|
|
'bindir': os.path.join(bin_dir, 'decred'),
|
|
'use_csv': True,
|
|
'use_segwit': True,
|
|
'blocks_confirmed': 2,
|
|
'conf_target': 2,
|
|
'core_type_group': 'dcr',
|
|
'min_relay_fee': 0.00001,
|
|
},
|
|
'namecoin': {
|
|
'connection_type': 'rpc' if 'namecoin' in with_coins else 'none',
|
|
'manage_daemon': True if ('namecoin' in with_coins and NMC_RPC_HOST == '127.0.0.1') else False,
|
|
'rpchost': NMC_RPC_HOST,
|
|
'rpcport': NMC_RPC_PORT + port_offset,
|
|
'datadir': os.getenv('NMC_DATA_DIR', os.path.join(data_dir, 'namecoin')),
|
|
'bindir': os.path.join(bin_dir, 'namecoin'),
|
|
'use_segwit': False,
|
|
'use_csv': False,
|
|
'blocks_confirmed': 1,
|
|
'conf_target': 2,
|
|
'core_version_group': 18,
|
|
'chain_lookups': 'local',
|
|
},
|
|
'monero': {
|
|
'connection_type': 'rpc' if 'monero' in with_coins else 'none',
|
|
'manage_daemon': True if ('monero' in with_coins and XMR_RPC_HOST == '127.0.0.1') else False,
|
|
'manage_wallet_daemon': True if ('monero' in with_coins and XMR_WALLET_RPC_HOST == '127.0.0.1') else False,
|
|
'rpcport': BASE_XMR_RPC_PORT + port_offset,
|
|
'zmqport': BASE_XMR_ZMQ_PORT + port_offset,
|
|
'walletrpcport': BASE_XMR_WALLET_PORT + port_offset,
|
|
'rpchost': XMR_RPC_HOST,
|
|
'trusted_daemon': extra_opts.get('trust_remote_node', 'auto'),
|
|
'walletrpchost': XMR_WALLET_RPC_HOST,
|
|
'walletrpcuser': XMR_WALLET_RPC_USER,
|
|
'walletrpcpassword': XMR_WALLET_RPC_PWD,
|
|
'walletfile': 'swap_wallet',
|
|
'datadir': os.getenv('XMR_DATA_DIR', os.path.join(data_dir, 'monero')),
|
|
'bindir': os.path.join(bin_dir, 'monero'),
|
|
'restore_height': xmr_restore_height,
|
|
'blocks_confirmed': 3,
|
|
'rpctimeout': 60,
|
|
'walletrpctimeout': 120,
|
|
'walletrpctimeoutlong': 600,
|
|
'core_type_group': 'xmr',
|
|
},
|
|
'pivx': {
|
|
'connection_type': 'rpc' if 'pivx' in with_coins else 'none',
|
|
'manage_daemon': True if ('pivx' in with_coins and PIVX_RPC_HOST == '127.0.0.1') else False,
|
|
'rpchost': PIVX_RPC_HOST,
|
|
'rpcport': PIVX_RPC_PORT + port_offset,
|
|
'onionport': PIVX_ONION_PORT + port_offset,
|
|
'datadir': os.getenv('PIVX_DATA_DIR', os.path.join(data_dir, 'pivx')),
|
|
'bindir': os.path.join(bin_dir, 'pivx'),
|
|
'use_segwit': False,
|
|
'use_csv': False,
|
|
'blocks_confirmed': 1,
|
|
'conf_target': 2,
|
|
'core_version_group': 17,
|
|
},
|
|
'dash': {
|
|
'connection_type': 'rpc' if 'dash' in with_coins else 'none',
|
|
'manage_daemon': True if ('dash' in with_coins and DASH_RPC_HOST == '127.0.0.1') else False,
|
|
'rpchost': DASH_RPC_HOST,
|
|
'rpcport': DASH_RPC_PORT + port_offset,
|
|
'onionport': DASH_ONION_PORT + port_offset,
|
|
'datadir': os.getenv('DASH_DATA_DIR', os.path.join(data_dir, 'dash')),
|
|
'bindir': os.path.join(bin_dir, 'dash'),
|
|
'use_segwit': False,
|
|
'use_csv': True,
|
|
'blocks_confirmed': 1,
|
|
'conf_target': 2,
|
|
'core_version_group': 18,
|
|
},
|
|
'firo': {
|
|
'connection_type': 'rpc' if 'firo' in with_coins else 'none',
|
|
'manage_daemon': True if ('firo' in with_coins and FIRO_RPC_HOST == '127.0.0.1') else False,
|
|
'rpchost': FIRO_RPC_HOST,
|
|
'rpcport': FIRO_RPC_PORT + port_offset,
|
|
'onionport': FIRO_ONION_PORT + port_offset,
|
|
'datadir': os.getenv('FIRO_DATA_DIR', os.path.join(data_dir, 'firo')),
|
|
'bindir': os.path.join(bin_dir, 'firo'),
|
|
'use_segwit': False,
|
|
'use_csv': False,
|
|
'blocks_confirmed': 1,
|
|
'conf_target': 2,
|
|
'core_version_group': 14,
|
|
'min_relay_fee': 0.00001,
|
|
},
|
|
'navcoin': {
|
|
'connection_type': 'rpc' if 'navcoin' in with_coins else 'none',
|
|
'manage_daemon': True if ('navcoin' in with_coins and NAV_RPC_HOST == '127.0.0.1') else False,
|
|
'rpchost': NAV_RPC_HOST,
|
|
'rpcport': NAV_RPC_PORT + port_offset,
|
|
'onionport': NAV_ONION_PORT + port_offset,
|
|
'datadir': os.getenv('NAV_DATA_DIR', os.path.join(data_dir, 'navcoin')),
|
|
'bindir': os.path.join(bin_dir, 'navcoin'),
|
|
'use_segwit': True,
|
|
'use_csv': True,
|
|
'blocks_confirmed': 1,
|
|
'conf_target': 2,
|
|
'core_version_group': 18,
|
|
'chain_lookups': 'local',
|
|
'startup_tries': 40,
|
|
}
|
|
}
|
|
|
|
if PART_RPC_USER != '':
|
|
chainclients['particl']['rpcuser'] = PART_RPC_USER
|
|
chainclients['particl']['rpcpassword'] = PART_RPC_PWD
|
|
if LTC_RPC_USER != '':
|
|
chainclients['litecoin']['rpcuser'] = LTC_RPC_USER
|
|
chainclients['litecoin']['rpcpassword'] = LTC_RPC_PWD
|
|
if BTC_RPC_USER != '':
|
|
chainclients['bitcoin']['rpcuser'] = BTC_RPC_USER
|
|
chainclients['bitcoin']['rpcpassword'] = BTC_RPC_PWD
|
|
if XMR_RPC_USER != '':
|
|
chainclients['monero']['rpcuser'] = XMR_RPC_USER
|
|
chainclients['monero']['rpcpassword'] = XMR_RPC_PWD
|
|
if PIVX_RPC_USER != '':
|
|
chainclients['pivx']['rpcuser'] = PIVX_RPC_USER
|
|
chainclients['pivx']['rpcpassword'] = PIVX_RPC_PWD
|
|
if DASH_RPC_USER != '':
|
|
chainclients['dash']['rpcuser'] = DASH_RPC_USER
|
|
chainclients['dash']['rpcpassword'] = DASH_RPC_PWD
|
|
if FIRO_RPC_USER != '':
|
|
chainclients['firo']['rpcuser'] = FIRO_RPC_USER
|
|
chainclients['firo']['rpcpassword'] = FIRO_RPC_PWD
|
|
if NAV_RPC_USER != '':
|
|
chainclients['nav']['rpcuser'] = NAV_RPC_USER
|
|
chainclients['nav']['rpcpassword'] = NAV_RPC_PWD
|
|
|
|
chainclients['monero']['walletsdir'] = os.getenv('XMR_WALLETS_DIR', chainclients['monero']['datadir'])
|
|
|
|
if initwalletsonly:
|
|
logger.info('Initialising wallets')
|
|
settings = load_config(config_path)
|
|
|
|
init_coins = settings['chainclients'].keys()
|
|
logger.info('Active coins: %s', ', '.join(init_coins))
|
|
if coins_changed:
|
|
init_coins = with_coins
|
|
logger.info('Initialising coins: %s', ', '.join(init_coins))
|
|
initialise_wallets(particl_wallet_mnemonic, init_coins, data_dir, settings, chain, use_tor_proxy)
|
|
|
|
print('Done.')
|
|
return 0
|
|
|
|
if enable_tor:
|
|
logger.info('Enabling TOR')
|
|
settings = load_config(config_path)
|
|
|
|
tor_control_password = settings.get('tor_control_password', None)
|
|
if tor_control_password is None:
|
|
tor_control_password = generate_salt(24)
|
|
settings['tor_control_password'] = tor_control_password
|
|
write_torrc(data_dir, tor_control_password)
|
|
|
|
addTorSettings(settings, tor_control_password)
|
|
for coin in settings['chainclients']:
|
|
modify_tor_config(settings, coin, tor_control_password, enable=True, extra_opts=extra_opts)
|
|
|
|
with open(config_path, 'w') as fp:
|
|
json.dump(settings, fp, indent=4)
|
|
|
|
logger.info('Done.')
|
|
return 0
|
|
|
|
if disable_tor:
|
|
logger.info('Disabling TOR')
|
|
settings = load_config(config_path)
|
|
settings['use_tor'] = False
|
|
for coin in settings['chainclients']:
|
|
modify_tor_config(settings, coin, tor_control_password=None, enable=False, extra_opts=extra_opts)
|
|
|
|
with open(config_path, 'w') as fp:
|
|
json.dump(settings, fp, indent=4)
|
|
|
|
logger.info('Done.')
|
|
return 0
|
|
|
|
if disable_coin != '':
|
|
logger.info('Disabling coin: %s', disable_coin)
|
|
settings = load_config(config_path)
|
|
|
|
if disable_coin not in settings['chainclients']:
|
|
exitWithError(f'{disable_coin} not configured')
|
|
|
|
coin_settings = settings['chainclients'][disable_coin]
|
|
if coin_settings['connection_type'] == 'none' and coin_settings['manage_daemon'] is False:
|
|
exitWithError(f'{disable_coin} is already disabled')
|
|
coin_settings['connection_type'] = 'none'
|
|
coin_settings['manage_daemon'] = False
|
|
|
|
with open(config_path, 'w') as fp:
|
|
json.dump(settings, fp, indent=4)
|
|
|
|
logger.info('Done.')
|
|
return 0
|
|
|
|
extra_opts['data_dir'] = data_dir
|
|
extra_opts['tor_control_password'] = tor_control_password
|
|
|
|
if add_coin != '':
|
|
logger.info('Adding coin: %s', add_coin)
|
|
settings = load_config(config_path)
|
|
if tor_control_password is None and settings.get('use_tor', False):
|
|
extra_opts['tor_control_password'] = settings.get('tor_control_password', None)
|
|
|
|
if particl_wallet_mnemonic != 'none':
|
|
# Ensure Particl wallet is unencrypted or correct password is supplied
|
|
test_particl_encryption(data_dir, settings, chain, use_tor_proxy)
|
|
|
|
if add_coin in settings['chainclients']:
|
|
coin_settings = settings['chainclients'][add_coin]
|
|
if coin_settings['connection_type'] == 'none' and coin_settings['manage_daemon'] is False:
|
|
logger.info('Enabling coin: %s', add_coin)
|
|
coin_settings['connection_type'] = 'rpc'
|
|
coin_settings['manage_daemon'] = True
|
|
with open(config_path, 'w') as fp:
|
|
json.dump(settings, fp, indent=4)
|
|
logger.info('Done.')
|
|
return 0
|
|
exitWithError('{} is already in the settings file'.format(add_coin))
|
|
|
|
settings['chainclients'][add_coin] = chainclients[add_coin]
|
|
settings['use_tor_proxy'] = use_tor_proxy
|
|
|
|
if not no_cores:
|
|
prepareCore(add_coin, known_coins[add_coin], settings, data_dir, extra_opts)
|
|
|
|
if not prepare_bin_only:
|
|
prepareDataDir(add_coin, settings, chain, particl_wallet_mnemonic, extra_opts)
|
|
|
|
if particl_wallet_mnemonic != 'none':
|
|
initialise_wallets(None, {add_coin, }, data_dir, settings, chain, use_tor_proxy)
|
|
|
|
with open(config_path, 'w') as fp:
|
|
json.dump(settings, fp, indent=4)
|
|
|
|
logger.info(f'Done. Coin {add_coin} successfully added.')
|
|
return 0
|
|
|
|
logger.info('With coins: %s', ', '.join(with_coins))
|
|
if os.path.exists(config_path):
|
|
if not prepare_bin_only:
|
|
exitWithError('{} exists'.format(config_path))
|
|
else:
|
|
with open(config_path) as fs:
|
|
settings = json.load(fs)
|
|
|
|
# Add temporary default config for any coins that have not been added
|
|
for c in with_coins:
|
|
if c not in settings['chainclients']:
|
|
settings['chainclients'][c] = chainclients[c]
|
|
else:
|
|
for c in with_coins:
|
|
withchainclients[c] = chainclients[c]
|
|
|
|
settings = {
|
|
'debug': True,
|
|
'zmqhost': f'tcp://{PART_RPC_HOST}',
|
|
'zmqport': PART_ZMQ_PORT + port_offset,
|
|
'htmlhost': htmlhost,
|
|
'htmlport': UI_HTML_PORT + port_offset,
|
|
'network_key': '7sW2UEcHXvuqEjkpE5mD584zRaQYs6WXYohue4jLFZPTvMSxwvgs',
|
|
'network_pubkey': '035758c4a22d7dd59165db02a56156e790224361eb3191f02197addcb3bde903d2',
|
|
'chainclients': withchainclients,
|
|
'min_delay_event': 5, # Min delay in seconds before reacting to an event
|
|
'max_delay_event': 50, # Max delay in seconds before reacting to an event
|
|
'check_progress_seconds': 60,
|
|
'check_watched_seconds': 60,
|
|
'check_expired_seconds': 60,
|
|
'wallet_update_timeout': 10, # Seconds to wait for wallet page update
|
|
}
|
|
|
|
wshost: str = extra_opts.get('wshost', htmlhost)
|
|
if wshost != 'none':
|
|
settings['wshost'] = wshost
|
|
settings['wsport'] = UI_WS_PORT + port_offset
|
|
|
|
if use_tor_proxy:
|
|
tor_control_password = generate_salt(24)
|
|
addTorSettings(settings, tor_control_password)
|
|
|
|
if not no_cores:
|
|
for c in with_coins:
|
|
prepareCore(c, known_coins[c], settings, data_dir, extra_opts)
|
|
|
|
if prepare_bin_only:
|
|
logger.info('Done.')
|
|
return 0
|
|
|
|
for c in with_coins:
|
|
prepareDataDir(c, settings, chain, particl_wallet_mnemonic, extra_opts)
|
|
|
|
with open(config_path, 'w') as fp:
|
|
json.dump(settings, fp, indent=4)
|
|
|
|
if particl_wallet_mnemonic == 'none':
|
|
logger.info('Done.')
|
|
return 0
|
|
|
|
initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, chain, use_tor_proxy)
|
|
print('Done.')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|