diff --git a/.cirrus.yml b/.cirrus.yml index e4a42e4..49ddf13 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -6,7 +6,7 @@ lint_task: - pip install flake8 codespell script: - flake8 --version - - PYTHONWARNINGS="ignore" flake8 --ignore=E203,E501,F841,W503,E702,E131 --exclude=basicswap/contrib,basicswap/interface/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py + - flake8 --ignore=E203,E501,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,.eggs,.tox,bin/install_certifi.py - codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static test_task: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index eef2bc5..6e559a3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: pip install flake8 codespell - name: Running flake8 run: | - flake8 --ignore=E203,E501,F841,W503 --per-file-ignores="basicswap/interface/bch.py:E131,E702" --exclude=basicswap/contrib,basicswap/interface/contrib,messages_pb2.py,.eggs,.tox,bin/install_certifi.py + flake8 --ignore=E203,E501,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,.eggs,.tox,bin/install_certifi.py - name: Running codespell run: | codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static diff --git a/basicswap/base.py b/basicswap/base.py index 27e5635..6a6ef7f 100644 --- a/basicswap/base.py +++ b/basicswap/base.py @@ -35,7 +35,7 @@ def getaddrinfo_tor(*args): class BaseApp: - def __init__(self, fp, data_dir, settings, chain, log_name='BasicSwap'): + def __init__(self, fp, data_dir, settings, chain, log_name="BasicSwap"): self.log_name = log_name self.fp = fp self.fail_code = 0 @@ -47,19 +47,19 @@ class BaseApp: self.coin_clients = {} self.coin_interfaces = {} self.mxDB = threading.Lock() - self.debug = self.settings.get('debug', False) + self.debug = self.settings.get("debug", False) self.delay_event = threading.Event() self.chainstate_delay_event = threading.Event() self._network = None self.prepareLogging() - self.log.info('Network: {}'.format(self.chain)) + self.log.info("Network: {}".format(self.chain)) - self.use_tor_proxy = self.settings.get('use_tor', False) - self.tor_proxy_host = self.settings.get('tor_proxy_host', '127.0.0.1') - self.tor_proxy_port = self.settings.get('tor_proxy_port', 9050) - self.tor_control_password = self.settings.get('tor_control_password', None) - self.tor_control_port = self.settings.get('tor_control_port', 9051) + self.use_tor_proxy = self.settings.get("use_tor", False) + self.tor_proxy_host = self.settings.get("tor_proxy_host", "127.0.0.1") + self.tor_proxy_port = self.settings.get("tor_proxy_port", 9050) + self.tor_control_password = self.settings.get("tor_control_password", None) + self.tor_control_port = self.settings.get("tor_control_port", 9051) self.default_socket = socket.socket self.default_socket_timeout = socket.getdefaulttimeout() self.default_socket_getaddrinfo = socket.getaddrinfo @@ -77,10 +77,17 @@ class BaseApp: # Remove any existing handlers self.log.handlers = [] - formatter = logging.Formatter('%(asctime)s %(levelname)s : %(message)s', '%Y-%m-%d %H:%M:%S') + formatter = logging.Formatter( + "%(asctime)s %(levelname)s : %(message)s", "%Y-%m-%d %H:%M:%S" + ) stream_stdout = logging.StreamHandler() - if self.log_name != 'BasicSwap': - stream_stdout.setFormatter(logging.Formatter('%(asctime)s %(name)s %(levelname)s : %(message)s', '%Y-%m-%d %H:%M:%S')) + if self.log_name != "BasicSwap": + stream_stdout.setFormatter( + logging.Formatter( + "%(asctime)s %(name)s %(levelname)s : %(message)s", + "%Y-%m-%d %H:%M:%S", + ) + ) else: stream_stdout.setFormatter(formatter) stream_fp = logging.StreamHandler(self.fp) @@ -92,68 +99,91 @@ class BaseApp: def getChainClientSettings(self, coin): try: - return self.settings['chainclients'][chainparams[coin]['name']] + return self.settings["chainclients"][chainparams[coin]["name"]] except Exception: return {} def setDaemonPID(self, name, pid) -> None: if isinstance(name, Coins): - self.coin_clients[name]['pid'] = pid + self.coin_clients[name]["pid"] = pid return for c, v in self.coin_clients.items(): - if v['name'] == name: - v['pid'] = pid + if v["name"] == name: + v["pid"] = pid def getChainDatadirPath(self, coin) -> str: - datadir = self.coin_clients[coin]['datadir'] - testnet_name = '' if self.chain == 'mainnet' else chainparams[coin][self.chain].get('name', self.chain) + datadir = self.coin_clients[coin]["datadir"] + testnet_name = ( + "" + if self.chain == "mainnet" + else chainparams[coin][self.chain].get("name", self.chain) + ) return os.path.join(datadir, testnet_name) def getCoinIdFromName(self, coin_name: str): for c, params in chainparams.items(): - if coin_name.lower() == params['name'].lower(): + if coin_name.lower() == params["name"].lower(): return c - raise ValueError('Unknown coin: {}'.format(coin_name)) + raise ValueError("Unknown coin: {}".format(coin_name)) def callrpc(self, method, params=[], wallet=None): cc = self.coin_clients[Coins.PART] - return callrpc(cc['rpcport'], cc['rpcauth'], method, params, wallet, cc['rpchost']) + return callrpc( + cc["rpcport"], cc["rpcauth"], method, params, wallet, cc["rpchost"] + ) def callcoinrpc(self, coin, method, params=[], wallet=None): cc = self.coin_clients[coin] - return callrpc(cc['rpcport'], cc['rpcauth'], method, params, wallet, cc['rpchost']) + return callrpc( + cc["rpcport"], cc["rpcauth"], method, params, wallet, cc["rpchost"] + ) def callcoincli(self, coin_type, params, wallet=None, timeout=None): - bindir = self.coin_clients[coin_type]['bindir'] - datadir = self.coin_clients[coin_type]['datadir'] - cli_bin: str = chainparams[coin_type].get('cli_binname', chainparams[coin_type]['name'] + '-cli') - command_cli = os.path.join(bindir, cli_bin + ('.exe' if os.name == 'nt' else '')) - args = [command_cli, ] - if self.chain != 'mainnet': - args.append('-' + self.chain) - args.append('-datadir=' + datadir) + bindir = self.coin_clients[coin_type]["bindir"] + datadir = self.coin_clients[coin_type]["datadir"] + cli_bin: str = chainparams[coin_type].get( + "cli_binname", chainparams[coin_type]["name"] + "-cli" + ) + command_cli = os.path.join( + bindir, cli_bin + (".exe" if os.name == "nt" else "") + ) + args = [ + command_cli, + ] + if self.chain != "mainnet": + args.append("-" + self.chain) + args.append("-datadir=" + datadir) args += shlex.split(params) - p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen( + args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) out = p.communicate(timeout=timeout) if len(out[1]) > 0: - raise ValueError('CLI error ' + str(out[1])) - return out[0].decode('utf-8').strip() + raise ValueError("CLI error " + str(out[1])) + return out[0].decode("utf-8").strip() def is_transient_error(self, ex) -> bool: if isinstance(ex, TemporaryError): return True str_error = str(ex).lower() - return 'read timed out' in str_error or 'no connection to daemon' in str_error + return "read timed out" in str_error or "no connection to daemon" in str_error def setConnectionParameters(self, timeout=120): opener = urllib.request.build_opener() - opener.addheaders = [('User-agent', 'Mozilla/5.0')] + opener.addheaders = [("User-agent", "Mozilla/5.0")] urllib.request.install_opener(opener) if self.use_tor_proxy: - socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, self.tor_proxy_host, self.tor_proxy_port, rdns=True) + socks.setdefaultproxy( + socks.PROXY_TYPE_SOCKS5, + self.tor_proxy_host, + self.tor_proxy_port, + rdns=True, + ) socket.socket = socks.socksocket - socket.getaddrinfo = getaddrinfo_tor # Without this accessing .onion links would fail + socket.getaddrinfo = ( + getaddrinfo_tor # Without this accessing .onion links would fail + ) socket.setdefaulttimeout(timeout) @@ -166,10 +196,16 @@ class BaseApp: def readURL(self, url: str, timeout: int = 120, headers={}) -> bytes: open_handler = None if self.use_tor_proxy: - open_handler = SocksiPyHandler(socks.PROXY_TYPE_SOCKS5, self.tor_proxy_host, self.tor_proxy_port) - opener = urllib.request.build_opener(open_handler) if self.use_tor_proxy else urllib.request.build_opener() + open_handler = SocksiPyHandler( + socks.PROXY_TYPE_SOCKS5, self.tor_proxy_host, self.tor_proxy_port + ) + opener = ( + urllib.request.build_opener(open_handler) + if self.use_tor_proxy + else urllib.request.build_opener() + ) if headers is None: - opener.addheaders = [('User-agent', 'Mozilla/5.0')] + opener.addheaders = [("User-agent", "Mozilla/5.0")] request = urllib.request.Request(url, headers=headers) return opener.open(request, timeout=timeout).read() @@ -180,7 +216,9 @@ class BaseApp: def torControl(self, query): try: - command = 'AUTHENTICATE "{}"\r\n{}\r\nQUIT\r\n'.format(self.tor_control_password, query).encode('utf-8') + command = 'AUTHENTICATE "{}"\r\n{}\r\nQUIT\r\n'.format( + self.tor_control_password, query + ).encode("utf-8") c = socket.create_connection((self.tor_proxy_host, self.tor_control_port)) c.send(command) response = bytearray() @@ -192,23 +230,23 @@ class BaseApp: c.close() return response except Exception as e: - self.log.error(f'torControl {e}') + self.log.error(f"torControl {e}") return def getTime(self) -> int: return int(time.time()) + self.mock_time_offset def setMockTimeOffset(self, new_offset: int) -> None: - self.log.warning(f'Setting mocktime to {new_offset}') + self.log.warning(f"Setting mocktime to {new_offset}") self.mock_time_offset = new_offset def get_int_setting(self, name: str, default_v: int, min_v: int, max_v) -> int: value: int = self.settings.get(name, default_v) if value < min_v: - self.log.warning(f'Setting {name} to {min_v}') + self.log.warning(f"Setting {name} to {min_v}") value = min_v if value > max_v: - self.log.warning(f'Setting {name} to {max_v}') + self.log.warning(f"Setting {name} to {max_v}") value = max_v return value diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 6c97af6..3e29940 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019-2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -177,13 +178,19 @@ def threadPollXMRChainState(swap_client, coin_type): while not swap_client.chainstate_delay_event.is_set(): try: new_height = ci.getChainHeight() - if new_height != cc['chain_height']: - swap_client.log.debug('New {} block at height: {}'.format(ci.ticker(), new_height)) + if new_height != cc["chain_height"]: + swap_client.log.debug( + "New {} block at height: {}".format(ci.ticker(), new_height) + ) with swap_client.mxDB: - cc['chain_height'] = new_height + cc["chain_height"] = new_height except Exception as e: - swap_client.log.warning('threadPollXMRChainState {}, error: {}'.format(ci.ticker(), str(e))) - swap_client.chainstate_delay_event.wait(random.randrange(20, 30)) # Random to stagger updates + swap_client.log.warning( + "threadPollXMRChainState {}, error: {}".format(ci.ticker(), str(e)) + ) + swap_client.chainstate_delay_event.wait( + random.randrange(20, 30) + ) # Random to stagger updates def threadPollWOWChainState(swap_client, coin_type): @@ -192,13 +199,19 @@ def threadPollWOWChainState(swap_client, coin_type): while not swap_client.chainstate_delay_event.is_set(): try: new_height = ci.getChainHeight() - if new_height != cc['chain_height']: - swap_client.log.debug('New {} block at height: {}'.format(ci.ticker(), new_height)) + if new_height != cc["chain_height"]: + swap_client.log.debug( + "New {} block at height: {}".format(ci.ticker(), new_height) + ) with swap_client.mxDB: - cc['chain_height'] = new_height + cc["chain_height"] = new_height except Exception as e: - swap_client.log.warning('threadPollWOWChainState {}, error: {}'.format(ci.ticker(), str(e))) - swap_client.chainstate_delay_event.wait(random.randrange(20, 30)) # Random to stagger updates + swap_client.log.warning( + "threadPollWOWChainState {}, error: {}".format(ci.ticker(), str(e)) + ) + swap_client.chainstate_delay_event.wait( + random.randrange(20, 30) + ) # Random to stagger updates def threadPollChainState(swap_client, coin_type): @@ -207,20 +220,28 @@ def threadPollChainState(swap_client, coin_type): while not swap_client.chainstate_delay_event.is_set(): try: chain_state = ci.getBlockchainInfo() - if chain_state['bestblockhash'] != cc['chain_best_block']: - swap_client.log.debug('New {} block at height: {}'.format(ci.ticker(), chain_state['blocks'])) + if chain_state["bestblockhash"] != cc["chain_best_block"]: + swap_client.log.debug( + "New {} block at height: {}".format( + ci.ticker(), chain_state["blocks"] + ) + ) with swap_client.mxDB: - cc['chain_height'] = chain_state['blocks'] - cc['chain_best_block'] = chain_state['bestblockhash'] - if 'mediantime' in chain_state: - cc['chain_median_time'] = chain_state['mediantime'] + cc["chain_height"] = chain_state["blocks"] + cc["chain_best_block"] = chain_state["bestblockhash"] + if "mediantime" in chain_state: + cc["chain_median_time"] = chain_state["mediantime"] except Exception as e: - swap_client.log.warning('threadPollChainState {}, error: {}'.format(ci.ticker(), str(e))) - swap_client.chainstate_delay_event.wait(random.randrange(20, 30)) # Random to stagger updates + swap_client.log.warning( + "threadPollChainState {}, error: {}".format(ci.ticker(), str(e)) + ) + swap_client.chainstate_delay_event.wait( + random.randrange(20, 30) + ) # Random to stagger updates -class WatchedOutput(): # Watch for spends - __slots__ = ('bid_id', 'txid_hex', 'vout', 'tx_type', 'swap_type') +class WatchedOutput: # Watch for spends + __slots__ = ("bid_id", "txid_hex", "vout", "tx_type", "swap_type") def __init__(self, bid_id: bytes, txid_hex: str, vout, tx_type, swap_type): self.bid_id = bid_id @@ -230,8 +251,8 @@ class WatchedOutput(): # Watch for spends self.swap_type = swap_type -class WatchedScript(): # Watch for txns containing outputs - __slots__ = ('bid_id', 'script', 'tx_type', 'swap_type') +class WatchedScript: # Watch for txns containing outputs + __slots__ = ("bid_id", "script", "tx_type", "swap_type") def __init__(self, bid_id: bytes, script: bytes, tx_type, swap_type): self.bid_id = bid_id @@ -240,7 +261,7 @@ class WatchedScript(): # Watch for txns containing outputs self.swap_type = swap_type -class WatchedTransaction(): +class WatchedTransaction: # TODO # Watch for presence in mempool (getrawtransaction) def __init__(self, bid_id: bytes, txid_hex: str, tx_type, swap_type): @@ -258,22 +279,46 @@ class BasicSwap(BaseApp): SwapTypes.XMR_SWAP: xmr_swap_1.XmrSwapInterface(), } - def __init__(self, fp, data_dir, settings, chain, log_name='BasicSwap', transient_instance=False): + def __init__( + self, + fp, + data_dir, + settings, + chain, + log_name="BasicSwap", + transient_instance=False, + ): super().__init__(fp, data_dir, settings, chain, log_name) - v = __version__.split('.') - self._version = struct.pack('>HHH', int(v[0]), int(v[1]), int(v[2])) + v = __version__.split(".") + self._version = struct.pack(">HHH", int(v[0]), int(v[1]), int(v[2])) self._transient_instance = transient_instance - self.check_actions_seconds = self.get_int_setting('check_actions_seconds', 10, 1, 10 * 60) - self.check_expired_seconds = self.get_int_setting('check_expired_seconds', 5 * 60, 1, 10 * 60) # Expire DB records and smsg messages - self.check_expiring_bids_offers_seconds = self.get_int_setting('check_expiring_bids_offers_seconds', 60, 1, 10 * 60) # Set offer and bid states to expired - self.check_progress_seconds = self.get_int_setting('check_progress_seconds', 60, 1, 10 * 60) - self.check_smsg_seconds = self.get_int_setting('check_smsg_seconds', 10, 1, 10 * 60) - self.check_watched_seconds = self.get_int_setting('check_watched_seconds', 60, 1, 10 * 60) - self.check_xmr_swaps_seconds = self.get_int_setting('check_xmr_swaps_seconds', 20, 1, 10 * 60) - self.startup_tries = self.get_int_setting('startup_tries', 21, 1, 100) # Seconds waited for will be (x(1 + x+1) / 2 - self.debug_ui = self.settings.get('debug_ui', False) + self.check_actions_seconds = self.get_int_setting( + "check_actions_seconds", 10, 1, 10 * 60 + ) + self.check_expired_seconds = self.get_int_setting( + "check_expired_seconds", 5 * 60, 1, 10 * 60 + ) # Expire DB records and smsg messages + self.check_expiring_bids_offers_seconds = self.get_int_setting( + "check_expiring_bids_offers_seconds", 60, 1, 10 * 60 + ) # Set offer and bid states to expired + self.check_progress_seconds = self.get_int_setting( + "check_progress_seconds", 60, 1, 10 * 60 + ) + self.check_smsg_seconds = self.get_int_setting( + "check_smsg_seconds", 10, 1, 10 * 60 + ) + self.check_watched_seconds = self.get_int_setting( + "check_watched_seconds", 60, 1, 10 * 60 + ) + self.check_xmr_swaps_seconds = self.get_int_setting( + "check_xmr_swaps_seconds", 20, 1, 10 * 60 + ) + self.startup_tries = self.get_int_setting( + "startup_tries", 21, 1, 100 + ) # Seconds waited for will be (x(1 + x+1) / 2 + self.debug_ui = self.settings.get("debug_ui", False) self._debug_cases = [] self._last_checked_actions = 0 self._last_checked_expired = 0 @@ -282,103 +327,140 @@ class BasicSwap(BaseApp): self._last_checked_smsg = 0 self._last_checked_watched = 0 self._last_checked_xmr_swaps = 0 - self._possibly_revoked_offers = collections.deque([], maxlen=48) # TODO: improve + self._possibly_revoked_offers = collections.deque( + [], maxlen=48 + ) # TODO: improve self._expiring_bids = [] # List of bids expiring soon self._expiring_offers = [] # List of offers expiring soon self._updating_wallets_info = {} self._last_updated_wallets_info = 0 - self._zmq_queue_enabled = self.settings.get('zmq_queue_enabled', True) - self._poll_smsg = self.settings.get('poll_smsg', False) + self._zmq_queue_enabled = self.settings.get("zmq_queue_enabled", True) + self._poll_smsg = self.settings.get("poll_smsg", False) - self._notifications_enabled = self.settings.get('notifications_enabled', True) - self._disabled_notification_types = self.settings.get('disabled_notification_types', []) - self._keep_notifications = self.settings.get('keep_notifications', 50) - self._show_notifications = self.settings.get('show_notifications', 10) - self._expire_db_records = self.settings.get('expire_db_records', False) - self._expire_db_records_after = self.get_int_setting('expire_db_records_after', 7 * 86400, 0, 31 * 86400) # Seconds + self._notifications_enabled = self.settings.get("notifications_enabled", True) + self._disabled_notification_types = self.settings.get( + "disabled_notification_types", [] + ) + self._keep_notifications = self.settings.get("keep_notifications", 50) + self._show_notifications = self.settings.get("show_notifications", 10) + self._expire_db_records = self.settings.get("expire_db_records", False) + self._expire_db_records_after = self.get_int_setting( + "expire_db_records_after", 7 * 86400, 0, 31 * 86400 + ) # Seconds self._notifications_cache = {} self._is_encrypted = None self._is_locked = None # TODO: Set dynamically - self.balance_only_coins = (Coins.LTC_MWEB, ) + self.balance_only_coins = (Coins.LTC_MWEB,) self.scriptless_coins = (Coins.XMR, Coins.WOW, Coins.PART_ANON, Coins.FIRO) - self.adaptor_swap_only_coins = self.scriptless_coins + (Coins.PART_BLIND, Coins.BCH) + self.adaptor_swap_only_coins = self.scriptless_coins + ( + Coins.PART_BLIND, + Coins.BCH, + ) self.coins_without_segwit = (Coins.PIVX, Coins.DASH, Coins.NMC) # TODO: Adjust ranges - self.min_delay_event = self.get_int_setting('min_delay_event', 10, 0, 20 * 60) - self.max_delay_event = self.get_int_setting('max_delay_event', 60, self.min_delay_event, 20 * 60) - self.min_delay_event_short = self.get_int_setting('min_delay_event_short', 2, 0, 10 * 60) - self.max_delay_event_short = self.get_int_setting('max_delay_event_short', 30, self.min_delay_event_short, 10 * 60) + self.min_delay_event = self.get_int_setting("min_delay_event", 10, 0, 20 * 60) + self.max_delay_event = self.get_int_setting( + "max_delay_event", 60, self.min_delay_event, 20 * 60 + ) + self.min_delay_event_short = self.get_int_setting( + "min_delay_event_short", 2, 0, 10 * 60 + ) + self.max_delay_event_short = self.get_int_setting( + "max_delay_event_short", 30, self.min_delay_event_short, 10 * 60 + ) - self.min_delay_retry = self.get_int_setting('min_delay_retry', 60, 0, 20 * 60) - self.max_delay_retry = self.get_int_setting('max_delay_retry', 5 * 60, self.min_delay_retry, 20 * 60) + self.min_delay_retry = self.get_int_setting("min_delay_retry", 60, 0, 20 * 60) + self.max_delay_retry = self.get_int_setting( + "max_delay_retry", 5 * 60, self.min_delay_retry, 20 * 60 + ) - self.min_sequence_lock_seconds = self.settings.get('min_sequence_lock_seconds', 60 if self.debug else (1 * 60 * 60)) - self.max_sequence_lock_seconds = self.settings.get('max_sequence_lock_seconds', 96 * 60 * 60) + self.min_sequence_lock_seconds = self.settings.get( + "min_sequence_lock_seconds", 60 if self.debug else (1 * 60 * 60) + ) + self.max_sequence_lock_seconds = self.settings.get( + "max_sequence_lock_seconds", 96 * 60 * 60 + ) - self._wallet_update_timeout = self.settings.get('wallet_update_timeout', 10) + self._wallet_update_timeout = self.settings.get("wallet_update_timeout", 10) - self._restrict_unknown_seed_wallets = self.settings.get('restrict_unknown_seed_wallets', True) - self._max_check_loop_blocks = self.settings.get('max_check_loop_blocks', 100000) + self._restrict_unknown_seed_wallets = self.settings.get( + "restrict_unknown_seed_wallets", True + ) + self._max_check_loop_blocks = self.settings.get("max_check_loop_blocks", 100000) self._bid_expired_leeway = 5 self.swaps_in_progress = dict() - self.SMSG_SECONDS_IN_HOUR = 60 * 60 # Note: Set smsgsregtestadjust=0 for regtest + self.SMSG_SECONDS_IN_HOUR = ( + 60 * 60 + ) # Note: Set smsgsregtestadjust=0 for regtest self.threads = [] - self.thread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=4, thread_name_prefix='bsp') + self.thread_pool = concurrent.futures.ThreadPoolExecutor( + max_workers=4, thread_name_prefix="bsp" + ) # Encode key to match network - wif_prefix = chainparams[Coins.PART][self.chain]['key_prefix'] - self.network_key = toWIF(wif_prefix, decodeWif(self.settings['network_key'])) + wif_prefix = chainparams[Coins.PART][self.chain]["key_prefix"] + self.network_key = toWIF(wif_prefix, decodeWif(self.settings["network_key"])) - self.network_pubkey = self.settings['network_pubkey'] - self.network_addr = pubkeyToAddress(chainparams[Coins.PART][self.chain]['pubkey_address'], bytes.fromhex(self.network_pubkey)) + self.network_pubkey = self.settings["network_pubkey"] + self.network_addr = pubkeyToAddress( + chainparams[Coins.PART][self.chain]["pubkey_address"], + bytes.fromhex(self.network_pubkey), + ) - self.db_echo: bool = self.settings.get('db_echo', False) - self.sqlite_file: str = os.path.join(self.data_dir, 'db{}.sqlite'.format('' if self.chain == 'mainnet' else ('_' + self.chain))) + self.db_echo: bool = self.settings.get("db_echo", False) + self.sqlite_file: str = os.path.join( + self.data_dir, + "db{}.sqlite".format("" if self.chain == "mainnet" else ("_" + self.chain)), + ) db_exists: bool = os.path.exists(self.sqlite_file) # HACK: create_all hangs when using tox, unless create_engine is called with echo=True if not db_exists: - if os.getenv('FOR_TOX'): - self.engine = sa.create_engine('sqlite:///' + self.sqlite_file, echo=True) + if os.getenv("FOR_TOX"): + self.engine = sa.create_engine( + "sqlite:///" + self.sqlite_file, echo=True + ) else: - self.engine = sa.create_engine('sqlite:///' + self.sqlite_file) + self.engine = sa.create_engine("sqlite:///" + self.sqlite_file) close_all_sessions() Base.metadata.create_all(self.engine) self.engine.dispose() - self.engine = sa.create_engine('sqlite:///' + self.sqlite_file, echo=self.db_echo) + self.engine = sa.create_engine( + "sqlite:///" + self.sqlite_file, echo=self.db_echo + ) self.session_factory = sessionmaker(bind=self.engine, expire_on_commit=False) session = scoped_session(self.session_factory) try: - self.db_version = session.query(DBKVInt).filter_by(key='db_version').first().value + self.db_version = ( + session.query(DBKVInt).filter_by(key="db_version").first().value + ) except Exception: - self.log.info('First run') + self.log.info("First run") self.db_version = CURRENT_DB_VERSION - session.add(DBKVInt( - key='db_version', - value=self.db_version - )) + session.add(DBKVInt(key="db_version", value=self.db_version)) session.commit() try: - self.db_data_version = session.query(DBKVInt).filter_by(key='db_data_version').first().value + self.db_data_version = ( + session.query(DBKVInt).filter_by(key="db_data_version").first().value + ) except Exception: self.db_data_version = 0 try: - self._contract_count = session.query(DBKVInt).filter_by(key='contract_count').first().value + self._contract_count = ( + session.query(DBKVInt).filter_by(key="contract_count").first().value + ) except Exception: self._contract_count = 0 - session.add(DBKVInt( - key='contract_count', - value=self._contract_count - )) + session.add(DBKVInt(key="contract_count", value=self._contract_count)) session.commit() session.close() @@ -388,30 +470,44 @@ class BasicSwap(BaseApp): self.zmqContext = zmq.Context() self.zmqSubscriber = self.zmqContext.socket(zmq.SUB) - self.zmqSubscriber.connect(self.settings['zmqhost'] + ':' + str(self.settings['zmqport'])) - self.zmqSubscriber.setsockopt_string(zmq.SUBSCRIBE, 'smsg') + self.zmqSubscriber.connect( + self.settings["zmqhost"] + ":" + str(self.settings["zmqport"]) + ) + self.zmqSubscriber.setsockopt_string(zmq.SUBSCRIBE, "smsg") for c in Coins: if c in chainparams: self.setCoinConnectParams(c) - if self.chain == 'mainnet': - self.coin_clients[Coins.PART]['explorers'].append(ExplorerInsight( - self, Coins.PART, - 'https://explorer.particl.io/particl-insight-api')) - self.coin_clients[Coins.LTC]['explorers'].append(ExplorerBitAps( - self, Coins.LTC, - 'https://api.bitaps.com/ltc/v1/blockchain')) - self.coin_clients[Coins.LTC]['explorers'].append(ExplorerChainz( - self, Coins.LTC, - 'http://chainz.cryptoid.info/ltc/api.dws')) - elif self.chain == 'testnet': - self.coin_clients[Coins.PART]['explorers'].append(ExplorerInsight( - self, Coins.PART, - 'https://explorer-testnet.particl.io/particl-insight-api')) - self.coin_clients[Coins.LTC]['explorers'].append(ExplorerBitAps( - self, Coins.LTC, - 'https://api.bitaps.com/ltc/testnet/v1/blockchain')) + if self.chain == "mainnet": + self.coin_clients[Coins.PART]["explorers"].append( + ExplorerInsight( + self, Coins.PART, "https://explorer.particl.io/particl-insight-api" + ) + ) + self.coin_clients[Coins.LTC]["explorers"].append( + ExplorerBitAps( + self, Coins.LTC, "https://api.bitaps.com/ltc/v1/blockchain" + ) + ) + self.coin_clients[Coins.LTC]["explorers"].append( + ExplorerChainz( + self, Coins.LTC, "http://chainz.cryptoid.info/ltc/api.dws" + ) + ) + elif self.chain == "testnet": + self.coin_clients[Coins.PART]["explorers"].append( + ExplorerInsight( + self, + Coins.PART, + "https://explorer-testnet.particl.io/particl-insight-api", + ) + ) + self.coin_clients[Coins.LTC]["explorers"].append( + ExplorerBitAps( + self, Coins.LTC, "https://api.bitaps.com/ltc/testnet/v1/blockchain" + ) + ) # non-segwit # https://testnet.litecore.io/insight-api @@ -419,7 +515,7 @@ class BasicSwap(BaseApp): random.seed(secrets.randbits(128)) def finalise(self): - self.log.info('Finalise') + self.log.info("Finalise") with self.mxDB: self.delay_event.set() @@ -463,116 +559,166 @@ class BasicSwap(BaseApp): if self.debug: self.log.error(traceback.format_exc()) - self.log.error(f'Error: {tag} - {e}') + self.log.error(f"Error: {tag} - {e}") session.rollback() def setCoinConnectParams(self, coin): # Set anything that does not require the daemon to be running chain_client_settings = self.getChainClientSettings(coin) - bindir = os.path.expanduser(chain_client_settings.get('bindir', '')) - datadir = os.path.expanduser(chain_client_settings.get('datadir', os.path.join(cfg.TEST_DATADIRS, chainparams[coin]['name']))) + bindir = os.path.expanduser(chain_client_settings.get("bindir", "")) + datadir = os.path.expanduser( + chain_client_settings.get( + "datadir", os.path.join(cfg.TEST_DATADIRS, chainparams[coin]["name"]) + ) + ) - connection_type = chain_client_settings.get('connection_type', 'none') + connection_type = chain_client_settings.get("connection_type", "none") rpcauth = None - if connection_type == 'rpc': - if 'rpcauth' in chain_client_settings: - rpcauth = chain_client_settings['rpcauth'] - self.log.debug(f'Read {Coins(coin).name} rpc credentials from json settings') - elif 'rpcpassword' in chain_client_settings: - rpcauth = chain_client_settings['rpcuser'] + ':' + chain_client_settings['rpcpassword'] - self.log.debug(f'Read {Coins(coin).name} rpc credentials from json settings') + if connection_type == "rpc": + if "rpcauth" in chain_client_settings: + rpcauth = chain_client_settings["rpcauth"] + self.log.debug( + f"Read {Coins(coin).name} rpc credentials from json settings" + ) + elif "rpcpassword" in chain_client_settings: + rpcauth = ( + chain_client_settings["rpcuser"] + + ":" + + chain_client_settings["rpcpassword"] + ) + self.log.debug( + f"Read {Coins(coin).name} rpc credentials from json settings" + ) try: session = self.openSession() try: - last_height_checked = session.query(DBKVInt).filter_by(key='last_height_checked_' + chainparams[coin]['name']).first().value + last_height_checked = ( + session.query(DBKVInt) + .filter_by(key="last_height_checked_" + chainparams[coin]["name"]) + .first() + .value + ) except Exception: last_height_checked = 0 try: - block_check_min_time = session.query(DBKVInt).filter_by(key='block_check_min_time_' + chainparams[coin]['name']).first().value + block_check_min_time = ( + session.query(DBKVInt) + .filter_by(key="block_check_min_time_" + chainparams[coin]["name"]) + .first() + .value + ) except Exception: - block_check_min_time = 0xffffffffffffffff + block_check_min_time = 0xFFFFFFFFFFFFFFFF finally: self.closeSession(session) coin_chainparams = chainparams[coin] - default_segwit = coin_chainparams.get('has_segwit', False) - default_csv = coin_chainparams.get('has_csv', True) + default_segwit = coin_chainparams.get("has_segwit", False) + default_csv = coin_chainparams.get("has_csv", True) self.coin_clients[coin] = { - 'coin': coin, - 'name': coin_chainparams['name'], - 'connection_type': connection_type, - 'bindir': bindir, - 'datadir': datadir, - 'rpchost': chain_client_settings.get('rpchost', '127.0.0.1'), - 'rpcport': chain_client_settings.get('rpcport', coin_chainparams[self.chain]['rpcport']), - 'rpcauth': rpcauth, - 'blocks_confirmed': chain_client_settings.get('blocks_confirmed', 6), - 'conf_target': chain_client_settings.get('conf_target', 2), - 'watched_outputs': [], - 'watched_scripts': [], - 'last_height_checked': last_height_checked, - 'block_check_min_time': block_check_min_time, - 'use_segwit': chain_client_settings.get('use_segwit', default_segwit), - 'use_csv': chain_client_settings.get('use_csv', default_csv), - 'core_version_group': chain_client_settings.get('core_version_group', 0), - 'pid': None, - 'core_version': None, - 'explorers': [], - 'chain_lookups': chain_client_settings.get('chain_lookups', 'local'), - 'restore_height': chain_client_settings.get('restore_height', 0), - 'fee_priority': chain_client_settings.get('fee_priority', 0), - + "coin": coin, + "name": coin_chainparams["name"], + "connection_type": connection_type, + "bindir": bindir, + "datadir": datadir, + "rpchost": chain_client_settings.get("rpchost", "127.0.0.1"), + "rpcport": chain_client_settings.get( + "rpcport", coin_chainparams[self.chain]["rpcport"] + ), + "rpcauth": rpcauth, + "blocks_confirmed": chain_client_settings.get("blocks_confirmed", 6), + "conf_target": chain_client_settings.get("conf_target", 2), + "watched_outputs": [], + "watched_scripts": [], + "last_height_checked": last_height_checked, + "block_check_min_time": block_check_min_time, + "use_segwit": chain_client_settings.get("use_segwit", default_segwit), + "use_csv": chain_client_settings.get("use_csv", default_csv), + "core_version_group": chain_client_settings.get("core_version_group", 0), + "pid": None, + "core_version": None, + "explorers": [], + "chain_lookups": chain_client_settings.get("chain_lookups", "local"), + "restore_height": chain_client_settings.get("restore_height", 0), + "fee_priority": chain_client_settings.get("fee_priority", 0), # Chain state - 'chain_height': None, - 'chain_best_block': None, - 'chain_median_time': None, + "chain_height": None, + "chain_best_block": None, + "chain_median_time": None, } if coin in (Coins.FIRO, Coins.LTC): - if not chain_client_settings.get('min_relay_fee'): - chain_client_settings['min_relay_fee'] = 0.00001 + if not chain_client_settings.get("min_relay_fee"): + chain_client_settings["min_relay_fee"] = 0.00001 if coin == Coins.PART: - self.coin_clients[coin]['anon_tx_ring_size'] = chain_client_settings.get('anon_tx_ring_size', 12) + self.coin_clients[coin]["anon_tx_ring_size"] = chain_client_settings.get( + "anon_tx_ring_size", 12 + ) self.coin_clients[Coins.PART_ANON] = self.coin_clients[coin] self.coin_clients[Coins.PART_BLIND] = self.coin_clients[coin] if coin == Coins.LTC: self.coin_clients[Coins.LTC_MWEB] = self.coin_clients[coin] - if self.coin_clients[coin]['connection_type'] == 'rpc': + if self.coin_clients[coin]["connection_type"] == "rpc": if coin == Coins.DCR: - self.coin_clients[coin]['walletrpcport'] = chain_client_settings['walletrpcport'] + self.coin_clients[coin]["walletrpcport"] = chain_client_settings[ + "walletrpcport" + ] elif coin in (Coins.XMR, Coins.WOW): - self.coin_clients[coin]['rpctimeout'] = chain_client_settings.get('rpctimeout', 60) - self.coin_clients[coin]['walletrpctimeout'] = chain_client_settings.get('walletrpctimeout', 120) - self.coin_clients[coin]['walletrpctimeoutlong'] = chain_client_settings.get('walletrpctimeoutlong', 600) + self.coin_clients[coin]["rpctimeout"] = chain_client_settings.get( + "rpctimeout", 60 + ) + self.coin_clients[coin]["walletrpctimeout"] = chain_client_settings.get( + "walletrpctimeout", 120 + ) + self.coin_clients[coin]["walletrpctimeoutlong"] = ( + chain_client_settings.get("walletrpctimeoutlong", 600) + ) - if not self._transient_instance and chain_client_settings.get('automatically_select_daemon', False): + if not self._transient_instance and chain_client_settings.get( + "automatically_select_daemon", False + ): self.selectXMRRemoteDaemon(coin) - self.coin_clients[coin]['walletrpchost'] = chain_client_settings.get('walletrpchost', '127.0.0.1') - self.coin_clients[coin]['walletrpcport'] = chain_client_settings.get('walletrpcport', chainparams[coin][self.chain]['walletrpcport']) - if 'walletrpcpassword' in chain_client_settings: - self.coin_clients[coin]['walletrpcauth'] = (chain_client_settings['walletrpcuser'], chain_client_settings['walletrpcpassword']) + self.coin_clients[coin]["walletrpchost"] = chain_client_settings.get( + "walletrpchost", "127.0.0.1" + ) + self.coin_clients[coin]["walletrpcport"] = chain_client_settings.get( + "walletrpcport", chainparams[coin][self.chain]["walletrpcport"] + ) + if "walletrpcpassword" in chain_client_settings: + self.coin_clients[coin]["walletrpcauth"] = ( + chain_client_settings["walletrpcuser"], + chain_client_settings["walletrpcpassword"], + ) else: - raise ValueError('Missing XMR wallet rpc credentials.') + raise ValueError("Missing XMR wallet rpc credentials.") - self.coin_clients[coin]['rpcuser'] = chain_client_settings.get('rpcuser', '') - self.coin_clients[coin]['rpcpassword'] = chain_client_settings.get('rpcpassword', '') + self.coin_clients[coin]["rpcuser"] = chain_client_settings.get( + "rpcuser", "" + ) + self.coin_clients[coin]["rpcpassword"] = chain_client_settings.get( + "rpcpassword", "" + ) def getXMRTrustedDaemon(self, coin, node_host: str) -> bool: coin = Coins(coin) # Errors for invalid coin value chain_client_settings = self.getChainClientSettings(coin) - trusted_daemon_setting = chain_client_settings.get('trusted_daemon', 'auto') - self.log.debug(f'\'trusted_daemon\' setting for {getCoinName(coin)}: {trusted_daemon_setting}.') + trusted_daemon_setting = chain_client_settings.get("trusted_daemon", "auto") + self.log.debug( + f"'trusted_daemon' setting for {getCoinName(coin)}: {trusted_daemon_setting}." + ) if isinstance(trusted_daemon_setting, bool): return trusted_daemon_setting - if trusted_daemon_setting == 'auto': + if trusted_daemon_setting == "auto": return is_private_ip_address(node_host) - self.log.warning(f'Unknown \'trusted_daemon\' setting for {getCoinName(coin)}: {trusted_daemon_setting}.') + self.log.warning( + f"Unknown 'trusted_daemon' setting for {getCoinName(coin)}: {trusted_daemon_setting}." + ) return False def getXMRWalletProxy(self, coin, node_host: str) -> (Optional[str], Optional[int]): @@ -581,99 +727,114 @@ class BasicSwap(BaseApp): proxy_host = None proxy_port = None if self.use_tor_proxy: - have_cc_tor_opt = 'use_tor' in chain_client_settings - if have_cc_tor_opt and chain_client_settings['use_tor'] is False: - self.log.warning(f'use_tor is true for system but false for {coin.name}.') + have_cc_tor_opt = "use_tor" in chain_client_settings + if have_cc_tor_opt and chain_client_settings["use_tor"] is False: + self.log.warning( + f"use_tor is true for system but false for {coin.name}." + ) elif have_cc_tor_opt is False and is_private_ip_address(node_host): - self.log.warning(f'Not using proxy for {coin.name} node at private ip address {node_host}.') + self.log.warning( + f"Not using proxy for {coin.name} node at private ip address {node_host}." + ) else: proxy_host = self.tor_proxy_host proxy_port = self.tor_proxy_port return proxy_host, proxy_port def selectXMRRemoteDaemon(self, coin): - self.log.info('Selecting remote XMR daemon.') + self.log.info("Selecting remote XMR daemon.") chain_client_settings = self.getChainClientSettings(coin) - remote_daemon_urls = chain_client_settings.get('remote_daemon_urls', []) + remote_daemon_urls = chain_client_settings.get("remote_daemon_urls", []) coin_settings = self.coin_clients[coin] - rpchost: str = coin_settings['rpchost'] - rpcport: int = coin_settings['rpcport'] - timeout: int = coin_settings['rpctimeout'] + rpchost: str = coin_settings["rpchost"] + rpcport: int = coin_settings["rpcport"] + timeout: int = coin_settings["rpctimeout"] def get_rpc_func(rpcport, daemon_login, rpchost): proxy_host, proxy_port = self.getXMRWalletProxy(coin, rpchost) if proxy_host: - self.log.info(f'Connecting through proxy at {proxy_host}.') + self.log.info(f"Connecting through proxy at {proxy_host}.") if coin in (Coins.XMR, Coins.WOW): - return make_xmr_rpc2_func(rpcport, daemon_login, rpchost, proxy_host=proxy_host, proxy_port=proxy_port) + return make_xmr_rpc2_func( + rpcport, + daemon_login, + rpchost, + proxy_host=proxy_host, + proxy_port=proxy_port, + ) daemon_login = None - if coin_settings.get('rpcuser', '') != '': - daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', '')) - current_daemon_url = f'{rpchost}:{rpcport}' + if coin_settings.get("rpcuser", "") != "": + daemon_login = ( + coin_settings.get("rpcuser", ""), + coin_settings.get("rpcpassword", ""), + ) + current_daemon_url = f"{rpchost}:{rpcport}" if current_daemon_url in remote_daemon_urls: - self.log.info(f'Trying last used url {rpchost}:{rpcport}.') + self.log.info(f"Trying last used url {rpchost}:{rpcport}.") try: rpc2 = get_rpc_func(rpcport, daemon_login, rpchost) - test = rpc2('get_height', timeout=timeout)['height'] + _ = rpc2("get_height", timeout=timeout)["height"] return True except Exception as e: - self.log.warning(f'Failed to set XMR remote daemon to {rpchost}:{rpcport}, {e}') + self.log.warning( + f"Failed to set XMR remote daemon to {rpchost}:{rpcport}, {e}" + ) random.shuffle(remote_daemon_urls) for url in remote_daemon_urls: - self.log.info(f'Trying url {url}.') + self.log.info(f"Trying url {url}.") try: - rpchost, rpcport = url.rsplit(':', 1) + rpchost, rpcport = url.rsplit(":", 1) rpc2 = get_rpc_func(rpcport, daemon_login, rpchost) - test = rpc2('get_height', timeout=timeout)['height'] - coin_settings['rpchost'] = rpchost - coin_settings['rpcport'] = rpcport + _ = rpc2("get_height", timeout=timeout)["height"] + coin_settings["rpchost"] = rpchost + coin_settings["rpcport"] = rpcport data = { - 'rpchost': rpchost, - 'rpcport': rpcport, + "rpchost": rpchost, + "rpcport": rpcport, } - self.editSettings(self.coin_clients[coin]['name'], data) + self.editSettings(self.coin_clients[coin]["name"], data) return True except Exception as e: - self.log.warning(f'Failed to set XMR remote daemon to {url}, {e}') + self.log.warning(f"Failed to set XMR remote daemon to {url}, {e}") - raise ValueError('Failed to select a working XMR daemon url.') + raise ValueError("Failed to select a working XMR daemon url.") def isCoinActive(self, coin): use_coinid = coin - interface_ind = 'interface' + interface_ind = "interface" if coin == Coins.PART_ANON: use_coinid = Coins.PART - interface_ind = 'interface_anon' + interface_ind = "interface_anon" if coin == Coins.PART_BLIND: use_coinid = Coins.PART - interface_ind = 'interface_blind' + interface_ind = "interface_blind" if coin == Coins.LTC_MWEB: use_coinid = Coins.LTC - interface_ind = 'interface_mweb' + interface_ind = "interface_mweb" if use_coinid not in self.coin_clients: - raise ValueError('Unknown coinid {}'.format(int(coin))) + raise ValueError("Unknown coinid {}".format(int(coin))) return interface_ind in self.coin_clients[use_coinid] def ci(self, coin): # Coin interface use_coinid = coin - interface_ind = 'interface' + interface_ind = "interface" if coin == Coins.PART_ANON: use_coinid = Coins.PART - interface_ind = 'interface_anon' + interface_ind = "interface_anon" if coin == Coins.PART_BLIND: use_coinid = Coins.PART - interface_ind = 'interface_blind' + interface_ind = "interface_blind" if coin == Coins.LTC_MWEB: use_coinid = Coins.LTC - interface_ind = 'interface_mweb' + interface_ind = "interface_mweb" if use_coinid not in self.coin_clients: - raise ValueError('Unknown coinid {}'.format(int(coin))) + raise ValueError("Unknown coinid {}".format(int(coin))) if interface_ind not in self.coin_clients[use_coinid]: raise InactiveCoin(int(coin)) @@ -688,125 +849,167 @@ class BasicSwap(BaseApp): def pi(self, protocol_ind): if protocol_ind not in self.protocolInterfaces: - raise ValueError('Unknown protocol_ind {}'.format(int(protocol_ind))) + raise ValueError("Unknown protocol_ind {}".format(int(protocol_ind))) return self.protocolInterfaces[protocol_ind] def createInterface(self, coin): if coin == Coins.PART: interface = PARTInterface(self.coin_clients[coin], self.chain, self) - self.coin_clients[coin]['interface_anon'] = PARTInterfaceAnon(self.coin_clients[coin], self.chain, self) - self.coin_clients[coin]['interface_blind'] = PARTInterfaceBlind(self.coin_clients[coin], self.chain, self) + self.coin_clients[coin]["interface_anon"] = PARTInterfaceAnon( + self.coin_clients[coin], self.chain, self + ) + self.coin_clients[coin]["interface_blind"] = PARTInterfaceBlind( + self.coin_clients[coin], self.chain, self + ) return interface elif coin == Coins.BTC: from .interface.btc import BTCInterface + return BTCInterface(self.coin_clients[coin], self.chain, self) elif coin == Coins.BCH: from .interface.bch import BCHInterface + return BCHInterface(self.coin_clients[coin], self.chain, self) elif coin == Coins.LTC: from .interface.ltc import LTCInterface, LTCInterfaceMWEB + interface = LTCInterface(self.coin_clients[coin], self.chain, self) - self.coin_clients[coin]['interface_mweb'] = LTCInterfaceMWEB(self.coin_clients[coin], self.chain, self) + self.coin_clients[coin]["interface_mweb"] = LTCInterfaceMWEB( + self.coin_clients[coin], self.chain, self + ) return interface elif coin == Coins.DCR: from .interface.dcr import DCRInterface + return DCRInterface(self.coin_clients[coin], self.chain, self) elif coin == Coins.NMC: from .interface.nmc import NMCInterface + return NMCInterface(self.coin_clients[coin], self.chain, self) elif coin == Coins.XMR: from .interface.xmr import XMRInterface + xmr_i = XMRInterface(self.coin_clients[coin], self.chain, self) chain_client_settings = self.getChainClientSettings(coin) - xmr_i.setWalletFilename(chain_client_settings['walletfile']) + xmr_i.setWalletFilename(chain_client_settings["walletfile"]) return xmr_i elif coin == Coins.WOW: from .interface.wow import WOWInterface + wow_i = WOWInterface(self.coin_clients[coin], self.chain, self) chain_client_settings = self.getChainClientSettings(coin) - wow_i.setWalletFilename(chain_client_settings['walletfile']) + wow_i.setWalletFilename(chain_client_settings["walletfile"]) return wow_i elif coin == Coins.PIVX: from .interface.pivx import PIVXInterface + return PIVXInterface(self.coin_clients[coin], self.chain, self) elif coin == Coins.DASH: from .interface.dash import DASHInterface + return DASHInterface(self.coin_clients[coin], self.chain, self) elif coin == Coins.FIRO: from .interface.firo import FIROInterface + return FIROInterface(self.coin_clients[coin], self.chain, self) elif coin == Coins.NAV: from .interface.nav import NAVInterface + return NAVInterface(self.coin_clients[coin], self.chain, self) else: - raise ValueError('Unknown coin type') + raise ValueError("Unknown coin type") def createPassthroughInterface(self, coin): if coin == Coins.BTC: from .interface.passthrough_btc import PassthroughBTCInterface + return PassthroughBTCInterface(self.coin_clients[coin], self.chain) else: - raise ValueError('Unknown coin type') + raise ValueError("Unknown coin type") def setCoinRunParams(self, coin): cc = self.coin_clients[coin] if coin in (Coins.XMR, Coins.WOW): return - if cc['connection_type'] == 'rpc' and cc['rpcauth'] is None: - chain_client_settings = self.getChainClientSettings(coin) - authcookiepath = os.path.join(self.getChainDatadirPath(coin), '.cookie') + if cc["connection_type"] == "rpc" and cc["rpcauth"] is None: + authcookiepath = os.path.join(self.getChainDatadirPath(coin), ".cookie") - pidfilename = cc['name'] - if cc['name'] in ('bitcoin', 'litecoin', 'namecoin', 'dash', 'firo', 'bitcoincash'): - pidfilename += 'd' + pidfilename = cc["name"] + if cc["name"] in ( + "bitcoin", + "litecoin", + "namecoin", + "dash", + "firo", + "bitcoincash", + ): + pidfilename += "d" - pidfilepath = os.path.join(self.getChainDatadirPath(coin), pidfilename + '.pid') - self.log.debug('Reading %s rpc credentials from auth cookie %s', Coins(coin).name, authcookiepath) + pidfilepath = os.path.join( + self.getChainDatadirPath(coin), pidfilename + ".pid" + ) + self.log.debug( + "Reading %s rpc credentials from auth cookie %s", + Coins(coin).name, + authcookiepath, + ) # Wait for daemon to start # Test pids to ensure authcookie is read for the correct process datadir_pid = -1 for i in range(20): try: - if os.name == 'nt' and cc['core_version_group'] <= 17: + if os.name == "nt" and cc["core_version_group"] <= 17: # Older core versions don't write a pid file on windows pass else: - with open(pidfilepath, 'rb') as fp: - datadir_pid = int(fp.read().decode('utf-8')) - assert (datadir_pid == cc['pid']), 'Mismatched pid' - assert (os.path.exists(authcookiepath)) + with open(pidfilepath, "rb") as fp: + datadir_pid = int(fp.read().decode("utf-8")) + assert datadir_pid == cc["pid"], "Mismatched pid" + assert os.path.exists(authcookiepath) break except Exception as e: if self.debug: - self.log.warning('Error, iteration %d: %s', i, str(e)) + self.log.warning("Error, iteration %d: %s", i, str(e)) self.delay_event.wait(0.5) try: - if os.name != 'nt' or cc['core_version_group'] > 17: # Litecoin on windows doesn't write a pid file - assert (datadir_pid == cc['pid']), 'Mismatched pid' - with open(authcookiepath, 'rb') as fp: - cc['rpcauth'] = escape_rpcauth(fp.read().decode('utf-8')) + if ( + os.name != "nt" or cc["core_version_group"] > 17 + ): # Litecoin on windows doesn't write a pid file + assert datadir_pid == cc["pid"], "Mismatched pid" + with open(authcookiepath, "rb") as fp: + cc["rpcauth"] = escape_rpcauth(fp.read().decode("utf-8")) except Exception as e: - self.log.error('Unable to read authcookie for %s, %s, datadir pid %d, daemon pid %s. Error: %s', Coins(coin).name, authcookiepath, datadir_pid, cc['pid'], str(e)) - raise ValueError('Error, terminating') + self.log.error( + "Unable to read authcookie for %s, %s, datadir pid %d, daemon pid %s. Error: %s", + Coins(coin).name, + authcookiepath, + datadir_pid, + cc["pid"], + str(e), + ) + raise ValueError("Error, terminating") def createCoinInterface(self, coin): - if self.coin_clients[coin]['connection_type'] == 'rpc': - self.coin_clients[coin]['interface'] = self.createInterface(coin) - elif self.coin_clients[coin]['connection_type'] == 'passthrough': - self.coin_clients[coin]['interface'] = self.createPassthroughInterface(coin) + if self.coin_clients[coin]["connection_type"] == "rpc": + self.coin_clients[coin]["interface"] = self.createInterface(coin) + elif self.coin_clients[coin]["connection_type"] == "passthrough": + self.coin_clients[coin]["interface"] = self.createPassthroughInterface(coin) def start(self): import platform - self.log.info('Starting BasicSwap %s, database v%d\n\n', __version__, self.db_version) - self.log.info(f'Python version: {platform.python_version()}') - self.log.info('SQLAlchemy version: %s', sa.__version__) - self.log.info('Timezone offset: %d (%s)', time.timezone, time.tzname[0]) + + self.log.info( + "Starting BasicSwap %s, database v%d\n\n", __version__, self.db_version + ) + self.log.info(f"Python version: {platform.python_version()}") + self.log.info("SQLAlchemy version: %s", sa.__version__) + self.log.info("Timezone offset: %d (%s)", time.timezone, time.tzname[0]) upgradeDatabase(self, self.db_version) upgradeDatabaseData(self, self.db_data_version) if self._zmq_queue_enabled and self._poll_smsg: - self.log.warning('SMSG polling and zmq listener enabled.') + self.log.warning("SMSG polling and zmq listener enabled.") for c in Coins: if c not in chainparams: @@ -814,13 +1017,13 @@ class BasicSwap(BaseApp): self.setCoinRunParams(c) self.createCoinInterface(c) - if self.coin_clients[c]['connection_type'] == 'rpc': + if self.coin_clients[c]["connection_type"] == "rpc": ci = self.ci(c) self.waitForDaemonRPC(c) core_version = ci.getDaemonVersion() - self.log.info('%s Core version %d', ci.coin_name(), core_version) - self.coin_clients[c]['core_version'] = core_version + self.log.info("%s Core version %d", ci.coin_name(), core_version) + self.coin_clients[c]["core_version"] = core_version # thread_func = threadPollXMRChainState if c in (Coins.XMR, Coins.WOW) else threadPollChainState if c == Coins.XMR: thread_func = threadPollXMRChainState @@ -834,30 +1037,35 @@ class BasicSwap(BaseApp): t.start() if c == Coins.PART: - self.coin_clients[c]['have_spent_index'] = ci.haveSpentIndex() + self.coin_clients[c]["have_spent_index"] = ci.haveSpentIndex() try: # Sanity checks - rv = self.callcoinrpc(c, 'extkey') - if 'result' in rv and 'No keys to list.' in rv['result']: - raise ValueError('No keys loaded.') + rv = self.callcoinrpc(c, "extkey") + if "result" in rv and "No keys to list." in rv["result"]: + raise ValueError("No keys loaded.") - if self.callcoinrpc(c, 'getstakinginfo')['enabled'] is not False: - self.log.warning('%s staking is not disabled.', ci.coin_name()) + if ( + self.callcoinrpc(c, "getstakinginfo")["enabled"] + is not False + ): + self.log.warning( + "%s staking is not disabled.", ci.coin_name() + ) except Exception as e: - self.log.error('Sanity checks failed: %s', str(e)) + self.log.error("Sanity checks failed: %s", str(e)) elif c == Coins.XMR: try: ci.ensureWalletExists() - except Exception as e: - self.log.warning('Can\'t open XMR wallet, could be locked.') + except Exception as e: # noqa: F841 + self.log.warning("Can't open XMR wallet, could be locked.") continue elif c == Coins.WOW: try: ci.ensureWalletExists() - except Exception as e: - self.log.warning('Can\'t open WOW wallet, could be locked.') + except Exception as e: # noqa: F841 + self.log.warning("Can't open WOW wallet, could be locked.") continue elif c == Coins.LTC: ci_mweb = self.ci(Coins.LTC_MWEB) @@ -867,25 +1075,31 @@ class BasicSwap(BaseApp): self.checkWalletSeed(c) - if 'p2p_host' in self.settings: + if "p2p_host" in self.settings: network_key = self.getNetworkKey(1) - self._network = bsn.Network(self.settings['p2p_host'], self.settings['p2p_port'], network_key, self) + self._network = bsn.Network( + self.settings["p2p_host"], self.settings["p2p_port"], network_key, self + ) self._network.startNetwork() - self.log.debug('network_key %s\nnetwork_pubkey %s\nnetwork_addr %s', - self.network_key, self.network_pubkey, self.network_addr) + self.log.debug( + "network_key %s\nnetwork_pubkey %s\nnetwork_addr %s", + self.network_key, + self.network_pubkey, + self.network_addr, + ) - ro = self.callrpc('smsglocalkeys') + ro = self.callrpc("smsglocalkeys") found = False - for k in ro['smsg_keys']: - if k['address'] == self.network_addr: + for k in ro["smsg_keys"]: + if k["address"] == self.network_addr: found = True break if not found: - self.log.info('Importing network key to SMSG') - self.callrpc('smsgimportprivkey', [self.network_key, 'basicswap offers']) - ro = self.callrpc('smsglocalkeys', ['anon', '-', self.network_addr]) - ensure(ro['result'] == 'Success.', 'smsglocalkeys failed') + self.log.info("Importing network key to SMSG") + self.callrpc("smsgimportprivkey", [self.network_key, "basicswap offers"]) + ro = self.callrpc("smsglocalkeys", ["anon", "-", self.network_addr]) + ensure(ro["result"] == "Success.", "smsglocalkeys failed") # TODO: Ensure smsg is enabled for the active wallet. @@ -898,50 +1112,58 @@ class BasicSwap(BaseApp): # Scan inbox # TODO: Redundant? small window for zmq messages to go unnoticed during startup? # options = {'encoding': 'hex'} - options = {'encoding': 'none'} - ro = self.callrpc('smsginbox', ['unread', '', options]) + options = {"encoding": "none"} + ro = self.callrpc("smsginbox", ["unread", "", options]) nm = 0 - for msg in ro['messages']: + for msg in ro["messages"]: # TODO: Remove workaround for smsginbox bug - get_msg = self.callrpc('smsg', [msg['msgid'], {'encoding': 'hex', 'setread': True}]) + get_msg = self.callrpc( + "smsg", [msg["msgid"], {"encoding": "hex", "setread": True}] + ) self.processMsg(get_msg) nm += 1 - self.log.info('Scanned %d unread messages.', nm) + self.log.info("Scanned %d unread messages.", nm) def stopDaemon(self, coin) -> None: if coin in (Coins.XMR, Coins.DCR, Coins.WOW): return num_tries = 10 - authcookiepath = os.path.join(self.getChainDatadirPath(coin), '.cookie') + authcookiepath = os.path.join(self.getChainDatadirPath(coin), ".cookie") stopping = False try: for i in range(num_tries): - rv = self.callcoincli(coin, 'stop', timeout=10) - self.log.debug('Trying to stop %s', Coins(coin).name) + self.callcoincli(coin, "stop", timeout=10) + self.log.debug("Trying to stop %s", Coins(coin).name) stopping = True # self.delay_event will be set here time.sleep(i + 1) except Exception as ex: str_ex = str(ex) - if 'Could not connect' in str_ex or 'Could not locate RPC credentials' in str_ex or 'couldn\'t connect to server' in str_ex: + if ( + "Could not connect" in str_ex + or "Could not locate RPC credentials" in str_ex + or "couldn't connect to server" in str_ex + ): if stopping: for i in range(30): # The lock file doesn't get deleted # Using .cookie is a temporary workaround, will only work if rpc password is unset. # TODO: Query lock on .lock properly if os.path.exists(authcookiepath): - self.log.debug('Waiting on .cookie file %s', Coins(coin).name) + self.log.debug( + "Waiting on .cookie file %s", Coins(coin).name + ) time.sleep(i + 1) time.sleep(4) # Extra time to settle return - self.log.error('stopDaemon %s', str(ex)) + self.log.error("stopDaemon %s", str(ex)) self.log.error(traceback.format_exc()) - raise ValueError('Could not stop {}'.format(Coins(coin).name)) + raise ValueError("Could not stop {}".format(Coins(coin).name)) def stopDaemons(self) -> None: for c in self.activeCoins(): chain_client_settings = self.getChainClientSettings(c) - if chain_client_settings['manage_daemon'] is True: + if chain_client_settings["manage_daemon"] is True: self.stopDaemon(c) def waitForDaemonRPC(self, coin_type, with_wallet: bool = True) -> None: @@ -951,33 +1173,46 @@ class BasicSwap(BaseApp): if coin_type in (Coins.XMR, Coins.WOW): return - check_coin_types = [coin_type,] + check_coin_types = [ + coin_type, + ] if coin_type == Coins.PART: check_coin_types += [Coins.PART_ANON, Coins.PART_BLIND] for check_coin_type in check_coin_types: ci = self.ci(check_coin_type) # checkWallets can adjust the wallet name. if ci.checkWallets() < 1: - self.log.error('No wallets found for coin {}.'.format(ci.coin_name())) - self.stopRunning(1) # systemd will try to restart the process if fail_code != 0 + self.log.error( + "No wallets found for coin {}.".format(ci.coin_name()) + ) + self.stopRunning( + 1 + ) # systemd will try to restart the process if fail_code != 0 startup_tries = self.startup_tries chain_client_settings = self.getChainClientSettings(coin_type) - if 'startup_tries' in chain_client_settings: - startup_tries = chain_client_settings['startup_tries'] + if "startup_tries" in chain_client_settings: + startup_tries = chain_client_settings["startup_tries"] if startup_tries < 1: - self.log.warning('startup_tries can\'t be less than 1.') + self.log.warning("startup_tries can't be less than 1.") startup_tries = 1 for i in range(startup_tries): if self.delay_event.is_set(): return try: - self.coin_clients[coin_type]['interface'].testDaemonRPC(with_wallet) + self.coin_clients[coin_type]["interface"].testDaemonRPC(with_wallet) return except Exception as ex: - self.log.warning('Can\'t connect to %s RPC: %s. Trying again in %d second/s, %d/%d.', Coins(coin_type).name, str(ex), (1 + i), i + 1, startup_tries) + self.log.warning( + "Can't connect to %s RPC: %s. Trying again in %d second/s, %d/%d.", + Coins(coin_type).name, + str(ex), + (1 + i), + i + 1, + startup_tries, + ) self.delay_event.wait(1 + i) - self.log.error('Can\'t connect to %s RPC, exiting.', Coins(coin_type).name) + self.log.error("Can't connect to %s RPC, exiting.", Coins(coin_type).name) self.stopRunning(1) # systemd will try to restart the process if fail_code != 0 def checkCoinsReady(self, coin_from, coin_to) -> None: @@ -985,14 +1220,22 @@ class BasicSwap(BaseApp): for c in check_coins: ci = self.ci(c) if self._restrict_unknown_seed_wallets and not ci.knownWalletSeed(): - raise ValueError('{} has an unexpected wallet seed and "restrict_unknown_seed_wallets" is enabled.'.format(ci.coin_name())) - if self.coin_clients[c]['connection_type'] != 'rpc': + raise ValueError( + '{} has an unexpected wallet seed and "restrict_unknown_seed_wallets" is enabled.'.format( + ci.coin_name() + ) + ) + if self.coin_clients[c]["connection_type"] != "rpc": continue if c in (Coins.XMR, Coins.WOW): continue # TODO - synced = round(ci.getBlockchainInfo()['verificationprogress'], 3) + synced = round(ci.getBlockchainInfo()["verificationprogress"], 3) if synced < 1.0: - raise ValueError('{} chain is still syncing, currently at {}.'.format(ci.coin_name(), synced)) + raise ValueError( + "{} chain is still syncing, currently at {}.".format( + ci.coin_name(), synced + ) + ) def isSystemUnlocked(self) -> bool: # TODO - Check all active coins @@ -1008,27 +1251,30 @@ class BasicSwap(BaseApp): for c in Coins: if c not in chainparams: continue - chain_client_settings = self.getChainClientSettings(c) - if self.coin_clients[c]['connection_type'] == 'rpc': + if self.coin_clients[c]["connection_type"] == "rpc": yield c def getListOfWalletCoins(self): # Always unlock Particl first - coins_list = [Coins.PART, ] + [c for c in self.activeCoins() if c != Coins.PART] + coins_list = [ + Coins.PART, + ] + [c for c in self.activeCoins() if c != Coins.PART] if Coins.LTC in coins_list: coins_list.append(Coins.LTC_MWEB) return coins_list - def changeWalletPasswords(self, old_password: str, new_password: str, coin=None) -> None: + def changeWalletPasswords( + self, old_password: str, new_password: str, coin=None + ) -> None: # Only the main wallet password is changed for monero, avoid issues by preventing until active swaps are complete if len(self.swaps_in_progress) > 0: - raise ValueError('Can\'t change passwords while swaps are in progress') + raise ValueError("Can't change passwords while swaps are in progress") if old_password == new_password: - raise ValueError('Passwords must differ') + raise ValueError("Passwords must differ") if len(new_password) < 4: - raise ValueError('New password is too short') + raise ValueError("New password is too short") coins_list = self.getListOfWalletCoins() @@ -1039,8 +1285,8 @@ class BasicSwap(BaseApp): ci = self.ci(c) try: ci.unlockWallet(old_password) - except Exception as e: - raise ValueError('Failed to unlock {}'.format(ci.coin_name())) + except Exception as e: # noqa: F841 + raise ValueError("Failed to unlock {}".format(ci.coin_name())) for c in coins_list: if coin and c != coin: @@ -1049,7 +1295,9 @@ class BasicSwap(BaseApp): # Update cached state if coin is None or coin == Coins.PART: - self._is_encrypted, self._is_locked = self.ci(Coins.PART).isWalletEncryptedLocked() + self._is_encrypted, self._is_locked = self.ci( + Coins.PART + ).isWalletEncryptedLocked() def unlockWallets(self, password: str, coin=None) -> None: try: @@ -1060,7 +1308,9 @@ class BasicSwap(BaseApp): try: self.ci(c).unlockWallet(password) except Exception as e: - self.log.warning('Failed to unlock wallet {}'.format(getCoinName(c))) + self.log.warning( + "Failed to unlock wallet {}".format(getCoinName(c)) + ) if coin is not None or c == Coins.PART: raise e if c == Coins.PART: @@ -1089,7 +1339,7 @@ class BasicSwap(BaseApp): return ci = self.ci(coin_type) db_key_coin_name = ci.coin_name().lower() - self.log.info('Initialising {} wallet.'.format(ci.coin_name())) + self.log.info("Initialising {} wallet.".format(ci.coin_name())) if coin_type in (Coins.XMR, Coins.WOW): key_view = self.getWalletKey(coin_type, 1, for_ed25519=True) @@ -1097,7 +1347,7 @@ class BasicSwap(BaseApp): ci.initialiseWallet(key_view, key_spend) root_address = ci.getAddressFromKeys(key_view, key_spend) - key_str = 'main_wallet_addr_' + db_key_coin_name + key_str = "main_wallet_addr_" + db_key_coin_name self.setStringKV(key_str, root_address) return @@ -1107,7 +1357,7 @@ class BasicSwap(BaseApp): ci.initialiseWallet(root_key) except Exception as e: # < 0.21: sethdseed cannot set a new HD seed while still in Initial Block Download. - self.log.error('initialiseWallet failed: {}'.format(str(e))) + self.log.error("initialiseWallet failed: {}".format(str(e))) if raise_errors: raise e if self.debug: @@ -1119,21 +1369,21 @@ class BasicSwap(BaseApp): legacy_root_hash = ci.getSeedHash(root_key, 20) try: session = self.openSession() - key_str = 'main_wallet_seedid_' + db_key_coin_name + key_str = "main_wallet_seedid_" + db_key_coin_name self.setStringKV(key_str, root_hash.hex(), session) if coin_type == Coins.DCR: # TODO: How to force getmasterpubkey to always return the new slip44 (42) key - key_str = 'main_wallet_seedid_alt_' + db_key_coin_name + key_str = "main_wallet_seedid_alt_" + db_key_coin_name self.setStringKV(key_str, legacy_root_hash.hex(), session) # Clear any saved addresses - self.clearStringKV('receive_addr_' + db_key_coin_name, session) - self.clearStringKV('stealth_addr_' + db_key_coin_name, session) + self.clearStringKV("receive_addr_" + db_key_coin_name, session) + self.clearStringKV("stealth_addr_" + db_key_coin_name, session) coin_id = int(coin_type) info_type = 1 # wallet - query_str = f'DELETE FROM wallets WHERE coin_id = {coin_id} AND balance_type = {info_type}' + query_str = f"DELETE FROM wallets WHERE coin_id = {coin_id} AND balance_type = {info_type}" session.execute(text(query_str)) finally: self.closeSession(session) @@ -1141,18 +1391,34 @@ class BasicSwap(BaseApp): def updateIdentityBidState(self, session, address: str, bid) -> None: identity_stats = session.query(KnownIdentity).filter_by(address=address).first() if not identity_stats: - identity_stats = KnownIdentity(active_ind=1, address=address, created_at=self.getTime()) + identity_stats = KnownIdentity( + active_ind=1, address=address, created_at=self.getTime() + ) if bid.state == BidStates.SWAP_COMPLETED: if bid.was_sent: - identity_stats.num_sent_bids_successful = zeroIfNone(identity_stats.num_sent_bids_successful) + 1 + identity_stats.num_sent_bids_successful = ( + zeroIfNone(identity_stats.num_sent_bids_successful) + 1 + ) else: - identity_stats.num_recv_bids_successful = zeroIfNone(identity_stats.num_recv_bids_successful) + 1 - elif bid.state in (BidStates.BID_ERROR, BidStates.XMR_SWAP_FAILED_REFUNDED, BidStates.XMR_SWAP_FAILED_SWIPED, BidStates.XMR_SWAP_FAILED, BidStates.SWAP_TIMEDOUT): + identity_stats.num_recv_bids_successful = ( + zeroIfNone(identity_stats.num_recv_bids_successful) + 1 + ) + elif bid.state in ( + BidStates.BID_ERROR, + BidStates.XMR_SWAP_FAILED_REFUNDED, + BidStates.XMR_SWAP_FAILED_SWIPED, + BidStates.XMR_SWAP_FAILED, + BidStates.SWAP_TIMEDOUT, + ): if bid.was_sent: - identity_stats.num_sent_bids_failed = zeroIfNone(identity_stats.num_sent_bids_failed) + 1 + identity_stats.num_sent_bids_failed = ( + zeroIfNone(identity_stats.num_sent_bids_failed) + 1 + ) else: - identity_stats.num_recv_bids_failed = zeroIfNone(identity_stats.num_recv_bids_failed) + 1 + identity_stats.num_recv_bids_failed = ( + zeroIfNone(identity_stats.num_recv_bids_failed) + 1 + ) identity_stats.updated_at = self.getTime() session.add(identity_stats) @@ -1197,15 +1463,28 @@ class BasicSwap(BaseApp): def clearStringKV(self, str_key: str, session=None) -> None: try: use_session = self.openSession(session) - use_session.execute(text('DELETE FROM kv_string WHERE key = :key'), {'key': str_key}) + use_session.execute( + text("DELETE FROM kv_string WHERE key = :key"), {"key": str_key} + ) finally: if session is None: self.closeSession(use_session) - def getPreFundedTx(self, linked_type: int, linked_id: bytes, tx_type: int, session=None) -> Optional[bytes]: + def getPreFundedTx( + self, linked_type: int, linked_id: bytes, tx_type: int, session=None + ) -> Optional[bytes]: try: use_session = self.openSession(session) - tx = use_session.query(PrefundedTx).filter_by(linked_type=linked_type, linked_id=linked_id, tx_type=tx_type, used_by=None).first() + tx = ( + use_session.query(PrefundedTx) + .filter_by( + linked_type=linked_type, + linked_id=linked_id, + tx_type=tx_type, + used_by=None, + ) + .first() + ) if not tx: return None tx.used_by = linked_id @@ -1217,13 +1496,13 @@ class BasicSwap(BaseApp): def activateBid(self, session, bid) -> None: if bid.bid_id in self.swaps_in_progress: - self.log.debug('Bid %s is already in progress', bid.bid_id.hex()) + self.log.debug("Bid %s is already in progress", bid.bid_id.hex()) - self.log.debug('Loading active bid %s', bid.bid_id.hex()) + self.log.debug("Loading active bid %s", bid.bid_id.hex()) offer = session.query(Offer).filter_by(offer_id=bid.offer_id).first() if not offer: - raise ValueError('Offer not found') + raise ValueError("Offer not found") self.loadBidTxns(bid, session) @@ -1237,40 +1516,86 @@ class BasicSwap(BaseApp): if offer.swap_type == SwapTypes.XMR_SWAP: xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() self.watchXmrSwap(bid, offer, xmr_swap, session) - if ci_to.watch_blocks_for_scripts() and bid.xmr_a_lock_tx and bid.xmr_a_lock_tx.chain_height: + if ( + ci_to.watch_blocks_for_scripts() + and bid.xmr_a_lock_tx + and bid.xmr_a_lock_tx.chain_height + ): if not bid.xmr_b_lock_tx or not bid.xmr_b_lock_tx.txid: - chain_a_block_header = ci_from.getBlockHeaderFromHeight(bid.xmr_a_lock_tx.chain_height) - chain_b_block_header = ci_to.getBlockHeaderAt(chain_a_block_header['time']) + chain_a_block_header = ci_from.getBlockHeaderFromHeight( + bid.xmr_a_lock_tx.chain_height + ) + chain_b_block_header = ci_to.getBlockHeaderAt( + chain_a_block_header["time"] + ) dest_script = ci_to.getPkDest(xmr_swap.pkbs) - self.setLastHeightCheckedStart(ci_to.coin_type(), chain_b_block_header['height'], session) - self.addWatchedScript(ci_to.coin_type(), bid.bid_id, dest_script, TxTypes.XMR_SWAP_B_LOCK) + self.setLastHeightCheckedStart( + ci_to.coin_type(), chain_b_block_header["height"], session + ) + self.addWatchedScript( + ci_to.coin_type(), + bid.bid_id, + dest_script, + TxTypes.XMR_SWAP_B_LOCK, + ) else: self.swaps_in_progress[bid.bid_id] = (bid, offer) if bid.initiate_tx and bid.initiate_tx.txid: - self.addWatchedOutput(coin_from, bid.bid_id, bid.initiate_tx.txid.hex(), bid.initiate_tx.vout, BidStates.SWAP_INITIATED) + self.addWatchedOutput( + coin_from, + bid.bid_id, + bid.initiate_tx.txid.hex(), + bid.initiate_tx.vout, + BidStates.SWAP_INITIATED, + ) if bid.participate_tx and bid.participate_tx.txid: - self.addWatchedOutput(coin_to, bid.bid_id, bid.participate_tx.txid.hex(), bid.participate_tx.vout, BidStates.SWAP_PARTICIPATING) + self.addWatchedOutput( + coin_to, + bid.bid_id, + bid.participate_tx.txid.hex(), + bid.participate_tx.vout, + BidStates.SWAP_PARTICIPATING, + ) - if ci_to.watch_blocks_for_scripts() and bid.participate_tx and bid.participate_tx.txid is None: + if ( + ci_to.watch_blocks_for_scripts() + and bid.participate_tx + and bid.participate_tx.txid is None + ): if bid.initiate_tx and bid.initiate_tx.chain_height: - chain_a_block_header = ci_from.getBlockHeaderFromHeight(bid.initiate_tx.chain_height) - chain_b_block_header = ci_to.getBlockHeaderAt(chain_a_block_header['time']) - self.setLastHeightCheckedStart(coin_to, chain_b_block_header['height'], session) - self.addWatchedScript(coin_to, bid.bid_id, ci_to.getScriptDest(bid.participate_tx.script), TxTypes.PTX) + chain_a_block_header = ci_from.getBlockHeaderFromHeight( + bid.initiate_tx.chain_height + ) + chain_b_block_header = ci_to.getBlockHeaderAt( + chain_a_block_header["time"] + ) + self.setLastHeightCheckedStart( + coin_to, chain_b_block_header["height"], session + ) + self.addWatchedScript( + coin_to, + bid.bid_id, + ci_to.getScriptDest(bid.participate_tx.script), + TxTypes.PTX, + ) - if self.coin_clients[coin_from]['last_height_checked'] < 1: + if self.coin_clients[coin_from]["last_height_checked"] < 1: if bid.initiate_tx and bid.initiate_tx.chain_height: - self.setLastHeightCheckedStart(coin_from, bid.initiate_tx.chain_height, session) - if self.coin_clients[coin_to]['last_height_checked'] < 1: + self.setLastHeightCheckedStart( + coin_from, bid.initiate_tx.chain_height, session + ) + if self.coin_clients[coin_to]["last_height_checked"] < 1: if bid.participate_tx and bid.participate_tx.chain_height: - self.setLastHeightCheckedStart(coin_to, bid.participate_tx.chain_height, session) + self.setLastHeightCheckedStart( + coin_to, bid.participate_tx.chain_height, session + ) # TODO process addresspool if bid has previously been abandoned def deactivateBid(self, session, offer, bid) -> None: # Remove from in progress - self.log.debug('Removing bid from in-progress: %s', bid.bid_id.hex()) + self.log.debug("Removing bid from in-progress: %s", bid.bid_id.hex()) self.swaps_in_progress.pop(bid.bid_id, None) bid.in_progress = 0 @@ -1301,27 +1626,42 @@ class BasicSwap(BaseApp): use_session = self.openSession(session) # Remove any delayed events - query: str = 'DELETE FROM actions WHERE linked_id = x\'{}\' '.format(bid.bid_id.hex()) + query: str = "DELETE FROM actions WHERE linked_id = x'{}' ".format( + bid.bid_id.hex() + ) if self.debug: - query = 'UPDATE actions SET active_ind = 2 WHERE linked_id = x\'{}\' '.format(bid.bid_id.hex()) + query = ( + "UPDATE actions SET active_ind = 2 WHERE linked_id = x'{}' ".format( + bid.bid_id.hex() + ) + ) use_session.execute(text(query)) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) # Unlock locked inputs (TODO) if offer.swap_type == SwapTypes.XMR_SWAP: ci_from = self.ci(offer.coin_to if reverse_bid else offer.coin_from) - xmr_swap = use_session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() + xmr_swap = ( + use_session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() + ) if xmr_swap: try: ci_from.unlockInputs(xmr_swap.a_lock_tx) except Exception as e: - self.log.debug('unlockInputs failed {}'.format(str(e))) + self.log.debug("unlockInputs failed {}".format(str(e))) pass # Invalid parameter, unknown transaction elif SwapTypes.SELLER_FIRST: pass # No prevouts are locked # Update identity stats - if bid.state in (BidStates.BID_ERROR, BidStates.XMR_SWAP_FAILED_REFUNDED, BidStates.XMR_SWAP_FAILED_SWIPED, BidStates.XMR_SWAP_FAILED, BidStates.SWAP_COMPLETED, BidStates.SWAP_TIMEDOUT): + if bid.state in ( + BidStates.BID_ERROR, + BidStates.XMR_SWAP_FAILED_REFUNDED, + BidStates.XMR_SWAP_FAILED_SWIPED, + BidStates.XMR_SWAP_FAILED, + BidStates.SWAP_COMPLETED, + BidStates.SWAP_TIMEDOUT, + ): was_sent: bool = bid.was_received if reverse_bid else bid.was_sent peer_address = offer.addr_from if was_sent else bid.bid_addr self.updateIdentityBidState(use_session, peer_address, bid) @@ -1332,25 +1672,33 @@ class BasicSwap(BaseApp): def loadFromDB(self) -> None: if self.isSystemUnlocked() is False: - self.log.info('Not loading from db. System is locked.') + self.log.info("Not loading from db. System is locked.") return - self.log.info('Loading data from db') + self.log.info("Loading data from db") self.mxDB.acquire() self.swaps_in_progress.clear() try: session = scoped_session(self.session_factory) for bid in session.query(Bid): - if bid.in_progress == 1 or (bid.state and bid.state > BidStates.BID_RECEIVED and bid.state < BidStates.SWAP_COMPLETED): + if bid.in_progress == 1 or ( + bid.state + and bid.state > BidStates.BID_RECEIVED + and bid.state < BidStates.SWAP_COMPLETED + ): try: self.activateBid(session, bid) except Exception as ex: - self.logException(f'Failed to activate bid! Error: {ex}') + self.logException(f"Failed to activate bid! Error: {ex}") try: - bid.setState(BidStates.BID_ERROR, 'Failed to activate') - offer = session.query(Offer).filter_by(offer_id=bid.offer_id).first() + bid.setState(BidStates.BID_ERROR, "Failed to activate") + offer = ( + session.query(Offer) + .filter_by(offer_id=bid.offer_id) + .first() + ) self.deactivateBid(session, offer, bid) except Exception as ex: - self.logException(f'Further error deactivating: {ex}') + self.logException(f"Further error deactivating: {ex}") self.buildNotificationsCache(session) finally: session.close() @@ -1367,14 +1715,19 @@ class BasicSwap(BaseApp): bid_valid = (bid.expire_at - now) + 10 * 60 # Add 10 minute buffer return max(smsg_min_valid, min(smsg_max_valid, bid_valid)) - def sendSmsg(self, addr_from: str, addr_to: str, payload_hex: bytes, msg_valid: int) -> bytes: - options = {'decodehex': True, 'ttl_is_seconds': True} + def sendSmsg( + self, addr_from: str, addr_to: str, payload_hex: bytes, msg_valid: int + ) -> bytes: + options = {"decodehex": True, "ttl_is_seconds": True} try: - ro = self.callrpc('smsgsend', [addr_from, addr_to, payload_hex, False, msg_valid, False, options]) - return bytes.fromhex(ro['msgid']) + ro = self.callrpc( + "smsgsend", + [addr_from, addr_to, payload_hex, False, msg_valid, False, options], + ) + return bytes.fromhex(ro["msgid"]) except Exception as e: if self.debug: - self.log.error('smsgsend failed {}'.format(json.dumps(ro, indent=4))) + self.log.error("smsgsend failed {}".format(json.dumps(ro, indent=4))) raise e def is_reverse_ads_bid(self, coin_from, coin_to) -> bool: @@ -1384,7 +1737,7 @@ class BasicSwap(BaseApp): for coin in (coin_from, coin_to): if coin in self.balance_only_coins: - raise ValueError('Invalid coin: {}'.format(coin.name)) + raise ValueError("Invalid coin: {}".format(coin.name)) if swap_type == SwapTypes.XMR_SWAP: reverse_bid: bool = self.is_reverse_ads_bid(coin_from, coin_to) @@ -1392,43 +1745,69 @@ class BasicSwap(BaseApp): ptx_coin = coin_from if reverse_bid else coin_to if itx_coin in self.coins_without_segwit + self.scriptless_coins: if ptx_coin in self.coins_without_segwit + self.scriptless_coins: - raise ValueError('{} -> {} is not currently supported'.format(coin_from.name, coin_to.name)) - raise ValueError('Invalid swap type for: {} -> {}'.format(coin_from.name, coin_to.name)) + raise ValueError( + "{} -> {} is not currently supported".format( + coin_from.name, coin_to.name + ) + ) + raise ValueError( + "Invalid swap type for: {} -> {}".format( + coin_from.name, coin_to.name + ) + ) else: - if coin_from in self.adaptor_swap_only_coins or coin_to in self.adaptor_swap_only_coins: - raise ValueError('Invalid swap type for: {} -> {}'.format(coin_from.name, coin_to.name)) + if ( + coin_from in self.adaptor_swap_only_coins + or coin_to in self.adaptor_swap_only_coins + ): + raise ValueError( + "Invalid swap type for: {} -> {}".format( + coin_from.name, coin_to.name + ) + ) def notify(self, event_type, event_data, session=None) -> None: show_event = event_type not in self._disabled_notification_types if event_type == NT.OFFER_RECEIVED: - self.log.debug('Received new offer %s', event_data['offer_id']) + self.log.debug("Received new offer %s", event_data["offer_id"]) if self.ws_server and show_event: - event_data['event'] = 'new_offer' + event_data["event"] = "new_offer" self.ws_server.send_message_to_all(json.dumps(event_data)) elif event_type == NT.BID_RECEIVED: - self.log.info('Received valid bid %s for %s offer %s', event_data['bid_id'], event_data['type'], event_data['offer_id']) + self.log.info( + "Received valid bid %s for %s offer %s", + event_data["bid_id"], + event_data["type"], + event_data["offer_id"], + ) if self.ws_server and show_event: - event_data['event'] = 'new_bid' + event_data["event"] = "new_bid" self.ws_server.send_message_to_all(json.dumps(event_data)) elif event_type == NT.BID_ACCEPTED: - self.log.info('Received valid bid accept for %s', event_data['bid_id']) + self.log.info("Received valid bid accept for %s", event_data["bid_id"]) if self.ws_server and show_event: - event_data['event'] = 'bid_accepted' + event_data["event"] = "bid_accepted" self.ws_server.send_message_to_all(json.dumps(event_data)) else: - self.log.warning(f'Unknown notification {event_type}') + self.log.warning(f"Unknown notification {event_type}") try: now: int = self.getTime() use_session = self.openSession(session) - use_session.add(Notification( - active_ind=1, - created_at=now, - event_type=int(event_type), - event_data=bytes(json.dumps(event_data), 'UTF-8'), - )) + use_session.add( + Notification( + active_ind=1, + created_at=now, + event_type=int(event_type), + event_data=bytes(json.dumps(event_data), "UTF-8"), + ) + ) - use_session.execute(text(f'DELETE FROM notifications WHERE record_id NOT IN (SELECT record_id FROM notifications WHERE active_ind=1 ORDER BY created_at ASC LIMIT {self._keep_notifications})')) + use_session.execute( + text( + f"DELETE FROM notifications WHERE record_id NOT IN (SELECT record_id FROM notifications WHERE active_ind=1 ORDER BY created_at ASC LIMIT {self._keep_notifications})" + ) + ) if show_event: self._notifications_cache[now] = (event_type, event_data) @@ -1442,75 +1821,112 @@ class BasicSwap(BaseApp): def buildNotificationsCache(self, session): self._notifications_cache.clear() - q = session.execute(text(f'SELECT created_at, event_type, event_data FROM notifications WHERE active_ind = 1 ORDER BY created_at ASC LIMIT {self._show_notifications}')) + q = session.execute( + text( + f"SELECT created_at, event_type, event_data FROM notifications WHERE active_ind = 1 ORDER BY created_at ASC LIMIT {self._show_notifications}" + ) + ) for entry in q: - self._notifications_cache[entry[0]] = (entry[1], json.loads(entry[2].decode('UTF-8'))) + self._notifications_cache[entry[0]] = ( + entry[1], + json.loads(entry[2].decode("UTF-8")), + ) def getNotifications(self): rv = [] for k, v in self._notifications_cache.items(): - rv.append((time.strftime('%d-%m-%y %H:%M:%S', time.localtime(k)), int(v[0]), v[1])) + rv.append( + (time.strftime("%d-%m-%y %H:%M:%S", time.localtime(k)), int(v[0]), v[1]) + ) return rv def setIdentityData(self, filters, data): - address = filters['address'] + address = filters["address"] ci = self.ci(Coins.PART) - ensure(ci.isValidAddress(address), 'Invalid identity address') + ensure(ci.isValidAddress(address), "Invalid identity address") try: now: int = self.getTime() session = self.openSession() - q = session.execute(text('SELECT COUNT(*) FROM knownidentities WHERE address = :address'), {'address': address}).first() + q = session.execute( + text("SELECT COUNT(*) FROM knownidentities WHERE address = :address"), + {"address": address}, + ).first() if q[0] < 1: - session.execute(text('INSERT INTO knownidentities (active_ind, address, created_at) VALUES (1, :address, :now)'), {'address': address, 'now': now}) + session.execute( + text( + "INSERT INTO knownidentities (active_ind, address, created_at) VALUES (1, :address, :now)" + ), + {"address": address, "now": now}, + ) - if 'label' in data: - session.execute(text('UPDATE knownidentities SET label = :label WHERE address = :address'), {'address': address, 'label': data['label']}) + if "label" in data: + session.execute( + text( + "UPDATE knownidentities SET label = :label WHERE address = :address" + ), + {"address": address, "label": data["label"]}, + ) - if 'automation_override' in data: + if "automation_override" in data: new_value: int = 0 - data_value = data['automation_override'] + data_value = data["automation_override"] if isinstance(data_value, int): new_value = data_value elif isinstance(data_value, str): if data_value.isdigit(): new_value = int(data_value) - elif data_value == 'default': + elif data_value == "default": new_value = 0 - elif data_value == 'always_accept': + elif data_value == "always_accept": new_value = int(AutomationOverrideOptions.ALWAYS_ACCEPT) - elif data_value == 'never_accept': + elif data_value == "never_accept": new_value = int(AutomationOverrideOptions.NEVER_ACCEPT) else: - raise ValueError('Unknown automation_override value') + raise ValueError("Unknown automation_override value") else: - raise ValueError('Unknown automation_override type') + raise ValueError("Unknown automation_override type") - session.execute(text('UPDATE knownidentities SET automation_override = :new_value WHERE address = :address'), {'address': address, 'new_value': new_value}) + session.execute( + text( + "UPDATE knownidentities SET automation_override = :new_value WHERE address = :address" + ), + {"address": address, "new_value": new_value}, + ) - if 'visibility_override' in data: + if "visibility_override" in data: new_value: int = 0 - data_value = data['visibility_override'] + data_value = data["visibility_override"] if isinstance(data_value, int): new_value = data_value elif isinstance(data_value, str): if data_value.isdigit(): new_value = int(data_value) - elif data_value == 'default': + elif data_value == "default": new_value = 0 - elif data_value == 'hide': + elif data_value == "hide": new_value = int(VisibilityOverrideOptions.HIDE) - elif data_value == 'block': + elif data_value == "block": new_value = int(VisibilityOverrideOptions.BLOCK) else: - raise ValueError('Unknown visibility_override value') + raise ValueError("Unknown visibility_override value") else: - raise ValueError('Unknown visibility_override type') + raise ValueError("Unknown visibility_override type") - session.execute(text('UPDATE knownidentities SET visibility_override = :new_value WHERE address = :address'), {'address': address, 'new_value': new_value}) + session.execute( + text( + "UPDATE knownidentities SET visibility_override = :new_value WHERE address = :address" + ), + {"address": address, "new_value": new_value}, + ) - if 'note' in data: - session.execute(text('UPDATE knownidentities SET note = :note WHERE address = :address'), {'address': address, 'note': data['note']}) + if "note" in data: + session.execute( + text( + "UPDATE knownidentities SET note = :note WHERE address = :address" + ), + {"address": address, "note": data["note"]}, + ) finally: self.closeSession(session) @@ -1519,42 +1935,44 @@ class BasicSwap(BaseApp): try: session = self.openSession() - query_str = 'SELECT address, label, num_sent_bids_successful, num_recv_bids_successful, ' + \ - ' num_sent_bids_rejected, num_recv_bids_rejected, num_sent_bids_failed, num_recv_bids_failed, ' + \ - ' automation_override, visibility_override, note ' + \ - ' FROM knownidentities ' + \ - ' WHERE active_ind = 1 ' + query_str = ( + "SELECT address, label, num_sent_bids_successful, num_recv_bids_successful, " + + " num_sent_bids_rejected, num_recv_bids_rejected, num_sent_bids_failed, num_recv_bids_failed, " + + " automation_override, visibility_override, note " + + " FROM knownidentities " + + " WHERE active_ind = 1 " + ) - address = filters.get('address', None) + address = filters.get("address", None) if address is not None: query_str += f' AND address = "{address}" ' - sort_dir = filters.get('sort_dir', 'DESC').upper() - sort_by = filters.get('sort_by', 'created_at') - query_str += f' ORDER BY {sort_by} {sort_dir}' + sort_dir = filters.get("sort_dir", "DESC").upper() + sort_by = filters.get("sort_by", "created_at") + query_str += f" ORDER BY {sort_by} {sort_dir}" - limit = filters.get('limit', None) + limit = filters.get("limit", None) if limit is not None: - query_str += f' LIMIT {limit}' - offset = filters.get('offset', None) + query_str += f" LIMIT {limit}" + offset = filters.get("offset", None) if offset is not None: - query_str += f' OFFSET {offset}' + query_str += f" OFFSET {offset}" q = session.execute(text(query_str)) rv = [] for row in q: identity = { - 'address': row[0], - 'label': row[1], - 'num_sent_bids_successful': zeroIfNone(row[2]), - 'num_recv_bids_successful': zeroIfNone(row[3]), - 'num_sent_bids_rejected': zeroIfNone(row[4]), - 'num_recv_bids_rejected': zeroIfNone(row[5]), - 'num_sent_bids_failed': zeroIfNone(row[6]), - 'num_recv_bids_failed': zeroIfNone(row[7]), - 'automation_override': zeroIfNone(row[8]), - 'visibility_override': zeroIfNone(row[9]), - 'note': row[10], + "address": row[0], + "label": row[1], + "num_sent_bids_successful": zeroIfNone(row[2]), + "num_recv_bids_successful": zeroIfNone(row[3]), + "num_sent_bids_rejected": zeroIfNone(row[4]), + "num_recv_bids_rejected": zeroIfNone(row[5]), + "num_sent_bids_failed": zeroIfNone(row[6]), + "num_recv_bids_failed": zeroIfNone(row[7]), + "automation_override": zeroIfNone(row[8]), + "visibility_override": zeroIfNone(row[9]), + "note": row[10], } rv.append(identity) return rv @@ -1564,104 +1982,147 @@ class BasicSwap(BaseApp): def vacuumDB(self): try: session = self.openSession() - return session.execute(text('VACUUM')) + return session.execute(text("VACUUM")) finally: self.closeSession(session) - def validateOfferAmounts(self, coin_from, coin_to, amount: int, amount_to: int, min_bid_amount: int) -> None: + def validateOfferAmounts( + self, coin_from, coin_to, amount: int, amount_to: int, min_bid_amount: int + ) -> None: ci_from = self.ci(coin_from) ci_to = self.ci(coin_to) - ensure(amount >= min_bid_amount, 'amount < min_bid_amount') - ensure(amount > ci_from.min_amount(), 'From amount below min value for chain') - ensure(amount < ci_from.max_amount(), 'From amount above max value for chain') + ensure(amount >= min_bid_amount, "amount < min_bid_amount") + ensure(amount > ci_from.min_amount(), "From amount below min value for chain") + ensure(amount < ci_from.max_amount(), "From amount above max value for chain") - ensure(amount_to > ci_to.min_amount(), 'To amount below min value for chain') - ensure(amount_to < ci_to.max_amount(), 'To amount above max value for chain') + ensure(amount_to > ci_to.min_amount(), "To amount below min value for chain") + ensure(amount_to < ci_to.max_amount(), "To amount above max value for chain") - def validateOfferLockValue(self, swap_type, coin_from, coin_to, lock_type, lock_value: int) -> None: - coin_from_has_csv = self.coin_clients[coin_from]['use_csv'] - coin_to_has_csv = self.coin_clients[coin_to]['use_csv'] + def validateOfferLockValue( + self, swap_type, coin_from, coin_to, lock_type, lock_value: int + ) -> None: + coin_from_has_csv = self.coin_clients[coin_from]["use_csv"] + coin_to_has_csv = self.coin_clients[coin_to]["use_csv"] if lock_type == TxLockTypes.SEQUENCE_LOCK_TIME: - ensure(lock_value >= self.min_sequence_lock_seconds and lock_value <= self.max_sequence_lock_seconds, 'Invalid lock_value time') + ensure( + lock_value >= self.min_sequence_lock_seconds + and lock_value <= self.max_sequence_lock_seconds, + "Invalid lock_value time", + ) if swap_type == SwapTypes.XMR_SWAP: reverse_bid: bool = self.is_reverse_ads_bid(coin_from, coin_to) itx_coin_has_csv = coin_to_has_csv if reverse_bid else coin_from_has_csv - ensure(itx_coin_has_csv, 'ITX coin needs CSV activated.') + ensure(itx_coin_has_csv, "ITX coin needs CSV activated.") else: - ensure(coin_from_has_csv and coin_to_has_csv, 'Both coins need CSV activated.') + ensure( + coin_from_has_csv and coin_to_has_csv, + "Both coins need CSV activated.", + ) elif lock_type == TxLockTypes.SEQUENCE_LOCK_BLOCKS: - ensure(lock_value >= 5 and lock_value <= 1000, 'Invalid lock_value blocks') + ensure(lock_value >= 5 and lock_value <= 1000, "Invalid lock_value blocks") if swap_type == SwapTypes.XMR_SWAP: reverse_bid: bool = self.is_reverse_ads_bid(coin_from, coin_to) itx_coin_has_csv = coin_to_has_csv if reverse_bid else coin_from_has_csv - ensure(itx_coin_has_csv, 'ITX coin needs CSV activated.') + ensure(itx_coin_has_csv, "ITX coin needs CSV activated.") else: - ensure(coin_from_has_csv and coin_to_has_csv, 'Both coins need CSV activated.') + ensure( + coin_from_has_csv and coin_to_has_csv, + "Both coins need CSV activated.", + ) elif lock_type == TxLockTypes.ABS_LOCK_TIME: # TODO: range? - ensure(not coin_from_has_csv or not coin_to_has_csv, 'Should use CSV.') - ensure(lock_value >= 4 * 60 * 60 and lock_value <= 96 * 60 * 60, 'Invalid lock_value time') + ensure(not coin_from_has_csv or not coin_to_has_csv, "Should use CSV.") + ensure( + lock_value >= 4 * 60 * 60 and lock_value <= 96 * 60 * 60, + "Invalid lock_value time", + ) elif lock_type == TxLockTypes.ABS_LOCK_BLOCKS: # TODO: range? - ensure(not coin_from_has_csv or not coin_to_has_csv, 'Should use CSV.') - ensure(lock_value >= 10 and lock_value <= 1000, 'Invalid lock_value blocks') + ensure(not coin_from_has_csv or not coin_to_has_csv, "Should use CSV.") + ensure(lock_value >= 10 and lock_value <= 1000, "Invalid lock_value blocks") else: - raise ValueError('Unknown locktype') + raise ValueError("Unknown locktype") - def validateOfferValidTime(self, offer_type, coin_from, coin_to, valid_for_seconds: int) -> None: + def validateOfferValidTime( + self, offer_type, coin_from, coin_to, valid_for_seconds: int + ) -> None: # TODO: adjust if valid_for_seconds < 10 * 60: - raise ValueError('Offer TTL too low') + raise ValueError("Offer TTL too low") if valid_for_seconds > 48 * 60 * 60: - raise ValueError('Offer TTL too high') + raise ValueError("Offer TTL too high") - def validateBidValidTime(self, offer_type, coin_from, coin_to, valid_for_seconds: int) -> None: + def validateBidValidTime( + self, offer_type, coin_from, coin_to, valid_for_seconds: int + ) -> None: # TODO: adjust if valid_for_seconds < 10 * 60: - raise ValueError('Bid TTL too low') + raise ValueError("Bid TTL too low") if valid_for_seconds > 24 * 60 * 60: - raise ValueError('Bid TTL too high') + raise ValueError("Bid TTL too high") def validateBidAmount(self, offer, bid_amount: int, bid_rate: int) -> None: - ensure(bid_amount >= offer.min_bid_amount, 'Bid amount below minimum') - ensure(bid_amount <= offer.amount_from, 'Bid amount above offer amount') + ensure(bid_amount >= offer.min_bid_amount, "Bid amount below minimum") + ensure(bid_amount <= offer.amount_from, "Bid amount above offer amount") if not offer.amount_negotiable: - ensure(offer.amount_from == bid_amount, 'Bid amount must match offer amount.') + ensure( + offer.amount_from == bid_amount, "Bid amount must match offer amount." + ) if not offer.rate_negotiable: - ensure(offer.rate == bid_rate, 'Bid rate must match offer rate.') + ensure(offer.rate == bid_rate, "Bid rate must match offer rate.") def getOfferAddressTo(self, extra_options) -> str: - if 'addr_send_to' in extra_options: - return extra_options['addr_send_to'] + if "addr_send_to" in extra_options: + return extra_options["addr_send_to"] return self.network_addr - def postOffer(self, coin_from, coin_to, amount: int, rate: int, min_bid_amount: int, swap_type, - lock_type=TxLockTypes.SEQUENCE_LOCK_TIME, lock_value: int = 48 * 60 * 60, auto_accept_bids: bool = False, addr_send_from: str = None, extra_options={}) -> bytes: + def postOffer( + self, + coin_from, + coin_to, + amount: int, + rate: int, + min_bid_amount: int, + swap_type, + lock_type=TxLockTypes.SEQUENCE_LOCK_TIME, + lock_value: int = 48 * 60 * 60, + auto_accept_bids: bool = False, + addr_send_from: str = None, + extra_options={}, + ) -> bytes: # Offer to send offer.amount_from of coin_from in exchange for offer.amount_from * offer.rate of coin_to - ensure(coin_from != coin_to, 'coin_from == coin_to') + ensure(coin_from != coin_to, "coin_from == coin_to") try: coin_from_t = Coins(coin_from) ci_from = self.ci(coin_from_t) except Exception: - raise ValueError('Unknown coin from type') + raise ValueError("Unknown coin from type") try: coin_to_t = Coins(coin_to) ci_to = self.ci(coin_to_t) except Exception: - raise ValueError('Unknown coin to type') + raise ValueError("Unknown coin to type") - valid_for_seconds: int = extra_options.get('valid_for_seconds', 60 * 60) - amount_to: int = extra_options.get('amount_to', int((amount * rate) // ci_from.COIN())) + valid_for_seconds: int = extra_options.get("valid_for_seconds", 60 * 60) + amount_to: int = extra_options.get( + "amount_to", int((amount * rate) // ci_from.COIN()) + ) # Recalculate the rate so it will match the bid rate rate = ci_from.make_int(amount_to / amount, r=1) self.validateSwapType(coin_from_t, coin_to_t, swap_type) - self.validateOfferAmounts(coin_from_t, coin_to_t, amount, amount_to, min_bid_amount) - self.validateOfferLockValue(swap_type, coin_from_t, coin_to_t, lock_type, lock_value) - self.validateOfferValidTime(swap_type, coin_from_t, coin_to_t, valid_for_seconds) + self.validateOfferAmounts( + coin_from_t, coin_to_t, amount, amount_to, min_bid_amount + ) + self.validateOfferLockValue( + swap_type, coin_from_t, coin_to_t, lock_type, lock_value + ) + self.validateOfferValidTime( + swap_type, coin_from_t, coin_to_t, valid_for_seconds + ) offer_addr_to = self.getOfferAddressTo(extra_options) @@ -1670,13 +2131,19 @@ class BasicSwap(BaseApp): try: session = self.openSession() self.checkCoinsReady(coin_from_t, coin_to_t) - offer_addr = self.prepareSMSGAddress(addr_send_from, AddressTypes.OFFER, session) + offer_addr = self.prepareSMSGAddress( + addr_send_from, AddressTypes.OFFER, session + ) offer_created_at = self.getTime() msg_buf = OfferMessage() - msg_buf.protocol_version = PROTOCOL_VERSION_ADAPTOR_SIG if swap_type == SwapTypes.XMR_SWAP else PROTOCOL_VERSION_SECRET_HASH + msg_buf.protocol_version = ( + PROTOCOL_VERSION_ADAPTOR_SIG + if swap_type == SwapTypes.XMR_SWAP + else PROTOCOL_VERSION_SECRET_HASH + ) msg_buf.coin_from = int(coin_from) msg_buf.coin_to = int(coin_to) msg_buf.amount_from = int(amount) @@ -1687,71 +2154,92 @@ class BasicSwap(BaseApp): msg_buf.lock_type = lock_type msg_buf.lock_value = lock_value msg_buf.swap_type = swap_type - msg_buf.amount_negotiable = extra_options.get('amount_negotiable', False) - msg_buf.rate_negotiable = extra_options.get('rate_negotiable', False) + msg_buf.amount_negotiable = extra_options.get("amount_negotiable", False) + msg_buf.rate_negotiable = extra_options.get("rate_negotiable", False) if msg_buf.amount_negotiable or msg_buf.rate_negotiable: - ensure(auto_accept_bids is False, 'Auto-accept unavailable when amount or rate are variable') + ensure( + auto_accept_bids is False, + "Auto-accept unavailable when amount or rate are variable", + ) - if 'from_fee_override' in extra_options: - msg_buf.fee_rate_from = make_int(extra_options['from_fee_override'], self.ci(coin_from).exp()) + if "from_fee_override" in extra_options: + msg_buf.fee_rate_from = make_int( + extra_options["from_fee_override"], self.ci(coin_from).exp() + ) else: # TODO: conf_target = ci_from.settings.get('conf_target', 2) conf_target = 2 - if 'from_fee_conf_target' in extra_options: - conf_target = extra_options['from_fee_conf_target'] + if "from_fee_conf_target" in extra_options: + conf_target = extra_options["from_fee_conf_target"] fee_rate, fee_src = self.getFeeRateForCoin(coin_from, conf_target) - if 'from_fee_multiplier_percent' in extra_options: - fee_rate *= extra_options['fee_multiplier'] / 100.0 + if "from_fee_multiplier_percent" in extra_options: + fee_rate *= extra_options["fee_multiplier"] / 100.0 msg_buf.fee_rate_from = make_int(fee_rate, self.ci(coin_from).exp()) - if 'to_fee_override' in extra_options: - msg_buf.fee_rate_to = make_int(extra_options['to_fee_override'], self.ci(coin_to).exp()) + if "to_fee_override" in extra_options: + msg_buf.fee_rate_to = make_int( + extra_options["to_fee_override"], self.ci(coin_to).exp() + ) else: # TODO: conf_target = ci_to.settings.get('conf_target', 2) conf_target = 2 - if 'to_fee_conf_target' in extra_options: - conf_target = extra_options['to_fee_conf_target'] + if "to_fee_conf_target" in extra_options: + conf_target = extra_options["to_fee_conf_target"] fee_rate, fee_src = self.getFeeRateForCoin(coin_to, conf_target) - if 'to_fee_multiplier_percent' in extra_options: - fee_rate *= extra_options['fee_multiplier'] / 100.0 + if "to_fee_multiplier_percent" in extra_options: + fee_rate *= extra_options["fee_multiplier"] / 100.0 msg_buf.fee_rate_to = make_int(fee_rate, self.ci(coin_to).exp()) if swap_type == SwapTypes.XMR_SWAP: xmr_offer = XmrOffer() chain_a_ci = ci_to if reverse_bid else ci_from - lock_value_2 = lock_value + 1000 if (None, DebugTypes.OFFER_LOCK_2_VALUE_INC) in self._debug_cases else lock_value + lock_value_2 = ( + lock_value + 1000 + if (None, DebugTypes.OFFER_LOCK_2_VALUE_INC) in self._debug_cases + else lock_value + ) # Delay before the chain a lock refund tx can be mined - xmr_offer.lock_time_1 = chain_a_ci.getExpectedSequence(lock_type, lock_value) + xmr_offer.lock_time_1 = chain_a_ci.getExpectedSequence( + lock_type, lock_value + ) # Delay before the follower can spend from the chain a lock refund tx - xmr_offer.lock_time_2 = chain_a_ci.getExpectedSequence(lock_type, lock_value_2) + xmr_offer.lock_time_2 = chain_a_ci.getExpectedSequence( + lock_type, lock_value_2 + ) xmr_offer.a_fee_rate = msg_buf.fee_rate_from - xmr_offer.b_fee_rate = msg_buf.fee_rate_to # Unused: TODO - Set priority? + xmr_offer.b_fee_rate = ( + msg_buf.fee_rate_to + ) # Unused: TODO - Set priority? if coin_from in self.scriptless_coins: ci_from.ensureFunds(msg_buf.amount_from) else: proof_of_funds_hash = getOfferProofOfFundsHash(msg_buf, offer_addr) - proof_addr, proof_sig, proof_utxos = self.getProofOfFunds(coin_from_t, int(amount), proof_of_funds_hash) + proof_addr, proof_sig, proof_utxos = self.getProofOfFunds( + coin_from_t, int(amount), proof_of_funds_hash + ) # TODO: For now proof_of_funds is just a client side check, may need to be sent with offers in future however. offer_bytes = msg_buf.to_bytes() - payload_hex = str.format('{:02x}', MessageTypes.OFFER) + offer_bytes.hex() + payload_hex = str.format("{:02x}", MessageTypes.OFFER) + offer_bytes.hex() msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, valid_for_seconds) offer_id = self.sendSmsg(offer_addr, offer_addr_to, payload_hex, msg_valid) - security_token = extra_options.get('security_token', None) + security_token = extra_options.get("security_token", None) if security_token is not None and len(security_token) != 20: - raise ValueError('Security token must be 20 bytes long.') + raise ValueError("Security token must be 20 bytes long.") - bid_reversed: bool = msg_buf.swap_type == SwapTypes.XMR_SWAP and self.is_reverse_ads_bid(msg_buf.coin_from, msg_buf.coin_to) + bid_reversed: bool = ( + msg_buf.swap_type == SwapTypes.XMR_SWAP + and self.is_reverse_ads_bid(msg_buf.coin_from, msg_buf.coin_to) + ) offer = Offer( offer_id=offer_id, active_ind=1, protocol_version=msg_buf.protocol_version, - coin_from=msg_buf.coin_from, coin_to=msg_buf.coin_to, amount_from=msg_buf.amount_from, @@ -1764,21 +2252,21 @@ class BasicSwap(BaseApp): swap_type=msg_buf.swap_type, amount_negotiable=msg_buf.amount_negotiable, rate_negotiable=msg_buf.rate_negotiable, - addr_to=offer_addr_to, addr_from=offer_addr, created_at=offer_created_at, expire_at=offer_created_at + msg_buf.time_valid, was_sent=True, bid_reversed=bid_reversed, - security_token=security_token) + security_token=security_token, + ) offer.setState(OfferStates.OFFER_SENT) if swap_type == SwapTypes.XMR_SWAP: xmr_offer.offer_id = offer_id session.add(xmr_offer) - automation_id = extra_options.get('automation_id', -1) + automation_id = extra_options.get("automation_id", -1) if automation_id == -1 and auto_accept_bids: # Use default strategy automation_id = 1 @@ -1790,73 +2278,88 @@ class BasicSwap(BaseApp): strategy_id=automation_id, created_at=offer_created_at, repeat_limit=1, - repeat_count=0) + repeat_count=0, + ) session.add(auto_link) - if 'prefunded_itx' in extra_options: + if "prefunded_itx" in extra_options: prefunded_tx = PrefundedTx( active_ind=1, created_at=offer_created_at, linked_type=Concepts.OFFER, linked_id=offer_id, tx_type=TxTypes.ITX_PRE_FUNDED, - tx_data=extra_options['prefunded_itx']) + tx_data=extra_options["prefunded_itx"], + ) session.add(prefunded_tx) session.add(offer) session.add(SentOffer(offer_id=offer_id)) finally: self.closeSession(session) - self.log.info('Sent OFFER %s', offer_id.hex()) + self.log.info("Sent OFFER %s", offer_id.hex()) return offer_id def revokeOffer(self, offer_id, security_token=None) -> None: - self.log.info('Revoking offer %s', offer_id.hex()) + self.log.info("Revoking offer %s", offer_id.hex()) session = self.openSession() try: offer = session.query(Offer).filter_by(offer_id=offer_id).first() - if offer.security_token is not None and offer.security_token != security_token: - raise ValueError('Mismatched security token') + if ( + offer.security_token is not None + and offer.security_token != security_token + ): + raise ValueError("Mismatched security token") msg_buf = OfferRevokeMessage() msg_buf.offer_msg_id = offer_id - signature_enc = self.callcoinrpc(Coins.PART, 'signmessage', [offer.addr_from, offer_id.hex() + '_revoke']) + signature_enc = self.callcoinrpc( + Coins.PART, "signmessage", [offer.addr_from, offer_id.hex() + "_revoke"] + ) msg_buf.signature = base64.b64decode(signature_enc) msg_bytes = msg_buf.to_bytes() - payload_hex = str.format('{:02x}', MessageTypes.OFFER_REVOKE) + msg_bytes.hex() + payload_hex = ( + str.format("{:02x}", MessageTypes.OFFER_REVOKE) + msg_bytes.hex() + ) msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, offer.time_valid) - msg_id = self.sendSmsg(offer.addr_from, self.network_addr, payload_hex, msg_valid) - self.log.debug('Revoked offer %s in msg %s', offer_id.hex(), msg_id.hex()) + msg_id = self.sendSmsg( + offer.addr_from, self.network_addr, payload_hex, msg_valid + ) + self.log.debug("Revoked offer %s in msg %s", offer_id.hex(), msg_id.hex()) finally: self.closeSession(session, commit=False) def archiveOffer(self, offer_id) -> None: - self.log.info('Archiving offer %s', offer_id.hex()) + self.log.info("Archiving offer %s", offer_id.hex()) session = self.openSession() try: offer = session.query(Offer).filter_by(offer_id=offer_id).first() if offer.active_ind != 1: - raise ValueError('Offer is not active') + raise ValueError("Offer is not active") offer.active_ind = 3 finally: self.closeSession(session) def editOffer(self, offer_id, data) -> None: - self.log.info('Editing offer %s', offer_id.hex()) + self.log.info("Editing offer %s", offer_id.hex()) session = self.openSession() try: offer = session.query(Offer).filter_by(offer_id=offer_id).first() - if 'automation_strat_id' in data: - new_automation_strat_id = data['automation_strat_id'] - link = session.query(AutomationLink).filter_by(linked_type=Concepts.OFFER, linked_id=offer.offer_id).first() + if "automation_strat_id" in data: + new_automation_strat_id = data["automation_strat_id"] + link = ( + session.query(AutomationLink) + .filter_by(linked_type=Concepts.OFFER, linked_id=offer.offer_id) + .first() + ) if not link: if new_automation_strat_id > 0: link = AutomationLink( @@ -1864,7 +2367,8 @@ class BasicSwap(BaseApp): linked_type=Concepts.OFFER, linked_id=offer_id, strategy_id=new_automation_strat_id, - created_at=self.getTime()) + created_at=self.getTime(), + ) session.add(link) else: if new_automation_strat_id < 1: @@ -1880,74 +2384,124 @@ class BasicSwap(BaseApp): ci = self.ci(coin_type) nonce = 1 while True: - key_path = key_path_base + '/{}'.format(nonce) - extkey = self.callcoinrpc(Coins.PART, 'extkey', ['info', evkey, key_path])['key_info']['result'] - privkey = decodeWif(self.callcoinrpc(Coins.PART, 'extkey', ['info', extkey])['key_info']['privkey']) + key_path = key_path_base + "/{}".format(nonce) + extkey = self.callcoinrpc(Coins.PART, "extkey", ["info", evkey, key_path])[ + "key_info" + ]["result"] + privkey = decodeWif( + self.callcoinrpc(Coins.PART, "extkey", ["info", extkey])["key_info"][ + "privkey" + ] + ) if ci.verifyKey(privkey): return privkey nonce += 1 if nonce > 1000: - raise ValueError('grindForEd25519Key failed') + raise ValueError("grindForEd25519Key failed") def getWalletKey(self, coin_type, key_num, for_ed25519=False) -> bytes: - evkey = self.callcoinrpc(Coins.PART, 'extkey', ['account', 'default', 'true'])['evkey'] + evkey = self.callcoinrpc(Coins.PART, "extkey", ["account", "default", "true"])[ + "evkey" + ] - key_path_base = '44445555h/1h/{}/{}'.format(int(coin_type), key_num) + key_path_base = "44445555h/1h/{}/{}".format(int(coin_type), key_num) if not for_ed25519: - extkey = self.callcoinrpc(Coins.PART, 'extkey', ['info', evkey, key_path_base])['key_info']['result'] - return decodeWif(self.callcoinrpc(Coins.PART, 'extkey', ['info', extkey])['key_info']['privkey']) + extkey = self.callcoinrpc( + Coins.PART, "extkey", ["info", evkey, key_path_base] + )["key_info"]["result"] + return decodeWif( + self.callcoinrpc(Coins.PART, "extkey", ["info", extkey])["key_info"][ + "privkey" + ] + ) return self.grindForEd25519Key(coin_type, evkey, key_path_base) - def getPathKey(self, coin_from, coin_to, bid_created_at: int, contract_count: int, key_no: int, for_ed25519: bool = False) -> bytes: - evkey = self.callcoinrpc(Coins.PART, 'extkey', ['account', 'default', 'true'])['evkey'] - ci = self.ci(coin_to) + def getPathKey( + self, + coin_from, + coin_to, + bid_created_at: int, + contract_count: int, + key_no: int, + for_ed25519: bool = False, + ) -> bytes: + evkey = self.callcoinrpc(Coins.PART, "extkey", ["account", "default", "true"])[ + "evkey" + ] days = bid_created_at // 86400 secs = bid_created_at - days * 86400 - key_path_base = '44445555h/999999/{}/{}/{}/{}/{}/{}'.format(int(coin_from), int(coin_to), days, secs, contract_count, key_no) + key_path_base = "44445555h/999999/{}/{}/{}/{}/{}/{}".format( + int(coin_from), int(coin_to), days, secs, contract_count, key_no + ) if not for_ed25519: - extkey = self.callcoinrpc(Coins.PART, 'extkey', ['info', evkey, key_path_base])['key_info']['result'] - return decodeWif(self.callcoinrpc(Coins.PART, 'extkey', ['info', extkey])['key_info']['privkey']) + extkey = self.callcoinrpc( + Coins.PART, "extkey", ["info", evkey, key_path_base] + )["key_info"]["result"] + return decodeWif( + self.callcoinrpc(Coins.PART, "extkey", ["info", extkey])["key_info"][ + "privkey" + ] + ) return self.grindForEd25519Key(coin_to, evkey, key_path_base) def getNetworkKey(self, key_num): - evkey = self.callcoinrpc(Coins.PART, 'extkey', ['account', 'default', 'true'])['evkey'] + evkey = self.callcoinrpc(Coins.PART, "extkey", ["account", "default", "true"])[ + "evkey" + ] - key_path = '44445556h/1h/{}'.format(int(key_num)) + key_path = "44445556h/1h/{}".format(int(key_num)) - extkey = self.callcoinrpc(Coins.PART, 'extkey', ['info', evkey, key_path])['key_info']['result'] - return decodeWif(self.callcoinrpc(Coins.PART, 'extkey', ['info', extkey])['key_info']['privkey']) + extkey = self.callcoinrpc(Coins.PART, "extkey", ["info", evkey, key_path])[ + "key_info" + ]["result"] + return decodeWif( + self.callcoinrpc(Coins.PART, "extkey", ["info", extkey])["key_info"][ + "privkey" + ] + ) def getContractPubkey(self, date, contract_count): - account = self.callcoinrpc(Coins.PART, 'extkey', ['account']) # Derive an address to use for a contract - evkey = self.callcoinrpc(Coins.PART, 'extkey', ['account', 'default', 'true'])['evkey'] + evkey = self.callcoinrpc(Coins.PART, "extkey", ["account", "default", "true"])[ + "evkey" + ] # Should the coin path be included? - path = '44445555h' - path += '/' + str(date.year) + '/' + str(date.month) + '/' + str(date.day) - path += '/' + str(contract_count) + path = "44445555h" + path += "/" + str(date.year) + "/" + str(date.month) + "/" + str(date.day) + path += "/" + str(contract_count) - extkey = self.callcoinrpc(Coins.PART, 'extkey', ['info', evkey, path])['key_info']['result'] - pubkey = self.callcoinrpc(Coins.PART, 'extkey', ['info', extkey])['key_info']['pubkey'] + extkey = self.callcoinrpc(Coins.PART, "extkey", ["info", evkey, path])[ + "key_info" + ]["result"] + pubkey = self.callcoinrpc(Coins.PART, "extkey", ["info", extkey])["key_info"][ + "pubkey" + ] return bytes.fromhex(pubkey) def getContractPrivkey(self, date: dt.datetime, contract_count: int) -> bytes: # Derive an address to use for a contract - evkey = self.callcoinrpc(Coins.PART, 'extkey', ['account', 'default', 'true'])['evkey'] + evkey = self.callcoinrpc(Coins.PART, "extkey", ["account", "default", "true"])[ + "evkey" + ] - path = '44445555h' - path += '/' + str(date.year) + '/' + str(date.month) + '/' + str(date.day) - path += '/' + str(contract_count) + path = "44445555h" + path += "/" + str(date.year) + "/" + str(date.month) + "/" + str(date.day) + path += "/" + str(contract_count) - extkey = self.callcoinrpc(Coins.PART, 'extkey', ['info', evkey, path])['key_info']['result'] - privkey = self.callcoinrpc(Coins.PART, 'extkey', ['info', extkey])['key_info']['privkey'] + extkey = self.callcoinrpc(Coins.PART, "extkey", ["info", evkey, path])[ + "key_info" + ]["result"] + privkey = self.callcoinrpc(Coins.PART, "extkey", ["info", extkey])["key_info"][ + "privkey" + ] raw = decodeAddress(privkey)[1:] if len(raw) > 32: raw = raw[:32] @@ -1955,28 +2509,53 @@ class BasicSwap(BaseApp): def getContractSecret(self, date: dt.datetime, contract_count: int) -> bytes: # Derive a key to use for a contract secret - evkey = self.callcoinrpc(Coins.PART, 'extkey', ['account', 'default', 'true'])['evkey'] + evkey = self.callcoinrpc(Coins.PART, "extkey", ["account", "default", "true"])[ + "evkey" + ] - path = '44445555h/99999' - path += '/' + str(date.year) + '/' + str(date.month) + '/' + str(date.day) - path += '/' + str(contract_count) + path = "44445555h/99999" + path += "/" + str(date.year) + "/" + str(date.month) + "/" + str(date.day) + path += "/" + str(contract_count) - return sha256(bytes(self.callcoinrpc(Coins.PART, 'extkey', ['info', evkey, path])['key_info']['result'], 'utf-8')) + return sha256( + bytes( + self.callcoinrpc(Coins.PART, "extkey", ["info", evkey, path])[ + "key_info" + ]["result"], + "utf-8", + ) + ) - def getReceiveAddressFromPool(self, coin_type, bid_id: bytes, tx_type, session=None): - self.log.debug('Get address from pool bid_id {}, type {}, coin {}'.format(bid_id.hex(), tx_type, coin_type)) + def getReceiveAddressFromPool( + self, coin_type, bid_id: bytes, tx_type, session=None + ): + self.log.debug( + "Get address from pool bid_id {}, type {}, coin {}".format( + bid_id.hex(), tx_type, coin_type + ) + ) try: use_session = self.openSession(session) - record = use_session.query(PooledAddress).filter(sa.and_(PooledAddress.coin_type == int(coin_type), PooledAddress.bid_id == None)).first() # noqa: E712,E711 + record = ( + use_session.query(PooledAddress) + .filter( + sa.and_( + PooledAddress.coin_type == int(coin_type), + PooledAddress.bid_id == None, # noqa: E711 + ) + ) + .first() + ) if not record: address = self.getReceiveAddressForCoin(coin_type) - record = PooledAddress( - addr=address, - coin_type=int(coin_type)) + record = PooledAddress(addr=address, coin_type=int(coin_type)) record.bid_id = bid_id record.tx_type = tx_type addr = record.addr - ensure(self.ci(coin_type).isAddressMine(addr), 'Pool address not owned by wallet!') + ensure( + self.ci(coin_type).isAddressMine(addr), + "Pool address not owned by wallet!", + ) use_session.add(record) use_session.commit() finally: @@ -1985,22 +2564,37 @@ class BasicSwap(BaseApp): return addr def returnAddressToPool(self, bid_id: bytes, tx_type): - self.log.debug('Return address to pool bid_id {}, type {}'.format(bid_id.hex(), tx_type)) + self.log.debug( + "Return address to pool bid_id {}, type {}".format(bid_id.hex(), tx_type) + ) try: session = self.openSession() try: - record = session.query(PooledAddress).filter(sa.and_(PooledAddress.bid_id == bid_id, PooledAddress.tx_type == tx_type)).one() - self.log.debug('Returning address to pool addr {}'.format(record.addr)) + record = ( + session.query(PooledAddress) + .filter( + sa.and_( + PooledAddress.bid_id == bid_id, + PooledAddress.tx_type == tx_type, + ) + ) + .one() + ) + self.log.debug("Returning address to pool addr {}".format(record.addr)) record.bid_id = None session.commit() - except Exception as ex: + except Exception as e: # noqa: F841 pass finally: self.closeSession(session, commit=False) def getReceiveAddressForCoin(self, coin_type): - new_addr = self.ci(coin_type).getNewAddress(self.coin_clients[coin_type]['use_segwit']) - self.log.debug('Generated new receive address %s for %s', new_addr, Coins(coin_type).name) + new_addr = self.ci(coin_type).getNewAddress( + self.coin_clients[coin_type]["use_segwit"] + ) + self.log.debug( + "Generated new receive address %s for %s", new_addr, Coins(coin_type).name + ) return new_addr def getFeeRateForCoin(self, coin_type, conf_target: int = 2): @@ -2017,48 +2611,64 @@ class BasicSwap(BaseApp): def withdrawCoin(self, coin_type, value, addr_to, subfee: bool) -> str: ci = self.ci(coin_type) if subfee and coin_type in (Coins.XMR, Coins.WOW): - self.log.info('withdrawCoin sweep all {} to {}'.format(ci.ticker(), addr_to)) + self.log.info( + "withdrawCoin sweep all {} to {}".format(ci.ticker(), addr_to) + ) else: - self.log.info('withdrawCoin {} {} to {} {}'.format(value, ci.ticker(), addr_to, ' subfee' if subfee else '')) + self.log.info( + "withdrawCoin {} {} to {} {}".format( + value, ci.ticker(), addr_to, " subfee" if subfee else "" + ) + ) txid = ci.withdrawCoin(value, addr_to, subfee) - self.log.debug('In txn: {}'.format(txid)) + self.log.debug("In txn: {}".format(txid)) return txid def withdrawLTC(self, type_from, value, addr_to, subfee: bool) -> str: ci = self.ci(Coins.LTC) - self.log.info('withdrawLTC {} {} to {} {}'.format(value, type_from, addr_to, ' subfee' if subfee else '')) + self.log.info( + "withdrawLTC {} {} to {} {}".format( + value, type_from, addr_to, " subfee" if subfee else "" + ) + ) txid = ci.withdrawCoin(value, type_from, addr_to, subfee) - self.log.debug('In txn: {}'.format(txid)) + self.log.debug("In txn: {}".format(txid)) return txid - def withdrawParticl(self, type_from: str, type_to: str, value, addr_to: str, subfee: bool) -> str: - self.log.info('withdrawParticl {} {} to {} {} {}'.format(value, type_from, type_to, addr_to, ' subfee' if subfee else '')) + def withdrawParticl( + self, type_from: str, type_to: str, value, addr_to: str, subfee: bool + ) -> str: + self.log.info( + "withdrawParticl {} {} to {} {} {}".format( + value, type_from, type_to, addr_to, " subfee" if subfee else "" + ) + ) - if type_from == 'plain': - type_from = 'part' - if type_to == 'plain': - type_to = 'part' + if type_from == "plain": + type_from = "part" + if type_to == "plain": + type_to = "part" ci = self.ci(Coins.PART) txid = ci.sendTypeTo(type_from, type_to, value, addr_to, subfee) - self.log.debug('In txn: {}'.format(txid)) + self.log.debug("In txn: {}".format(txid)) return txid def cacheNewAddressForCoin(self, coin_type, session=None): - self.log.debug('cacheNewAddressForCoin %s', Coins(coin_type).name) - key_str = 'receive_addr_' + self.ci(coin_type).coin_name().lower() + self.log.debug("cacheNewAddressForCoin %s", Coins(coin_type).name) + key_str = "receive_addr_" + self.ci(coin_type).coin_name().lower() addr = self.getReceiveAddressForCoin(coin_type) self.setStringKV(key_str, addr, session) return addr def getCachedMainWalletAddress(self, ci, session=None): - db_key = 'main_wallet_addr_' + ci.coin_name().lower() + db_key = "main_wallet_addr_" + ci.coin_name().lower() cached_addr = self.getStringKV(db_key, session) if cached_addr is not None: return cached_addr - self.log.warning(f'Setting {db_key}') + self.log.warning(f"Setting {db_key}") main_address = ci.getMainWalletAddress() self.setStringKV(db_key, main_address, session) return main_address @@ -2066,46 +2676,68 @@ class BasicSwap(BaseApp): def checkWalletSeed(self, c) -> bool: ci = self.ci(c) if c == Coins.PART: - ci.setWalletSeedWarning(False) # All keys should be be derived from the Particl mnemonic + ci.setWalletSeedWarning( + False + ) # All keys should be be derived from the Particl mnemonic return True # TODO if c in (Coins.XMR, Coins.WOW): expect_address = self.getCachedMainWalletAddress(ci) if expect_address is None: - self.log.warning('Can\'t find expected main wallet address for coin {}'.format(ci.coin_name())) + self.log.warning( + "Can't find expected main wallet address for coin {}".format( + ci.coin_name() + ) + ) return False ci._have_checked_seed = True wallet_address: str = ci.getMainWalletAddress() if expect_address == wallet_address: ci.setWalletSeedWarning(False) return True - self.log.warning('Wallet for coin {} not derived from swap seed.\n Expected {}\n Have {}'.format(ci.coin_name(), expect_address, wallet_address)) + self.log.warning( + "Wallet for coin {} not derived from swap seed.\n Expected {}\n Have {}".format( + ci.coin_name(), expect_address, wallet_address + ) + ) return False - expect_seedid = self.getStringKV('main_wallet_seedid_' + ci.coin_name().lower()) + expect_seedid = self.getStringKV("main_wallet_seedid_" + ci.coin_name().lower()) if expect_seedid is None: - self.log.warning('Can\'t find expected wallet seed id for coin {}'.format(ci.coin_name())) + self.log.warning( + "Can't find expected wallet seed id for coin {}".format(ci.coin_name()) + ) return False - if c == Coins.BTC and len(ci.rpc('listwallets')) < 1: - self.log.warning('Missing wallet for coin {}'.format(ci.coin_name())) + if c == Coins.BTC and len(ci.rpc("listwallets")) < 1: + self.log.warning("Missing wallet for coin {}".format(ci.coin_name())) return False if ci.checkExpectedSeed(expect_seedid): ci.setWalletSeedWarning(False) return True if c == Coins.DCR: # Try the legacy extkey - expect_seedid = self.getStringKV('main_wallet_seedid_alt_' + ci.coin_name().lower()) + expect_seedid = self.getStringKV( + "main_wallet_seedid_alt_" + ci.coin_name().lower() + ) if ci.checkExpectedSeed(expect_seedid): ci.setWalletSeedWarning(False) - self.log.warning('{} is using the legacy extkey.'.format(ci.coin_name())) + self.log.warning( + "{} is using the legacy extkey.".format(ci.coin_name()) + ) return True - self.log.warning('Wallet for coin {} not derived from swap seed.'.format(ci.coin_name())) + self.log.warning( + "Wallet for coin {} not derived from swap seed.".format(ci.coin_name()) + ) return False def reseedWallet(self, coin_type): - self.log.info('reseedWallet %s', coin_type) + self.log.info("reseedWallet %s", coin_type) ci = self.ci(coin_type) if ci.knownWalletSeed(): - raise ValueError('{} wallet seed is already derived from the particl mnemonic'.format(ci.coin_name())) + raise ValueError( + "{} wallet seed is already derived from the particl mnemonic".format( + ci.coin_name() + ) + ) self.initialiseWallet(coin_type, raise_errors=True) @@ -2113,80 +2745,77 @@ class BasicSwap(BaseApp): if not self.checkWalletSeed(coin_type): if coin_type in (Coins.XMR, Coins.WOW): - raise ValueError('TODO: How to reseed XMR wallet?') + raise ValueError("TODO: How to reseed XMR wallet?") else: - raise ValueError('Wallet seed doesn\'t match expected.') + raise ValueError("Wallet seed doesn't match expected.") def getCachedAddressForCoin(self, coin_type, session=None): - self.log.debug('getCachedAddressForCoin %s', Coins(coin_type).name) + self.log.debug("getCachedAddressForCoin %s", Coins(coin_type).name) # TODO: auto refresh after used ci = self.ci(coin_type) - key_str = 'receive_addr_' + ci.coin_name().lower() + key_str = "receive_addr_" + ci.coin_name().lower() use_session = self.openSession(session) try: try: - addr = use_session.query(DBKVString).filter_by(key=key_str).first().value + addr = ( + use_session.query(DBKVString).filter_by(key=key_str).first().value + ) except Exception: addr = self.getReceiveAddressForCoin(coin_type) - use_session.add(DBKVString( - key=key_str, - value=addr - )) + use_session.add(DBKVString(key=key_str, value=addr)) finally: if session is None: self.closeSession(use_session) return addr def cacheNewStealthAddressForCoin(self, coin_type): - self.log.debug('cacheNewStealthAddressForCoin %s', Coins(coin_type).name) + self.log.debug("cacheNewStealthAddressForCoin %s", Coins(coin_type).name) if coin_type == Coins.LTC_MWEB: coin_type = Coins.LTC ci = self.ci(coin_type) - key_str = 'stealth_addr_' + ci.coin_name().lower() + key_str = "stealth_addr_" + ci.coin_name().lower() addr = ci.getNewStealthAddress() self.setStringKV(key_str, addr) return addr def getCachedStealthAddressForCoin(self, coin_type, session=None): - self.log.debug('getCachedStealthAddressForCoin %s', Coins(coin_type).name) + self.log.debug("getCachedStealthAddressForCoin %s", Coins(coin_type).name) if coin_type == Coins.LTC_MWEB: coin_type = Coins.LTC ci = self.ci(coin_type) - key_str = 'stealth_addr_' + ci.coin_name().lower() + key_str = "stealth_addr_" + ci.coin_name().lower() use_session = self.openSession(session) try: try: - addr = use_session.query(DBKVString).filter_by(key=key_str).first().value + addr = ( + use_session.query(DBKVString).filter_by(key=key_str).first().value + ) except Exception: addr = ci.getNewStealthAddress() - self.log.info('Generated new stealth address for %s', coin_type) - use_session.add(DBKVString( - key=key_str, - value=addr - )) + self.log.info("Generated new stealth address for %s", coin_type) + use_session.add(DBKVString(key=key_str, value=addr)) finally: if session is None: self.closeSession(use_session) return addr def getCachedWalletRestoreHeight(self, ci, session=None): - self.log.debug('getCachedWalletRestoreHeight %s', ci.coin_name()) + self.log.debug("getCachedWalletRestoreHeight %s", ci.coin_name()) - key_str = 'restore_height_' + ci.coin_name().lower() + key_str = "restore_height_" + ci.coin_name().lower() use_session = self.openSession(session) try: try: wrh = use_session.query(DBKVInt).filter_by(key=key_str).first().value except Exception: wrh = ci.getWalletRestoreHeight() - self.log.info('Found restore height for %s, block %d', ci.coin_name(), wrh) - use_session.add(DBKVInt( - key=key_str, - value=wrh - )) + self.log.info( + "Found restore height for %s, block %d", ci.coin_name(), wrh + ) + use_session.add(DBKVInt(key=key_str, value=wrh)) finally: if session is None: self.closeSession(use_session) @@ -2202,19 +2831,26 @@ class BasicSwap(BaseApp): def getNewContractId(self, session): self._contract_count += 1 - session.execute(text('UPDATE kv_int SET value = :value WHERE KEY="contract_count"'), {'value': self._contract_count}) + session.execute( + text('UPDATE kv_int SET value = :value WHERE KEY="contract_count"'), + {"value": self._contract_count}, + ) return self._contract_count def getProofOfFunds(self, coin_type, amount_for: int, extra_commit_bytes): ci = self.ci(coin_type) - self.log.debug('getProofOfFunds %s %s', ci.coin_name(), ci.format_amount(amount_for)) + self.log.debug( + "getProofOfFunds %s %s", ci.coin_name(), ci.format_amount(amount_for) + ) - if self.coin_clients[coin_type]['connection_type'] != 'rpc': + if self.coin_clients[coin_type]["connection_type"] != "rpc": return (None, None, None) return ci.getProofOfFunds(amount_for, extra_commit_bytes) - def saveBidInSession(self, bid_id: bytes, bid, session, xmr_swap=None, save_in_progress=None) -> None: + def saveBidInSession( + self, bid_id: bytes, bid, session, xmr_swap=None, save_in_progress=None + ) -> None: session.add(bid) if bid.initiate_tx: session.add(bid.initiate_tx) @@ -2233,7 +2869,7 @@ class BasicSwap(BaseApp): if save_in_progress is not None: if not isinstance(save_in_progress, Offer): - raise ValueError('Must specify offer for save_in_progress') + raise ValueError("Must specify offer for save_in_progress") self.swaps_in_progress[bid_id] = (bid, save_in_progress) # (bid, offer) def saveBid(self, bid_id: bytes, bid, xmr_swap=None) -> None: @@ -2250,15 +2886,18 @@ class BasicSwap(BaseApp): finally: self.closeSession(session) - def createActionInSession(self, delay: int, action_type: int, linked_id: bytes, session) -> None: - self.log.debug('createAction %d %s', action_type, linked_id.hex()) + def createActionInSession( + self, delay: int, action_type: int, linked_id: bytes, session + ) -> None: + self.log.debug("createAction %d %s", action_type, linked_id.hex()) now: int = self.getTime() action = Action( active_ind=1, created_at=now, trigger_at=now + delay, action_type=action_type, - linked_id=linked_id) + linked_id=linked_id, + ) session.add(action) for debug_case in self._debug_cases: bid_id, debug_ind = debug_case @@ -2268,7 +2907,8 @@ class BasicSwap(BaseApp): created_at=now, trigger_at=now + delay + 3, action_type=action_type, - linked_id=linked_id) + linked_id=linked_id, + ) session.add(action) def createAction(self, delay: int, action_type: int, linked_id: bytes) -> None: @@ -2280,14 +2920,22 @@ class BasicSwap(BaseApp): finally: self.closeSession(session) - def logEvent(self, linked_type: int, linked_id: bytes, event_type: int, event_msg: str, session) -> None: + def logEvent( + self, + linked_type: int, + linked_id: bytes, + event_type: int, + event_msg: str, + session, + ) -> None: entry = EventLog( active_ind=1, created_at=self.getTime(), linked_type=linked_type, linked_id=linked_id, event_type=int(event_type), - event_msg=event_msg) + event_msg=event_msg, + ) if session is not None: session.add(entry) @@ -2298,25 +2946,45 @@ class BasicSwap(BaseApp): finally: self.closeSession(session) - def logBidEvent(self, bid_id: bytes, event_type: int, event_msg: str, session) -> None: - self.log.debug('logBidEvent %s %s', bid_id.hex(), event_type) + def logBidEvent( + self, bid_id: bytes, event_type: int, event_msg: str, session + ) -> None: + self.log.debug("logBidEvent %s %s", bid_id.hex(), event_type) self.logEvent(Concepts.BID, bid_id, event_type, event_msg, session) def countBidEvents(self, bid, event_type, session): - q = session.execute(text('SELECT COUNT(*) FROM eventlog WHERE linked_type = {} AND linked_id = x\'{}\' AND event_type = {}'.format(int(Concepts.BID), bid.bid_id.hex(), int(event_type)))).first() + q = session.execute( + text( + "SELECT COUNT(*) FROM eventlog WHERE linked_type = {} AND linked_id = x'{}' AND event_type = {}".format( + int(Concepts.BID), bid.bid_id.hex(), int(event_type) + ) + ) + ).first() return q[0] def getEvents(self, linked_type: int, linked_id: bytes): events = [] session = self.openSession() try: - for entry in session.query(EventLog).filter(sa.and_(EventLog.linked_type == linked_type, EventLog.linked_id == linked_id)): + for entry in session.query(EventLog).filter( + sa.and_( + EventLog.linked_type == linked_type, EventLog.linked_id == linked_id + ) + ): events.append(entry) return events finally: self.closeSession(session, commit=False) - def addMessageLink(self, linked_type: int, linked_id: int, msg_type: int, msg_id: bytes, msg_sequence: int = 0, session=None) -> None: + def addMessageLink( + self, + linked_type: int, + linked_id: int, + msg_type: int, + msg_id: bytes, + msg_sequence: int = 0, + session=None, + ) -> None: entry = MessageLink( active_ind=1, created_at=self.getTime(), @@ -2324,7 +2992,8 @@ class BasicSwap(BaseApp): linked_id=linked_id, msg_type=int(msg_type), msg_sequence=msg_sequence, - msg_id=msg_id) + msg_id=msg_id, + ) if session is not None: session.add(entry) @@ -2335,89 +3004,150 @@ class BasicSwap(BaseApp): finally: self.closeSession(session) - def getLinkedMessageId(self, linked_type: int, linked_id: int, msg_type: int, msg_sequence: int = 0, session=None) -> bytes: + def getLinkedMessageId( + self, + linked_type: int, + linked_id: int, + msg_type: int, + msg_sequence: int = 0, + session=None, + ) -> bytes: try: use_session = self.openSession(session) - q = use_session.execute(text('SELECT msg_id FROM message_links WHERE linked_type = :linked_type AND linked_id = :linked_id AND msg_type = :msg_type AND msg_sequence = :msg_sequence'), - {'linked_type': linked_type, 'linked_id': linked_id, 'msg_type': msg_type, 'msg_sequence': msg_sequence}).first() + q = use_session.execute( + text( + "SELECT msg_id FROM message_links WHERE linked_type = :linked_type AND linked_id = :linked_id AND msg_type = :msg_type AND msg_sequence = :msg_sequence" + ), + { + "linked_type": linked_type, + "linked_id": linked_id, + "msg_type": msg_type, + "msg_sequence": msg_sequence, + }, + ).first() return q[0] finally: if session is None: self.closeSession(use_session, commit=False) - def countMessageLinks(self, linked_type: int, linked_id: int, msg_type: int, msg_sequence: int = 0, session=None) -> int: + def countMessageLinks( + self, + linked_type: int, + linked_id: int, + msg_type: int, + msg_sequence: int = 0, + session=None, + ) -> int: try: use_session = self.openSession(session) - q = use_session.execute(text('SELECT COUNT(*) FROM message_links WHERE linked_type = :linked_type AND linked_id = :linked_id AND msg_type = :msg_type AND msg_sequence = :msg_sequence'), - {'linked_type': linked_type, 'linked_id': linked_id, 'msg_type': msg_type, 'msg_sequence': msg_sequence}).first() + q = use_session.execute( + text( + "SELECT COUNT(*) FROM message_links WHERE linked_type = :linked_type AND linked_id = :linked_id AND msg_type = :msg_type AND msg_sequence = :msg_sequence" + ), + { + "linked_type": linked_type, + "linked_id": linked_id, + "msg_type": msg_type, + "msg_sequence": msg_sequence, + }, + ).first() return q[0] finally: if session is None: self.closeSession(use_session, commit=False) - def setBidAmounts(self, amount: int, offer, extra_options, ci_from) -> (int, int, int): - if 'amount_to' in extra_options: - amount_to: int = extra_options['amount_to'] - elif 'bid_rate' in extra_options: - bid_rate = extra_options['bid_rate'] + def setBidAmounts( + self, amount: int, offer, extra_options, ci_from + ) -> (int, int, int): + if "amount_to" in extra_options: + amount_to: int = extra_options["amount_to"] + elif "bid_rate" in extra_options: + bid_rate = extra_options["bid_rate"] amount_to: int = int((amount * bid_rate) // ci_from.COIN()) if not offer.rate_negotiable: - self.log.warning('Fixed-rate offer bids should set amount to instead of bid rate.') + self.log.warning( + "Fixed-rate offer bids should set amount to instead of bid rate." + ) else: amount_to: int = offer.amount_to bid_rate: int = ci_from.make_int(amount_to / amount, r=1) if offer.amount_negotiable and not offer.rate_negotiable: - if bid_rate != offer.rate and extra_options.get('adjust_amount_for_rate', True): - self.log.debug('Attempting to reduce amount to match offer rate.') + if bid_rate != offer.rate and extra_options.get( + "adjust_amount_for_rate", True + ): + self.log.debug("Attempting to reduce amount to match offer rate.") adjust_tries: int = 10000 if ci_from.exp() > 8 else 1000 for i in range(adjust_tries): test_amount = amount - i - test_amount_to: int = int((test_amount * offer.rate) // ci_from.COIN()) - test_bid_rate: int = ci_from.make_int(test_amount_to / test_amount, r=1) + test_amount_to: int = int( + (test_amount * offer.rate) // ci_from.COIN() + ) + test_bid_rate: int = ci_from.make_int( + test_amount_to / test_amount, r=1 + ) if test_bid_rate != offer.rate: test_amount_to -= 1 - test_bid_rate: int = ci_from.make_int(test_amount_to / test_amount, r=1) + test_bid_rate: int = ci_from.make_int( + test_amount_to / test_amount, r=1 + ) if test_bid_rate == offer.rate: if amount != test_amount: - self.log.info('Reducing bid amount-from from {} to {} to match offer rate.'.format(amount, test_amount)) + self.log.info( + "Reducing bid amount-from from {} to {} to match offer rate.".format( + amount, test_amount + ) + ) elif amount_to != test_amount_to: # Only show on first loop iteration (amount from unchanged) - self.log.info('Reducing bid amount-to from {} to {} to match offer rate.'.format(amount_to, test_amount_to)) + self.log.info( + "Reducing bid amount-to from {} to {} to match offer rate.".format( + amount_to, test_amount_to + ) + ) amount = test_amount amount_to = test_amount_to bid_rate = test_bid_rate break return amount, amount_to, bid_rate - def postBid(self, offer_id: bytes, amount: int, addr_send_from: str = None, extra_options={}) -> bytes: + def postBid( + self, offer_id: bytes, amount: int, addr_send_from: str = None, extra_options={} + ) -> bytes: # Bid to send bid.amount * bid.rate of coin_to in exchange for bid.amount of coin_from - self.log.debug('postBid for offer: %s', offer_id.hex()) + self.log.debug("postBid for offer: %s", offer_id.hex()) offer = self.getOffer(offer_id) - ensure(offer, 'Offer not found: {}.'.format(offer_id.hex())) - ensure(offer.expire_at > self.getTime(), 'Offer has expired') + ensure(offer, "Offer not found: {}.".format(offer_id.hex())) + ensure(offer.expire_at > self.getTime(), "Offer has expired") if offer.swap_type == SwapTypes.XMR_SWAP: return self.postXmrBid(offer_id, amount, addr_send_from, extra_options) - ensure(offer.protocol_version >= MINPROTO_VERSION_SECRET_HASH, 'Incompatible offer protocol version') - valid_for_seconds = extra_options.get('valid_for_seconds', 60 * 10) - self.validateBidValidTime(offer.swap_type, offer.coin_from, offer.coin_to, valid_for_seconds) + ensure( + offer.protocol_version >= MINPROTO_VERSION_SECRET_HASH, + "Incompatible offer protocol version", + ) + valid_for_seconds = extra_options.get("valid_for_seconds", 60 * 10) + self.validateBidValidTime( + offer.swap_type, offer.coin_from, offer.coin_to, valid_for_seconds + ) if not isinstance(amount, int): amount = int(amount) - self.log.warning('postBid amount should be an integer type.') + self.log.warning("postBid amount should be an integer type.") coin_from = Coins(offer.coin_from) coin_to = Coins(offer.coin_to) ci_from = self.ci(coin_from) ci_to = self.ci(coin_to) - amount, amount_to, bid_rate = self.setBidAmounts(amount, offer, extra_options, ci_from) + amount, amount_to, bid_rate = self.setBidAmounts( + amount, offer, extra_options, ci_from + ) self.validateBidAmount(offer, amount, bid_rate) try: @@ -2433,7 +3163,9 @@ class BasicSwap(BaseApp): now: int = self.getTime() if offer.swap_type == SwapTypes.SELLER_FIRST: - proof_addr, proof_sig, proof_utxos = self.getProofOfFunds(coin_to, amount_to, offer_id) + proof_addr, proof_sig, proof_utxos = self.getProofOfFunds( + coin_to, amount_to, offer_id + ) msg_buf.proof_address = proof_addr msg_buf.proof_signature = proof_sig @@ -2441,19 +3173,23 @@ class BasicSwap(BaseApp): msg_buf.proof_utxos = ci_to.encodeProofUtxos(proof_utxos) contract_count = self.getNewContractId(session) - contract_pubkey = self.getContractPubkey(dt.datetime.fromtimestamp(now).date(), contract_count) + contract_pubkey = self.getContractPubkey( + dt.datetime.fromtimestamp(now).date(), contract_count + ) msg_buf.pkhash_buyer = ci_from.pkh(contract_pubkey) pkhash_buyer_to = ci_to.pkh(contract_pubkey) if pkhash_buyer_to != msg_buf.pkhash_buyer: # Different pubkey hash msg_buf.pkhash_buyer_to = pkhash_buyer_to else: - raise ValueError('TODO') + raise ValueError("TODO") bid_bytes = msg_buf.to_bytes() - payload_hex = str.format('{:02x}', MessageTypes.BID) + bid_bytes.hex() + payload_hex = str.format("{:02x}", MessageTypes.BID) + bid_bytes.hex() - bid_addr = self.prepareSMSGAddress(addr_send_from, AddressTypes.BID, session) + bid_addr = self.prepareSMSGAddress( + addr_send_from, AddressTypes.BID, session + ) msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, valid_for_seconds) bid_id = self.sendSmsg(bid_addr, offer.addr_from, payload_hex, msg_valid) @@ -2468,7 +3204,6 @@ class BasicSwap(BaseApp): pkhash_buyer=msg_buf.pkhash_buyer, proof_address=msg_buf.proof_address, proof_utxos=msg_buf.proof_utxos, - created_at=now, contract_count=contract_count, expire_at=now + msg_buf.time_valid, @@ -2484,7 +3219,7 @@ class BasicSwap(BaseApp): self.saveBidInSession(bid_id, bid, session) - self.log.info('Sent BID %s', bid_id.hex()) + self.log.info("Sent BID %s", bid_id.hex()) return bid_id finally: self.closeSession(session) @@ -2501,10 +3236,10 @@ class BasicSwap(BaseApp): try: tx.block_height = height block_header = ci.getBlockHeaderFromHeight(height) - tx.block_hash = bytes.fromhex(block_header['hash']) - tx.block_time = block_header['time'] # Or median_time? + tx.block_hash = bytes.fromhex(block_header["hash"]) + tx.block_time = block_header["time"] # Or median_time? except Exception as e: - self.log.warning(f'setTxBlockInfoFromHeight failed {e}') + self.log.warning(f"setTxBlockInfoFromHeight failed {e}") def loadBidTxns(self, bid, session) -> None: bid.txns = {} @@ -2569,7 +3304,9 @@ class BasicSwap(BaseApp): bid = use_session.query(Bid).filter_by(bid_id=bid_id).first() offer = None if bid: - offer = use_session.query(Offer).filter_by(offer_id=bid.offer_id).first() + offer = ( + use_session.query(Offer).filter_by(offer_id=bid.offer_id).first() + ) self.loadBidTxns(bid, use_session) return bid, offer finally: @@ -2588,8 +3325,12 @@ class BasicSwap(BaseApp): if bid: offer = session.query(Offer).filter_by(offer_id=bid.offer_id).first() if offer and offer.swap_type == SwapTypes.XMR_SWAP: - xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() - xmr_offer = session.query(XmrOffer).filter_by(offer_id=bid.offer_id).first() + xmr_swap = ( + session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() + ) + xmr_offer = ( + session.query(XmrOffer).filter_by(offer_id=bid.offer_id).first() + ) self.loadBidTxns(bid, session) if list_events: events = self.list_bid_events(bid.bid_id, session) @@ -2607,44 +3348,68 @@ class BasicSwap(BaseApp): self.closeSession(session, commit=False) def list_bid_events(self, bid_id: bytes, session): - query_str = 'SELECT created_at, event_type, event_msg FROM eventlog ' + \ - 'WHERE active_ind = 1 AND linked_type = {} AND linked_id = x\'{}\' '.format(Concepts.BID, bid_id.hex()) + query_str = ( + "SELECT created_at, event_type, event_msg FROM eventlog " + + "WHERE active_ind = 1 AND linked_type = {} AND linked_id = x'{}' ".format( + Concepts.BID, bid_id.hex() + ) + ) q = session.execute(text(query_str)) events = [] for row in q: - events.append({'at': row[0], 'desc': describeEventEntry(row[1], row[2])}) + events.append({"at": row[0], "desc": describeEventEntry(row[1], row[2])}) - query_str = 'SELECT created_at, trigger_at FROM actions ' + \ - 'WHERE active_ind = 1 AND linked_id = x\'{}\' '.format(bid_id.hex()) + query_str = ( + "SELECT created_at, trigger_at FROM actions " + + "WHERE active_ind = 1 AND linked_id = x'{}' ".format(bid_id.hex()) + ) q = session.execute(text(query_str)) for row in q: - events.append({'at': row[0], 'desc': 'Delaying until: {}'.format(format_timestamp(row[1], with_seconds=True))}) + events.append( + { + "at": row[0], + "desc": "Delaying until: {}".format( + format_timestamp(row[1], with_seconds=True) + ), + } + ) return events def acceptBid(self, bid_id: bytes, session=None) -> None: - self.log.info('Accepting bid %s', bid_id.hex()) + self.log.info("Accepting bid %s", bid_id.hex()) try: use_session = self.openSession(session) bid, offer = self.getBidAndOffer(bid_id, use_session) - ensure(bid, 'Bid not found') - ensure(offer, 'Offer not found') + ensure(bid, "Bid not found") + ensure(offer, "Offer not found") # Ensure bid is still valid now: int = self.getTime() - ensure(bid.expire_at > now, 'Bid expired') - ensure(bid.state in (BidStates.BID_RECEIVED, ), 'Wrong bid state: {}'.format(BidStates(bid.state).name)) + ensure(bid.expire_at > now, "Bid expired") + ensure( + bid.state in (BidStates.BID_RECEIVED,), + "Wrong bid state: {}".format(BidStates(bid.state).name), + ) if offer.swap_type == SwapTypes.XMR_SWAP: - ensure(bid.protocol_version >= MINPROTO_VERSION_ADAPTOR_SIG, 'Incompatible bid protocol version') - reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) + ensure( + bid.protocol_version >= MINPROTO_VERSION_ADAPTOR_SIG, + "Incompatible bid protocol version", + ) + reverse_bid: bool = self.is_reverse_ads_bid( + offer.coin_from, offer.coin_to + ) if reverse_bid: return self.acceptADSReverseBid(bid_id, use_session) return self.acceptXmrBid(bid_id, use_session) - ensure(bid.protocol_version >= MINPROTO_VERSION_SECRET_HASH, 'Incompatible bid protocol version') + ensure( + bid.protocol_version >= MINPROTO_VERSION_SECRET_HASH, + "Incompatible bid protocol version", + ) if bid.contract_count is None: bid.contract_count = self.getNewContractId(use_session) @@ -2659,38 +3424,76 @@ class BasicSwap(BaseApp): pubkey_refund = self.getContractPubkey(bid_date, bid.contract_count) pkhash_refund = ci_from.pkh(pubkey_refund) - if coin_from in (Coins.DCR, ): + if coin_from in (Coins.DCR,): op_hash = OpCodes.OP_SHA256_DECRED else: op_hash = OpCodes.OP_SHA256 if bid.initiate_tx is not None: - self.log.warning('Initiate txn %s already exists for bid %s', bid.initiate_tx.txid, bid_id.hex()) + self.log.warning( + "Initiate txn %s already exists for bid %s", + bid.initiate_tx.txid, + bid_id.hex(), + ) txid = bid.initiate_tx.txid script = bid.initiate_tx.script else: if offer.lock_type < TxLockTypes.ABS_LOCK_BLOCKS: - sequence = ci_from.getExpectedSequence(offer.lock_type, offer.lock_value) - script = atomic_swap_1.buildContractScript(sequence, secret_hash, bid.pkhash_buyer, pkhash_refund, op_hash=op_hash) + sequence = ci_from.getExpectedSequence( + offer.lock_type, offer.lock_value + ) + script = atomic_swap_1.buildContractScript( + sequence, + secret_hash, + bid.pkhash_buyer, + pkhash_refund, + op_hash=op_hash, + ) else: if offer.lock_type == TxLockTypes.ABS_LOCK_BLOCKS: lock_value = ci_from.getChainHeight() + offer.lock_value else: lock_value = self.getTime() + offer.lock_value - self.log.debug('Initiate %s lock_value %d %d', ci_from.coin_name(), offer.lock_value, lock_value) - script = atomic_swap_1.buildContractScript(lock_value, secret_hash, bid.pkhash_buyer, pkhash_refund, OpCodes.OP_CHECKLOCKTIMEVERIFY, op_hash=op_hash) + self.log.debug( + "Initiate %s lock_value %d %d", + ci_from.coin_name(), + offer.lock_value, + lock_value, + ) + script = atomic_swap_1.buildContractScript( + lock_value, + secret_hash, + bid.pkhash_buyer, + pkhash_refund, + OpCodes.OP_CHECKLOCKTIMEVERIFY, + op_hash=op_hash, + ) bid.pkhash_seller = ci_to.pkh(pubkey_refund) - prefunded_tx = self.getPreFundedTx(Concepts.OFFER, offer.offer_id, TxTypes.ITX_PRE_FUNDED, session=use_session) - txn, lock_tx_vout = self.createInitiateTxn(coin_from, bid_id, bid, script, prefunded_tx) + prefunded_tx = self.getPreFundedTx( + Concepts.OFFER, + offer.offer_id, + TxTypes.ITX_PRE_FUNDED, + session=use_session, + ) + txn, lock_tx_vout = self.createInitiateTxn( + coin_from, bid_id, bid, script, prefunded_tx + ) # Store the signed refund txn in case wallet is locked when refund is possible - refund_txn = self.createRefundTxn(coin_from, txn, offer, bid, script, session=use_session) + refund_txn = self.createRefundTxn( + coin_from, txn, offer, bid, script, session=use_session + ) bid.initiate_txn_refund = bytes.fromhex(refund_txn) txid = ci_from.publishTx(bytes.fromhex(txn)) - self.log.debug('Submitted initiate txn %s to %s chain for bid %s', txid, ci_from.coin_name(), bid_id.hex()) + self.log.debug( + "Submitted initiate txn %s to %s chain for bid %s", + txid, + ci_from.coin_name(), + bid_id.hex(), + ) bid.initiate_tx = SwapTx( bid_id=bid_id, tx_type=TxTypes.ITX, @@ -2700,15 +3503,21 @@ class BasicSwap(BaseApp): script=script, ) bid.setITxState(TxStates.TX_SENT) - self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.ITX_PUBLISHED, '', use_session) + self.logEvent( + Concepts.BID, + bid.bid_id, + EventLogTypes.ITX_PUBLISHED, + "", + use_session, + ) # Check non-bip68 final try: txid = ci_from.publishTx(bid.initiate_txn_refund) - self.log.error('Submit refund_txn unexpectedly worked: ' + txid) + self.log.error("Submit refund_txn unexpectedly worked: " + txid) except Exception as ex: if ci_from.isTxNonFinalError(str(ex)) is False: - self.log.error('Submit refund_txn unexpected error' + str(ex)) + self.log.error("Submit refund_txn unexpected error" + str(ex)) raise ex if txid is not None: @@ -2722,13 +3531,23 @@ class BasicSwap(BaseApp): msg_buf.pkhash_seller = bid.pkhash_seller bid_bytes = msg_buf.to_bytes() - payload_hex = str.format('{:02x}', MessageTypes.BID_ACCEPT) + bid_bytes.hex() + payload_hex = ( + str.format("{:02x}", MessageTypes.BID_ACCEPT) + bid_bytes.hex() + ) msg_valid: int = self.getAcceptBidMsgValidTime(bid) - accept_msg_id = self.sendSmsg(offer.addr_from, bid.bid_addr, payload_hex, msg_valid) + accept_msg_id = self.sendSmsg( + offer.addr_from, bid.bid_addr, payload_hex, msg_valid + ) - self.addMessageLink(Concepts.BID, bid_id, MessageTypes.BID_ACCEPT, accept_msg_id, session=use_session) - self.log.info('Sent BID_ACCEPT %s', accept_msg_id.hex()) + self.addMessageLink( + Concepts.BID, + bid_id, + MessageTypes.BID_ACCEPT, + accept_msg_id, + session=use_session, + ) + self.log.info("Sent BID_ACCEPT %s", accept_msg_id.hex()) bid.setState(BidStates.BID_ACCEPTED) @@ -2739,62 +3558,81 @@ class BasicSwap(BaseApp): if session is None: self.closeSession(use_session) - def sendXmrSplitMessages(self, msg_type, addr_from: str, addr_to: str, bid_id: bytes, dleag: bytes, msg_valid: int, bid_msg_ids) -> None: + def sendXmrSplitMessages( + self, + msg_type, + addr_from: str, + addr_to: str, + bid_id: bytes, + dleag: bytes, + msg_valid: int, + bid_msg_ids, + ) -> None: msg_buf2 = XmrSplitMessage( - msg_id=bid_id, - msg_type=msg_type, - sequence=1, - dleag=dleag[16000:32000] + msg_id=bid_id, msg_type=msg_type, sequence=1, dleag=dleag[16000:32000] ) msg_bytes = msg_buf2.to_bytes() - payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex() + payload_hex = str.format("{:02x}", MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex() bid_msg_ids[1] = self.sendSmsg(addr_from, addr_to, payload_hex, msg_valid) msg_buf3 = XmrSplitMessage( - msg_id=bid_id, - msg_type=msg_type, - sequence=2, - dleag=dleag[32000:] + msg_id=bid_id, msg_type=msg_type, sequence=2, dleag=dleag[32000:] ) msg_bytes = msg_buf3.to_bytes() - payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex() + payload_hex = str.format("{:02x}", MessageTypes.XMR_BID_SPLIT) + msg_bytes.hex() bid_msg_ids[2] = self.sendSmsg(addr_from, addr_to, payload_hex, msg_valid) - def postXmrBid(self, offer_id: bytes, amount: int, addr_send_from: str = None, extra_options={}) -> bytes: + def postXmrBid( + self, offer_id: bytes, amount: int, addr_send_from: str = None, extra_options={} + ) -> bytes: # Bid to send bid.amount * bid.rate of coin_to in exchange for bid.amount of coin_from # Send MSG1L F -> L or MSG0F L -> F - self.log.debug('postXmrBid %s', offer_id.hex()) + self.log.debug("postXmrBid %s", offer_id.hex()) try: session = self.openSession() offer, xmr_offer = self.getXmrOffer(offer_id, session=session) - ensure(offer, 'Offer not found: {}.'.format(offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(offer_id.hex())) - ensure(offer.protocol_version >= MINPROTO_VERSION_ADAPTOR_SIG, 'Incompatible offer protocol version') - ensure(offer.expire_at > self.getTime(), 'Offer has expired') + ensure(offer, "Offer not found: {}.".format(offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(offer_id.hex())) + ensure( + offer.protocol_version >= MINPROTO_VERSION_ADAPTOR_SIG, + "Incompatible offer protocol version", + ) + ensure(offer.expire_at > self.getTime(), "Offer has expired") coin_from = Coins(offer.coin_from) coin_to = Coins(offer.coin_to) ci_from = self.ci(coin_from) ci_to = self.ci(coin_to) - valid_for_seconds: int = extra_options.get('valid_for_seconds', 60 * 10) + valid_for_seconds: int = extra_options.get("valid_for_seconds", 60 * 10) - amount, amount_to, bid_rate = self.setBidAmounts(amount, offer, extra_options, ci_from) + amount, amount_to, bid_rate = self.setBidAmounts( + amount, offer, extra_options, ci_from + ) bid_created_at: int = self.getTime() if offer.swap_type != SwapTypes.XMR_SWAP: - raise ValueError('TODO: Unknown swap type ' + offer.swap_type.name) + raise ValueError("TODO: Unknown swap type " + offer.swap_type.name) - if not (self.debug and extra_options.get('debug_skip_validation', False)): - self.validateBidValidTime(offer.swap_type, coin_from, coin_to, valid_for_seconds) + if not (self.debug and extra_options.get("debug_skip_validation", False)): + self.validateBidValidTime( + offer.swap_type, coin_from, coin_to, valid_for_seconds + ) self.validateBidAmount(offer, amount, bid_rate) self.checkCoinsReady(coin_from, coin_to) balance_to: int = ci_to.getSpendableBalance() - ensure(balance_to > amount_to, '{} spendable balance is too low: {} < {}'.format(ci_to.coin_name(), ci_to.format_amount(balance_to), ci_to.format_amount(amount_to))) + ensure( + balance_to > amount_to, + "{} spendable balance is too low: {} < {}".format( + ci_to.coin_name(), + ci_to.format_amount(balance_to), + ci_to.format_amount(amount_to), + ), + ) reverse_bid: bool = self.is_reverse_ads_bid(coin_from, coin_to) if reverse_bid: @@ -2808,15 +3646,21 @@ class BasicSwap(BaseApp): msg_buf.amount_to = amount_to bid_bytes = msg_buf.to_bytes() - payload_hex = str.format('{:02x}', MessageTypes.ADS_BID_LF) + bid_bytes.hex() + payload_hex = ( + str.format("{:02x}", MessageTypes.ADS_BID_LF) + bid_bytes.hex() + ) xmr_swap = XmrSwap() xmr_swap.contract_count = self.getNewContractId(session) - bid_addr = self.prepareSMSGAddress(addr_send_from, AddressTypes.BID, session) + bid_addr = self.prepareSMSGAddress( + addr_send_from, AddressTypes.BID, session + ) msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, valid_for_seconds) - xmr_swap.bid_id = self.sendSmsg(bid_addr, offer.addr_from, payload_hex, msg_valid) + xmr_swap.bid_id = self.sendSmsg( + bid_addr, offer.addr_from, payload_hex, msg_valid + ) bid = Bid( protocol_version=msg_buf.protocol_version, @@ -2839,7 +3683,7 @@ class BasicSwap(BaseApp): self.saveBidInSession(xmr_swap.bid_id, bid, session, xmr_swap) session.commit() - self.log.info('Sent ADS_BID_LF %s', xmr_swap.bid_id.hex()) + self.log.info("Sent ADS_BID_LF %s", xmr_swap.bid_id.hex()) return xmr_swap.bid_id msg_buf = XmrBidMessage() @@ -2849,10 +3693,12 @@ class BasicSwap(BaseApp): msg_buf.amount = int(amount) # Amount of coin_from msg_buf.amount_to = amount_to - address_out = self.getReceiveAddressFromPool(coin_from, offer_id, TxTypes.XMR_SWAP_A_LOCK, session=session) - if coin_from in (Coins.PART_BLIND, ): - addrinfo = ci_from.rpc('getaddressinfo', [address_out]) - msg_buf.dest_af = bytes.fromhex(addrinfo['pubkey']) + address_out = self.getReceiveAddressFromPool( + coin_from, offer_id, TxTypes.XMR_SWAP_A_LOCK, session=session + ) + if coin_from in (Coins.PART_BLIND,): + addrinfo = ci_from.rpc("getaddressinfo", [address_out]) + msg_buf.dest_af = bytes.fromhex(addrinfo["pubkey"]) else: msg_buf.dest_af = ci_from.decodeAddress(address_out) @@ -2861,10 +3707,30 @@ class BasicSwap(BaseApp): xmr_swap.dest_af = msg_buf.dest_af for_ed25519: bool = True if ci_to.curve_type() == Curves.ed25519 else False - kbvf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, KeyTypes.KBVF, for_ed25519) - kbsf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, KeyTypes.KBSF, for_ed25519) + kbvf = self.getPathKey( + coin_from, + coin_to, + bid_created_at, + xmr_swap.contract_count, + KeyTypes.KBVF, + for_ed25519, + ) + kbsf = self.getPathKey( + coin_from, + coin_to, + bid_created_at, + xmr_swap.contract_count, + KeyTypes.KBSF, + for_ed25519, + ) - kaf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, KeyTypes.KAF) + kaf = self.getPathKey( + coin_from, + coin_to, + bid_created_at, + xmr_swap.contract_count, + KeyTypes.KAF, + ) xmr_swap.vkbvf = kbvf xmr_swap.pkbvf = ci_to.getPubkey(kbvf) @@ -2874,36 +3740,54 @@ class BasicSwap(BaseApp): if ci_to.curve_type() == Curves.ed25519: xmr_swap.kbsf_dleag = ci_to.proveDLEAG(kbsf) - xmr_swap.pkasf = xmr_swap.kbsf_dleag[0: 33] + xmr_swap.pkasf = xmr_swap.kbsf_dleag[0:33] msg_buf.kbsf_dleag = xmr_swap.kbsf_dleag[:16000] elif ci_to.curve_type() == Curves.secp256k1: for i in range(10): - xmr_swap.kbsf_dleag = ci_to.signRecoverable(kbsf, 'proof kbsf owned for swap') - pk_recovered = ci_to.verifySigAndRecover(xmr_swap.kbsf_dleag, 'proof kbsf owned for swap') + xmr_swap.kbsf_dleag = ci_to.signRecoverable( + kbsf, "proof kbsf owned for swap" + ) + pk_recovered = ci_to.verifySigAndRecover( + xmr_swap.kbsf_dleag, "proof kbsf owned for swap" + ) if pk_recovered == xmr_swap.pkbsf: break - self.log.debug('kbsl recovered pubkey mismatch, retrying.') - assert (pk_recovered == xmr_swap.pkbsf) + self.log.debug("kbsl recovered pubkey mismatch, retrying.") + assert pk_recovered == xmr_swap.pkbsf xmr_swap.pkasf = xmr_swap.pkbsf msg_buf.kbsf_dleag = xmr_swap.kbsf_dleag else: - raise ValueError('Unknown curve') - assert (xmr_swap.pkasf == ci_from.getPubkey(kbsf)) + raise ValueError("Unknown curve") + assert xmr_swap.pkasf == ci_from.getPubkey(kbsf) msg_buf.pkaf = xmr_swap.pkaf msg_buf.kbvf = kbvf bid_bytes = msg_buf.to_bytes() - payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_FL) + bid_bytes.hex() + payload_hex = ( + str.format("{:02x}", MessageTypes.XMR_BID_FL) + bid_bytes.hex() + ) - bid_addr = self.prepareSMSGAddress(addr_send_from, AddressTypes.BID, session) + bid_addr = self.prepareSMSGAddress( + addr_send_from, AddressTypes.BID, session + ) msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, valid_for_seconds) - xmr_swap.bid_id = self.sendSmsg(bid_addr, offer.addr_from, payload_hex, msg_valid) + xmr_swap.bid_id = self.sendSmsg( + bid_addr, offer.addr_from, payload_hex, msg_valid + ) bid_msg_ids = {} if ci_to.curve_type() == Curves.ed25519: - self.sendXmrSplitMessages(XmrSplitMsgTypes.BID, bid_addr, offer.addr_from, xmr_swap.bid_id, xmr_swap.kbsf_dleag, msg_valid, bid_msg_ids) + self.sendXmrSplitMessages( + XmrSplitMsgTypes.BID, + bid_addr, + offer.addr_from, + xmr_swap.bid_id, + xmr_swap.kbsf_dleag, + msg_valid, + bid_msg_ids, + ) bid = Bid( protocol_version=msg_buf.protocol_version, @@ -2926,41 +3810,57 @@ class BasicSwap(BaseApp): wallet_restore_height = self.getWalletRestoreHeight(ci_to, session) if bid.chain_b_height_start < wallet_restore_height: bid.chain_b_height_start = wallet_restore_height - self.log.warning('Adaptor-sig swap restore height clamped to {}'.format(wallet_restore_height)) + self.log.warning( + "Adaptor-sig swap restore height clamped to {}".format( + wallet_restore_height + ) + ) bid.setState(BidStates.BID_SENT) self.saveBidInSession(xmr_swap.bid_id, bid, session, xmr_swap) for k, msg_id in bid_msg_ids.items(): - self.addMessageLink(Concepts.BID, xmr_swap.bid_id, MessageTypes.BID, msg_id, msg_sequence=k, session=session) + self.addMessageLink( + Concepts.BID, + xmr_swap.bid_id, + MessageTypes.BID, + msg_id, + msg_sequence=k, + session=session, + ) - self.log.info('Sent XMR_BID_FL %s', xmr_swap.bid_id.hex()) + self.log.info("Sent XMR_BID_FL %s", xmr_swap.bid_id.hex()) return xmr_swap.bid_id finally: self.closeSession(session) def acceptXmrBid(self, bid_id: bytes, session=None) -> None: # MSG1F and MSG2F L -> F - self.log.info('Accepting adaptor-sig bid %s', bid_id.hex()) + self.log.info("Accepting adaptor-sig bid %s", bid_id.hex()) now: int = self.getTime() try: use_session = self.openSession(session) bid, xmr_swap = self.getXmrBidFromSession(use_session, bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) - ensure(bid.expire_at > now, 'Bid expired') + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) + ensure(bid.expire_at > now, "Bid expired") last_bid_state = bid.state if last_bid_state == BidStates.SWAP_DELAYING: last_bid_state = getLastBidState(bid.states) - ensure(last_bid_state == BidStates.BID_RECEIVED, 'Wrong bid state: {}'.format(str(BidStates(last_bid_state)))) + ensure( + last_bid_state == BidStates.BID_RECEIVED, + "Wrong bid state: {}".format(str(BidStates(last_bid_state))), + ) offer, xmr_offer = self.getXmrOffer(bid.offer_id, session=use_session) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) - ensure(offer.expire_at > now, 'Offer has expired') + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure( + xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex()) + ) + ensure(offer.expire_at > now, "Offer has expired") reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) coin_from = Coins(offer.coin_to if reverse_bid else offer.coin_from) @@ -2968,24 +3868,45 @@ class BasicSwap(BaseApp): ci_from = self.ci(coin_from) ci_to = self.ci(coin_to) - a_fee_rate: int = xmr_offer.b_fee_rate if reverse_bid else xmr_offer.a_fee_rate - b_fee_rate: int = xmr_offer.a_fee_rate if reverse_bid else xmr_offer.b_fee_rate + a_fee_rate: int = ( + xmr_offer.b_fee_rate if reverse_bid else xmr_offer.a_fee_rate + ) if xmr_swap.contract_count is None: xmr_swap.contract_count = self.getNewContractId(use_session) for_ed25519: bool = True if ci_to.curve_type() == Curves.ed25519 else False - kbvl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBVL, for_ed25519) - kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBSL, for_ed25519) + kbvl = self.getPathKey( + coin_from, + coin_to, + bid.created_at, + xmr_swap.contract_count, + KeyTypes.KBVL, + for_ed25519, + ) + kbsl = self.getPathKey( + coin_from, + coin_to, + bid.created_at, + xmr_swap.contract_count, + KeyTypes.KBSL, + for_ed25519, + ) - kal = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KAL) + kal = self.getPathKey( + coin_from, + coin_to, + bid.created_at, + xmr_swap.contract_count, + KeyTypes.KAL, + ) xmr_swap.vkbvl = kbvl xmr_swap.pkbvl = ci_to.getPubkey(kbvl) xmr_swap.pkbsl = ci_to.getPubkey(kbsl) xmr_swap.vkbv = ci_to.sumKeys(kbvl, xmr_swap.vkbvf) - ensure(ci_to.verifyKey(xmr_swap.vkbv), 'Invalid key, vkbv') + ensure(ci_to.verifyKey(xmr_swap.vkbv), "Invalid key, vkbv") xmr_swap.pkbv = ci_to.sumPubkeys(xmr_swap.pkbvl, xmr_swap.pkbvf) xmr_swap.pkbs = ci_to.sumPubkeys(xmr_swap.pkbsl, xmr_swap.pkbsf) @@ -2997,60 +3918,107 @@ class BasicSwap(BaseApp): refundExtraArgs = dict() lockExtraArgs = dict() if self.isBchXmrSwap(offer): - pkh_refund_to = ci_from.decodeAddress(self.getCachedAddressForCoin(coin_from, use_session)) + pkh_refund_to = ci_from.decodeAddress( + self.getCachedAddressForCoin(coin_from, use_session) + ) pkh_dest = xmr_swap.dest_af # refund script - refundExtraArgs['mining_fee'] = 1000 - refundExtraArgs['out_1'] = ci_from.getScriptForPubkeyHash(pkh_refund_to) - refundExtraArgs['out_2'] = ci_from.getScriptForPubkeyHash(pkh_dest) - refundExtraArgs['public_key'] = xmr_swap.pkaf - refundExtraArgs['timelock'] = xmr_offer.lock_time_2 - refund_lock_tx_script = ci_from.genScriptLockTxScript(ci_from, xmr_swap.pkal, xmr_swap.pkaf, **refundExtraArgs) + refundExtraArgs["mining_fee"] = 1000 + refundExtraArgs["out_1"] = ci_from.getScriptForPubkeyHash(pkh_refund_to) + refundExtraArgs["out_2"] = ci_from.getScriptForPubkeyHash(pkh_dest) + refundExtraArgs["public_key"] = xmr_swap.pkaf + refundExtraArgs["timelock"] = xmr_offer.lock_time_2 + refund_lock_tx_script = ci_from.genScriptLockTxScript( + ci_from, xmr_swap.pkal, xmr_swap.pkaf, **refundExtraArgs + ) # will make use of this in `createSCLockRefundTx` - refundExtraArgs['refund_lock_tx_script'] = refund_lock_tx_script + refundExtraArgs["refund_lock_tx_script"] = refund_lock_tx_script # lock script - lockExtraArgs['mining_fee'] = 1000 - lockExtraArgs['out_1'] = ci_from.getScriptForPubkeyHash(pkh_dest) - lockExtraArgs['out_2'] = ci_from.scriptToP2SH32LockingBytecode(refund_lock_tx_script) - lockExtraArgs['public_key'] = xmr_swap.pkal - lockExtraArgs['timelock'] = xmr_offer.lock_time_1 + lockExtraArgs["mining_fee"] = 1000 + lockExtraArgs["out_1"] = ci_from.getScriptForPubkeyHash(pkh_dest) + lockExtraArgs["out_2"] = ci_from.scriptToP2SH32LockingBytecode( + refund_lock_tx_script + ) + lockExtraArgs["public_key"] = xmr_swap.pkal + lockExtraArgs["timelock"] = xmr_offer.lock_time_1 - xmr_swap.a_lock_tx_script = pi.genScriptLockTxScript(ci_from, xmr_swap.pkal, xmr_swap.pkaf, **lockExtraArgs) - prefunded_tx = self.getPreFundedTx(Concepts.OFFER, bid.offer_id, TxTypes.ITX_PRE_FUNDED, session=use_session) + xmr_swap.a_lock_tx_script = pi.genScriptLockTxScript( + ci_from, xmr_swap.pkal, xmr_swap.pkaf, **lockExtraArgs + ) + prefunded_tx = self.getPreFundedTx( + Concepts.OFFER, + bid.offer_id, + TxTypes.ITX_PRE_FUNDED, + session=use_session, + ) if prefunded_tx: - xmr_swap.a_lock_tx = pi.promoteMockTx(ci_from, prefunded_tx, xmr_swap.a_lock_tx_script) + xmr_swap.a_lock_tx = pi.promoteMockTx( + ci_from, prefunded_tx, xmr_swap.a_lock_tx_script + ) else: xmr_swap.a_lock_tx = ci_from.createSCLockTx( - bid.amount, - xmr_swap.a_lock_tx_script, xmr_swap.vkbv + bid.amount, xmr_swap.a_lock_tx_script, xmr_swap.vkbv + ) + xmr_swap.a_lock_tx = ci_from.fundSCLockTx( + xmr_swap.a_lock_tx, a_fee_rate, xmr_swap.vkbv ) - xmr_swap.a_lock_tx = ci_from.fundSCLockTx(xmr_swap.a_lock_tx, a_fee_rate, xmr_swap.vkbv) xmr_swap.a_lock_tx_id = ci_from.getTxid(xmr_swap.a_lock_tx) - a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script) - xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value = ci_from.createSCLockRefundTx( - xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script, - xmr_swap.pkal, xmr_swap.pkaf, - xmr_offer.lock_time_1, xmr_offer.lock_time_2, - a_fee_rate, xmr_swap.vkbv, **refundExtraArgs + ( + xmr_swap.a_lock_refund_tx, + xmr_swap.a_lock_refund_tx_script, + xmr_swap.a_swap_refund_value, + ) = ci_from.createSCLockRefundTx( + xmr_swap.a_lock_tx, + xmr_swap.a_lock_tx_script, + xmr_swap.pkal, + xmr_swap.pkaf, + xmr_offer.lock_time_1, + xmr_offer.lock_time_2, + a_fee_rate, + xmr_swap.vkbv, + **refundExtraArgs, ) xmr_swap.a_lock_refund_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_tx) prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap) - xmr_swap.al_lock_refund_tx_sig = ci_from.signTx(kal, xmr_swap.a_lock_refund_tx, 0, xmr_swap.a_lock_tx_script, prevout_amount) - v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_tx, xmr_swap.al_lock_refund_tx_sig, xmr_swap.pkal, 0, xmr_swap.a_lock_tx_script, prevout_amount) - ensure(v, 'Invalid coin A lock refund tx leader sig') - pkh_refund_to = ci_from.decodeAddress(self.getCachedAddressForCoin(coin_from, use_session)) - xmr_swap.a_lock_refund_spend_tx = ci_from.createSCLockRefundSpendTx( - xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script, - pkh_refund_to, - a_fee_rate, xmr_swap.vkbv, - **refundExtraArgs + xmr_swap.al_lock_refund_tx_sig = ci_from.signTx( + kal, + xmr_swap.a_lock_refund_tx, + 0, + xmr_swap.a_lock_tx_script, + prevout_amount, + ) + v = ci_from.verifyTxSig( + xmr_swap.a_lock_refund_tx, + xmr_swap.al_lock_refund_tx_sig, + xmr_swap.pkal, + 0, + xmr_swap.a_lock_tx_script, + prevout_amount, + ) + ensure(v, "Invalid coin A lock refund tx leader sig") + pkh_refund_to = ci_from.decodeAddress( + self.getCachedAddressForCoin(coin_from, use_session) + ) + xmr_swap.a_lock_refund_spend_tx = ci_from.createSCLockRefundSpendTx( + xmr_swap.a_lock_refund_tx, + xmr_swap.a_lock_refund_tx_script, + pkh_refund_to, + a_fee_rate, + xmr_swap.vkbv, + **refundExtraArgs, + ) + xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxid( + xmr_swap.a_lock_refund_spend_tx ) - xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_spend_tx) # Double check txns before sending - self.log.debug('Bid: {} - Double checking chain A lock txns are valid before sending bid accept.'.format(bid_id.hex())) + self.log.debug( + "Bid: {} - Double checking chain A lock txns are valid before sending bid accept.".format( + bid_id.hex() + ) + ) check_lock_tx_inputs = False # TODO: check_lock_tx_inputs without txindex _, xmr_swap.a_lock_tx_vout = ci_from.verifySCLockTx( xmr_swap.a_lock_tx, @@ -3061,7 +4029,8 @@ class BasicSwap(BaseApp): a_fee_rate, check_lock_tx_inputs, xmr_swap.vkbv, - **lockExtraArgs) + **lockExtraArgs, + ) _, _, lock_refund_vout = ci_from.verifySCLockRefundTx( xmr_swap.a_lock_refund_tx, @@ -3077,15 +4046,21 @@ class BasicSwap(BaseApp): bid.amount, a_fee_rate, xmr_swap.vkbv, - **refundExtraArgs) + **refundExtraArgs, + ) ci_from.verifySCLockRefundSpendTx( - xmr_swap.a_lock_refund_spend_tx, xmr_swap.a_lock_refund_tx, - xmr_swap.a_lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script, + xmr_swap.a_lock_refund_spend_tx, + xmr_swap.a_lock_refund_tx, + xmr_swap.a_lock_refund_tx_id, + xmr_swap.a_lock_refund_tx_script, xmr_swap.pkal, - lock_refund_vout, xmr_swap.a_swap_refund_value, a_fee_rate, + lock_refund_vout, + xmr_swap.a_swap_refund_value, + a_fee_rate, xmr_swap.vkbv, - **refundExtraArgs) + **refundExtraArgs, + ) msg_buf = XmrBidAcceptMessage() msg_buf.bid_msg_id = bid_id @@ -3097,15 +4072,19 @@ class BasicSwap(BaseApp): msg_buf.kbsl_dleag = xmr_swap.kbsl_dleag[:16000] elif ci_to.curve_type() == Curves.secp256k1: for i in range(10): - xmr_swap.kbsl_dleag = ci_to.signRecoverable(kbsl, 'proof kbsl owned for swap') - pk_recovered = ci_to.verifySigAndRecover(xmr_swap.kbsl_dleag, 'proof kbsl owned for swap') + xmr_swap.kbsl_dleag = ci_to.signRecoverable( + kbsl, "proof kbsl owned for swap" + ) + pk_recovered = ci_to.verifySigAndRecover( + xmr_swap.kbsl_dleag, "proof kbsl owned for swap" + ) if pk_recovered == xmr_swap.pkbsl: break - self.log.debug('kbsl recovered pubkey mismatch, retrying.') - assert (pk_recovered == xmr_swap.pkbsl) + self.log.debug("kbsl recovered pubkey mismatch, retrying.") + assert pk_recovered == xmr_swap.pkbsl msg_buf.kbsl_dleag = xmr_swap.kbsl_dleag else: - raise ValueError('Unknown curve') + raise ValueError("Unknown curve") # MSG2F msg_buf.a_lock_tx = xmr_swap.a_lock_tx @@ -3116,7 +4095,9 @@ class BasicSwap(BaseApp): msg_buf.al_lock_refund_tx_sig = xmr_swap.al_lock_refund_tx_sig msg_bytes = msg_buf.to_bytes() - payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_ACCEPT_LF) + msg_bytes.hex() + payload_hex = ( + str.format("{:02x}", MessageTypes.XMR_BID_ACCEPT_LF) + msg_bytes.hex() + ) addr_from: str = bid.bid_addr if reverse_bid else offer.addr_from addr_to: str = offer.addr_from if reverse_bid else bid.bid_addr @@ -3126,42 +4107,62 @@ class BasicSwap(BaseApp): bid_msg_ids[0] = self.sendSmsg(addr_from, addr_to, payload_hex, msg_valid) if ci_to.curve_type() == Curves.ed25519: - self.sendXmrSplitMessages(XmrSplitMsgTypes.BID_ACCEPT, addr_from, addr_to, xmr_swap.bid_id, xmr_swap.kbsl_dleag, msg_valid, bid_msg_ids) + self.sendXmrSplitMessages( + XmrSplitMsgTypes.BID_ACCEPT, + addr_from, + addr_to, + xmr_swap.bid_id, + xmr_swap.kbsl_dleag, + msg_valid, + bid_msg_ids, + ) bid.setState(BidStates.BID_ACCEPTED) # ADS self.saveBidInSession(bid_id, bid, use_session, xmr_swap=xmr_swap) for k, msg_id in bid_msg_ids.items(): - self.addMessageLink(Concepts.BID, bid_id, MessageTypes.BID_ACCEPT, msg_id, msg_sequence=k, session=use_session) + self.addMessageLink( + Concepts.BID, + bid_id, + MessageTypes.BID_ACCEPT, + msg_id, + msg_sequence=k, + session=use_session, + ) # Add to swaps_in_progress only when waiting on txns - self.log.info('Sent XMR_BID_ACCEPT_LF %s', bid_id.hex()) + self.log.info("Sent XMR_BID_ACCEPT_LF %s", bid_id.hex()) return bid_id finally: if session is None: self.closeSession(use_session) def acceptADSReverseBid(self, bid_id: bytes, session=None) -> None: - self.log.info('Accepting reverse adaptor-sig bid %s', bid_id.hex()) + self.log.info("Accepting reverse adaptor-sig bid %s", bid_id.hex()) now: int = self.getTime() try: use_session = self.openSession(session) bid, xmr_swap = self.getXmrBidFromSession(use_session, bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) - ensure(bid.expire_at > now, 'Bid expired') + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) + ensure(bid.expire_at > now, "Bid expired") last_bid_state = bid.state if last_bid_state == BidStates.SWAP_DELAYING: last_bid_state = getLastBidState(bid.states) - ensure(last_bid_state == BidStates.BID_RECEIVED, 'Wrong bid state: {}'.format(str(BidStates(last_bid_state)))) + ensure( + last_bid_state == BidStates.BID_RECEIVED, + "Wrong bid state: {}".format(str(BidStates(last_bid_state))), + ) offer, xmr_offer = self.getXmrOffer(bid.offer_id, session=use_session) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) - ensure(offer.expire_at > now, 'Offer has expired') + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure( + xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex()) + ) + ensure(offer.expire_at > now, "Offer has expired") # Bid is reversed coin_from = Coins(offer.coin_to) @@ -3173,15 +4174,37 @@ class BasicSwap(BaseApp): xmr_swap.contract_count = self.getNewContractId(use_session) for_ed25519: bool = True if ci_to.curve_type() == Curves.ed25519 else False - kbvf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBVF, for_ed25519) - kbsf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBSF, for_ed25519) + kbvf = self.getPathKey( + coin_from, + coin_to, + bid.created_at, + xmr_swap.contract_count, + KeyTypes.KBVF, + for_ed25519, + ) + kbsf = self.getPathKey( + coin_from, + coin_to, + bid.created_at, + xmr_swap.contract_count, + KeyTypes.KBSF, + for_ed25519, + ) - kaf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KAF) + kaf = self.getPathKey( + coin_from, + coin_to, + bid.created_at, + xmr_swap.contract_count, + KeyTypes.KAF, + ) - address_out = self.getReceiveAddressFromPool(coin_from, bid.offer_id, TxTypes.XMR_SWAP_A_LOCK, session=use_session) + address_out = self.getReceiveAddressFromPool( + coin_from, bid.offer_id, TxTypes.XMR_SWAP_A_LOCK, session=use_session + ) if coin_from == Coins.PART_BLIND: - addrinfo = ci_from.rpc('getaddressinfo', [address_out]) - xmr_swap.dest_af = bytes.fromhex(addrinfo['pubkey']) + addrinfo = ci_from.rpc("getaddressinfo", [address_out]) + xmr_swap.dest_af = bytes.fromhex(addrinfo["pubkey"]) else: xmr_swap.dest_af = ci_from.decodeAddress(address_out) @@ -3192,17 +4215,23 @@ class BasicSwap(BaseApp): xmr_swap.pkaf = ci_from.getPubkey(kaf) xmr_swap_1.setDLEAG(xmr_swap, ci_to, kbsf) - assert (xmr_swap.pkasf == ci_from.getPubkey(kbsf)) + assert xmr_swap.pkasf == ci_from.getPubkey(kbsf) msg_buf = ADSBidIntentAcceptMessage() msg_buf.bid_msg_id = bid_id msg_buf.dest_af = xmr_swap.dest_af msg_buf.pkaf = xmr_swap.pkaf msg_buf.kbvf = kbvf - msg_buf.kbsf_dleag = xmr_swap.kbsf_dleag if len(xmr_swap.kbsf_dleag) < 16000 else xmr_swap.kbsf_dleag[:16000] + msg_buf.kbsf_dleag = ( + xmr_swap.kbsf_dleag + if len(xmr_swap.kbsf_dleag) < 16000 + else xmr_swap.kbsf_dleag[:16000] + ) bid_bytes = msg_buf.to_bytes() - payload_hex = str.format('{:02x}', MessageTypes.ADS_BID_ACCEPT_FL) + bid_bytes.hex() + payload_hex = ( + str.format("{:02x}", MessageTypes.ADS_BID_ACCEPT_FL) + bid_bytes.hex() + ) addr_from: str = offer.addr_from addr_to: str = bid.bid_addr @@ -3211,13 +4240,28 @@ class BasicSwap(BaseApp): bid_msg_ids[0] = self.sendSmsg(addr_from, addr_to, payload_hex, msg_valid) if ci_to.curve_type() == Curves.ed25519: - self.sendXmrSplitMessages(XmrSplitMsgTypes.BID, addr_from, addr_to, xmr_swap.bid_id, xmr_swap.kbsf_dleag, msg_valid, bid_msg_ids) + self.sendXmrSplitMessages( + XmrSplitMsgTypes.BID, + addr_from, + addr_to, + xmr_swap.bid_id, + xmr_swap.kbsf_dleag, + msg_valid, + bid_msg_ids, + ) bid.setState(BidStates.BID_REQUEST_ACCEPTED) for k, msg_id in bid_msg_ids.items(): - self.addMessageLink(Concepts.BID, bid_id, MessageTypes.ADS_BID_ACCEPT_FL, msg_id, msg_sequence=k, session=use_session) - self.log.info('Sent ADS_BID_ACCEPT_FL %s', bid_msg_ids[0].hex()) + self.addMessageLink( + Concepts.BID, + bid_id, + MessageTypes.ADS_BID_ACCEPT_FL, + msg_id, + msg_sequence=k, + session=use_session, + ) + self.log.info("Sent ADS_BID_ACCEPT_FL %s", bid_msg_ids[0].hex()) self.saveBidInSession(bid_id, bid, use_session, xmr_swap=xmr_swap) finally: if session is None: @@ -3227,9 +4271,9 @@ class BasicSwap(BaseApp): try: session = self.openSession(session_in) bid = session.query(Bid).filter_by(bid_id=bid_id).first() - ensure(bid, 'Bid not found') + ensure(bid, "Bid not found") offer = session.query(Offer).filter_by(offer_id=bid.offer_id).first() - ensure(offer, 'Offer not found') + ensure(offer, "Offer not found") bid.setState(new_state) self.deactivateBid(session, offer, bid) @@ -3241,25 +4285,31 @@ class BasicSwap(BaseApp): def abandonBid(self, bid_id: bytes) -> None: if not self.debug: - self.log.error('Can\'t abandon bid %s when not in debug mode.', bid_id.hex()) + self.log.error("Can't abandon bid %s when not in debug mode.", bid_id.hex()) return - self.log.info('Abandoning Bid %s', bid_id.hex()) + self.log.info("Abandoning Bid %s", bid_id.hex()) self.deactivateBidForReason(bid_id, BidStates.BID_ABANDONED) def timeoutBid(self, bid_id: bytes, session_in=None) -> None: - self.log.info('Bid %s timed-out', bid_id.hex()) - self.deactivateBidForReason(bid_id, BidStates.SWAP_TIMEDOUT, session_in=session_in) + self.log.info("Bid %s timed-out", bid_id.hex()) + self.deactivateBidForReason( + bid_id, BidStates.SWAP_TIMEDOUT, session_in=session_in + ) - def setBidError(self, bid_id: bytes, bid, error_str: str, save_bid: bool = True, xmr_swap=None) -> None: - self.log.error('Bid %s - Error: %s', bid_id.hex(), error_str) + def setBidError( + self, bid_id: bytes, bid, error_str: str, save_bid: bool = True, xmr_swap=None + ) -> None: + self.log.error("Bid %s - Error: %s", bid_id.hex(), error_str) bid.setState(BidStates.BID_ERROR) - bid.state_note = 'error msg: ' + error_str + bid.state_note = "error msg: " + error_str if save_bid: self.saveBid(bid_id, bid, xmr_swap=xmr_swap) - def createInitiateTxn(self, coin_type, bid_id: bytes, bid, initiate_script, prefunded_tx=None) -> (Optional[str], Optional[int]): - if self.coin_clients[coin_type]['connection_type'] != 'rpc': + def createInitiateTxn( + self, coin_type, bid_id: bytes, bid, initiate_script, prefunded_tx=None + ) -> (Optional[str], Optional[int]): + if self.coin_clients[coin_type]["connection_type"] != "rpc": return None, None ci = self.ci(coin_type) @@ -3268,7 +4318,12 @@ class BasicSwap(BaseApp): addr_to = ci.encodeScriptDest(p2wsh) else: addr_to = ci.encode_p2sh(initiate_script) - self.log.debug('Create initiate txn for coin %s to %s for bid %s', Coins(coin_type).name, addr_to, bid_id.hex()) + self.log.debug( + "Create initiate txn for coin %s to %s for bid %s", + Coins(coin_type).name, + addr_to, + bid_id.hex(), + ) if prefunded_tx: pi = self.pi(SwapTypes.SELLER_FIRST) @@ -3278,12 +4333,12 @@ class BasicSwap(BaseApp): txjs = ci.describeTx(txn_signed) vout = getVoutByAddress(txjs, addr_to) - assert (vout is not None) + assert vout is not None return txn_signed, vout def deriveParticipateScript(self, bid_id: bytes, bid, offer) -> bytearray: - self.log.debug('deriveParticipateScript for bid %s', bid_id.hex()) + self.log.debug("deriveParticipateScript for bid %s", bid_id.hex()) coin_to = Coins(offer.coin_to) ci_to = self.ci(coin_to) @@ -3296,7 +4351,7 @@ class BasicSwap(BaseApp): else: pkhash_buyer_refund = bid.pkhash_buyer - if coin_to in (Coins.DCR, ): + if coin_to in (Coins.DCR,): op_hash = OpCodes.OP_SHA256_DECRED else: op_hash = OpCodes.OP_SHA256 @@ -3305,35 +4360,66 @@ class BasicSwap(BaseApp): lock_value = offer.lock_value // 2 if offer.lock_type < TxLockTypes.ABS_LOCK_BLOCKS: sequence = ci_to.getExpectedSequence(offer.lock_type, lock_value) - participate_script = atomic_swap_1.buildContractScript(sequence, secret_hash, pkhash_seller, pkhash_buyer_refund, op_hash=op_hash) + participate_script = atomic_swap_1.buildContractScript( + sequence, + secret_hash, + pkhash_seller, + pkhash_buyer_refund, + op_hash=op_hash, + ) else: # Lock from the height or time of the block containing the initiate txn coin_from = Coins(offer.coin_from) - block_header = self.ci(coin_from).getBlockHeaderFromHeight(bid.initiate_tx.chain_height) - initiate_tx_block_hash = block_header['hash'] - initiate_tx_block_time = block_header['time'] + block_header = self.ci(coin_from).getBlockHeaderFromHeight( + bid.initiate_tx.chain_height + ) + initiate_tx_block_hash = block_header["hash"] + initiate_tx_block_time = block_header["time"] if offer.lock_type == TxLockTypes.ABS_LOCK_BLOCKS: # Walk the coin_to chain back until block time matches - block_header_at = ci_to.getBlockHeaderAt(initiate_tx_block_time, block_after=True) - cblock_hash = block_header_at['hash'] - cblock_height = block_header_at['height'] + block_header_at = ci_to.getBlockHeaderAt( + initiate_tx_block_time, block_after=True + ) + cblock_hash = block_header_at["hash"] + cblock_height = block_header_at["height"] - self.log.debug('Setting lock value from height of block %s %s', Coins(coin_to).name, cblock_hash) + self.log.debug( + "Setting lock value from height of block %s %s", + Coins(coin_to).name, + cblock_hash, + ) contract_lock_value = cblock_height + lock_value else: - self.log.debug('Setting lock value from time of block %s %s', Coins(coin_from).name, initiate_tx_block_hash) + self.log.debug( + "Setting lock value from time of block %s %s", + Coins(coin_from).name, + initiate_tx_block_hash, + ) contract_lock_value = initiate_tx_block_time + lock_value - self.log.debug('participate %s lock_value %d %d', Coins(coin_to).name, lock_value, contract_lock_value) - participate_script = atomic_swap_1.buildContractScript(contract_lock_value, secret_hash, pkhash_seller, pkhash_buyer_refund, OpCodes.OP_CHECKLOCKTIMEVERIFY, op_hash=op_hash) + self.log.debug( + "participate %s lock_value %d %d", + Coins(coin_to).name, + lock_value, + contract_lock_value, + ) + participate_script = atomic_swap_1.buildContractScript( + contract_lock_value, + secret_hash, + pkhash_seller, + pkhash_buyer_refund, + OpCodes.OP_CHECKLOCKTIMEVERIFY, + op_hash=op_hash, + ) return participate_script - def createParticipateTxn(self, bid_id: bytes, bid, offer, participate_script: bytearray): - self.log.debug('createParticipateTxn') + def createParticipateTxn( + self, bid_id: bytes, bid, offer, participate_script: bytearray + ): + self.log.debug("createParticipateTxn") - offer_id = bid.offer_id coin_to = Coins(offer.coin_to) - if self.coin_clients[coin_to]['connection_type'] != 'rpc': + if self.coin_clients[coin_to]["connection_type"] != "rpc": return None ci = self.ci(coin_to) @@ -3341,8 +4427,15 @@ class BasicSwap(BaseApp): if bid.debug_ind == DebugTypes.MAKE_INVALID_PTX: amount_to -= 1 - self.log.debug('bid %s: Make invalid PTx for testing: %d.', bid_id.hex(), bid.debug_ind) - self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), None) + self.log.debug( + "bid %s: Make invalid PTx for testing: %d.", bid_id.hex(), bid.debug_ind + ) + self.logBidEvent( + bid.bid_id, + EventLogTypes.DEBUG_TWEAK_APPLIED, + "ind {}".format(bid.debug_ind), + None, + ) if ci.using_segwit(): p2wsh = ci.getScriptDest(participate_script) @@ -3352,12 +4445,19 @@ class BasicSwap(BaseApp): txn_signed = ci.createRawSignedTransaction(addr_to, amount_to) - refund_txn = self.createRefundTxn(coin_to, txn_signed, offer, bid, participate_script, tx_type=TxTypes.PTX_REFUND) + refund_txn = self.createRefundTxn( + coin_to, + txn_signed, + offer, + bid, + participate_script, + tx_type=TxTypes.PTX_REFUND, + ) bid.participate_txn_refund = bytes.fromhex(refund_txn) chain_height = ci.getChainHeight() - txjs = self.callcoinrpc(coin_to, 'decoderawtransaction', [txn_signed]) - txid = txjs['txid'] + txjs = self.callcoinrpc(coin_to, "decoderawtransaction", [txn_signed]) + txid = txjs["txid"] if ci.using_segwit(): vout = getVoutByScriptPubKey(txjs, p2wsh.hex()) @@ -3369,11 +4469,19 @@ class BasicSwap(BaseApp): return txn_signed - def createRedeemTxn(self, coin_type, bid, for_txn_type='participate', addr_redeem_out=None, fee_rate=None, session=None): - self.log.debug('createRedeemTxn for coin %s', Coins(coin_type).name) + def createRedeemTxn( + self, + coin_type, + bid, + for_txn_type="participate", + addr_redeem_out=None, + fee_rate=None, + session=None, + ): + self.log.debug("createRedeemTxn for coin %s", Coins(coin_type).name) ci = self.ci(coin_type) - if for_txn_type == 'participate': + if for_txn_type == "participate": prev_txnid = bid.participate_tx.txid.hex() prev_n = bid.participate_tx.vout txn_script = bid.participate_tx.script @@ -3391,11 +4499,12 @@ class BasicSwap(BaseApp): script_pub_key = getP2SHScriptForHash(ci.pkh(txn_script)).hex() prevout = { - 'txid': prev_txnid, - 'vout': prev_n, - 'scriptPubKey': script_pub_key, - 'redeemScript': txn_script.hex(), - 'amount': ci.format_amount(prev_amount)} + "txid": prev_txnid, + "vout": prev_n, + "scriptPubKey": script_pub_key, + "redeemScript": txn_script.hex(), + "amount": ci.format_amount(prev_amount), + } bid_date = dt.datetime.fromtimestamp(bid.created_at).date() privkey = self.getContractPrivkey(bid_date, bid.contract_count) @@ -3404,9 +4513,9 @@ class BasicSwap(BaseApp): secret = bid.recovered_secret if secret is None: secret = self.getContractSecret(bid_date, bid.contract_count) - ensure(len(secret) == 32, 'Bad secret length') + ensure(len(secret) == 32, "Bad secret length") - if self.coin_clients[coin_type]['connection_type'] != 'rpc': + if self.coin_clients[coin_type]["connection_type"] != "rpc": return None if fee_rate is None: @@ -3415,28 +4524,47 @@ class BasicSwap(BaseApp): tx_vsize = ci.getHTLCSpendTxVSize() tx_fee = (fee_rate * tx_vsize) / 1000 - self.log.debug('Redeem tx fee %s, rate %s', ci.format_amount(tx_fee, conv_int=True, r=1), str(fee_rate)) + self.log.debug( + "Redeem tx fee %s, rate %s", + ci.format_amount(tx_fee, conv_int=True, r=1), + str(fee_rate), + ) amount_out = prev_amount - ci.make_int(tx_fee, r=1) - ensure(amount_out > 0, 'Amount out <= 0') + ensure(amount_out > 0, "Amount out <= 0") if addr_redeem_out is None: - addr_redeem_out = self.getReceiveAddressFromPool(coin_type, bid.bid_id, TxTypes.PTX_REDEEM if for_txn_type == 'participate' else TxTypes.ITX_REDEEM, session) - assert (addr_redeem_out is not None) + addr_redeem_out = self.getReceiveAddressFromPool( + coin_type, + bid.bid_id, + ( + TxTypes.PTX_REDEEM + if for_txn_type == "participate" + else TxTypes.ITX_REDEEM + ), + session, + ) + assert addr_redeem_out is not None - self.log.debug('addr_redeem_out %s', addr_redeem_out) + self.log.debug("addr_redeem_out %s", addr_redeem_out) - redeem_txn = ci.createRedeemTxn(prevout, addr_redeem_out, amount_out, txn_script) + redeem_txn = ci.createRedeemTxn( + prevout, addr_redeem_out, amount_out, txn_script + ) options = {} if ci.using_segwit(): - options['force_segwit'] = True + options["force_segwit"] = True if coin_type in (Coins.NAV, Coins.DCR): privkey_wif = self.ci(coin_type).encodeKey(privkey) redeem_sig = ci.getTxSignature(redeem_txn, prevout, privkey_wif) else: privkey_wif = self.ci(Coins.PART).encodeKey(privkey) - redeem_sig = self.callcoinrpc(Coins.PART, 'createsignaturewithkey', [redeem_txn, prevout, privkey_wif, 'ALL', options]) + redeem_sig = self.callcoinrpc( + Coins.PART, + "createsignaturewithkey", + [redeem_txn, prevout, privkey_wif, "ALL", options], + ) if coin_type == Coins.PART or ci.using_segwit(): witness_stack = [ @@ -3444,49 +4572,81 @@ class BasicSwap(BaseApp): pubkey, secret, bytes((1,)), # Converted to OP_1 in Decred push_script_data - txn_script] - redeem_txn = ci.setTxSignature(bytes.fromhex(redeem_txn), witness_stack).hex() + txn_script, + ] + redeem_txn = ci.setTxSignature( + bytes.fromhex(redeem_txn), witness_stack + ).hex() else: - script = (len(redeem_sig) // 2).to_bytes(1, 'big') + bytes.fromhex(redeem_sig) - script += (33).to_bytes(1, 'big') + pubkey - script += (32).to_bytes(1, 'big') + secret - script += (OpCodes.OP_1).to_bytes(1, 'big') - script += (OpCodes.OP_PUSHDATA1).to_bytes(1, 'big') + (len(txn_script)).to_bytes(1, 'big') + txn_script + script = (len(redeem_sig) // 2).to_bytes(1, "big") + bytes.fromhex( + redeem_sig + ) + script += (33).to_bytes(1, "big") + pubkey + script += (32).to_bytes(1, "big") + secret + script += (OpCodes.OP_1).to_bytes(1, "big") + script += ( + (OpCodes.OP_PUSHDATA1).to_bytes(1, "big") + + (len(txn_script)).to_bytes(1, "big") + + txn_script + ) redeem_txn = ci.setTxScriptSig(bytes.fromhex(redeem_txn), 0, script).hex() if coin_type in (Coins.NAV, Coins.DCR): # Only checks signature ro = ci.verifyRawTransaction(redeem_txn, [prevout]) else: - ro = self.callcoinrpc(Coins.PART, 'verifyrawtransaction', [redeem_txn, [prevout]]) + ro = self.callcoinrpc( + Coins.PART, "verifyrawtransaction", [redeem_txn, [prevout]] + ) - ensure(ro['inputs_valid'] is True, 'inputs_valid is false') + ensure(ro["inputs_valid"] is True, "inputs_valid is false") # outputs_valid will be false if not a Particl txn # ensure(ro['complete'] is True, 'complete is false') - ensure(ro['validscripts'] == 1, 'validscripts != 1') + ensure(ro["validscripts"] == 1, "validscripts != 1") if self.debug: # Check fee - if ci.get_connection_type() == 'rpc': - redeem_txjs = self.callcoinrpc(coin_type, 'decoderawtransaction', [redeem_txn]) - if coin_type in (Coins.DCR, ): + if ci.get_connection_type() == "rpc": + redeem_txjs = self.callcoinrpc( + coin_type, "decoderawtransaction", [redeem_txn] + ) + if coin_type in (Coins.DCR,): txsize = len(redeem_txn) // 2 - self.log.debug('size paid, actual size %d %d', tx_vsize, txsize) - ensure(tx_vsize >= txsize, 'underpaid fee') + self.log.debug("size paid, actual size %d %d", tx_vsize, txsize) + ensure(tx_vsize >= txsize, "underpaid fee") elif ci.use_tx_vsize(): - self.log.debug('vsize paid, actual vsize %d %d', tx_vsize, redeem_txjs['vsize']) - ensure(tx_vsize >= redeem_txjs['vsize'], 'underpaid fee') + self.log.debug( + "vsize paid, actual vsize %d %d", tx_vsize, redeem_txjs["vsize"] + ) + ensure(tx_vsize >= redeem_txjs["vsize"], "underpaid fee") else: - self.log.debug('size paid, actual size %d %d', tx_vsize, redeem_txjs['size']) - ensure(tx_vsize >= redeem_txjs['size'], 'underpaid fee') + self.log.debug( + "size paid, actual size %d %d", tx_vsize, redeem_txjs["size"] + ) + ensure(tx_vsize >= redeem_txjs["size"], "underpaid fee") redeem_txid = ci.getTxid(bytes.fromhex(redeem_txn)) - self.log.debug('Have valid redeem txn %s for contract %s tx %s', redeem_txid.hex(), for_txn_type, prev_txnid) + self.log.debug( + "Have valid redeem txn %s for contract %s tx %s", + redeem_txid.hex(), + for_txn_type, + prev_txnid, + ) return redeem_txn - def createRefundTxn(self, coin_type, txn, offer, bid, txn_script: bytearray, addr_refund_out=None, tx_type=TxTypes.ITX_REFUND, session=None): - self.log.debug('createRefundTxn for coin %s', Coins(coin_type).name) - if self.coin_clients[coin_type]['connection_type'] != 'rpc': + def createRefundTxn( + self, + coin_type, + txn, + offer, + bid, + txn_script: bytearray, + addr_refund_out=None, + tx_type=TxTypes.ITX_REFUND, + session=None, + ): + self.log.debug("createRefundTxn for coin %s", Coins(coin_type).name) + if self.coin_clients[coin_type]["connection_type"] != "rpc": return None ci = self.ci(coin_type) @@ -3494,7 +4654,7 @@ class BasicSwap(BaseApp): prevout = ci.find_prevout_info(txn, txn_script) else: # TODO: Sign in bsx for all coins - txjs = self.callcoinrpc(Coins.PART, 'decoderawtransaction', [txn]) + txjs = self.callcoinrpc(Coins.PART, "decoderawtransaction", [txn]) if ci.using_segwit(): p2wsh = ci.getScriptDest(txn_script) vout = getVoutByScriptPubKey(txjs, p2wsh.hex()) @@ -3503,11 +4663,11 @@ class BasicSwap(BaseApp): vout = getVoutByAddress(txjs, addr_to) prevout = { - 'txid': txjs['txid'], - 'vout': vout, - 'scriptPubKey': txjs['vout'][vout]['scriptPubKey']['hex'], - 'redeemScript': txn_script.hex(), - 'amount': txjs['vout'][vout]['value'] + "txid": txjs["txid"], + "vout": vout, + "scriptPubKey": txjs["vout"][vout]["scriptPubKey"]["hex"], + "redeemScript": txn_script.hex(), + "amount": txjs["vout"][vout]["value"], } bid_date = dt.datetime.fromtimestamp(bid.created_at).date() @@ -3525,103 +4685,162 @@ class BasicSwap(BaseApp): tx_vsize = ci.getHTLCSpendTxVSize(False) tx_fee = (fee_rate * tx_vsize) / 1000 - self.log.debug('Refund tx fee %s, rate %s', ci.format_amount(tx_fee, conv_int=True, r=1), str(fee_rate)) + self.log.debug( + "Refund tx fee %s, rate %s", + ci.format_amount(tx_fee, conv_int=True, r=1), + str(fee_rate), + ) - amount_out = ci.make_int(prevout['amount'], r=1) - ci.make_int(tx_fee, r=1) + amount_out = ci.make_int(prevout["amount"], r=1) - ci.make_int(tx_fee, r=1) if amount_out <= 0: - raise ValueError('Refund amount out <= 0') + raise ValueError("Refund amount out <= 0") if addr_refund_out is None: - addr_refund_out = self.getReceiveAddressFromPool(coin_type, bid.bid_id, tx_type, session) - ensure(addr_refund_out is not None, 'addr_refund_out is null') - self.log.debug('addr_refund_out %s', addr_refund_out) + addr_refund_out = self.getReceiveAddressFromPool( + coin_type, bid.bid_id, tx_type, session + ) + ensure(addr_refund_out is not None, "addr_refund_out is null") + self.log.debug("addr_refund_out %s", addr_refund_out) locktime: int = 0 - if offer.lock_type == TxLockTypes.ABS_LOCK_BLOCKS or offer.lock_type == TxLockTypes.ABS_LOCK_TIME: + if ( + offer.lock_type == TxLockTypes.ABS_LOCK_BLOCKS + or offer.lock_type == TxLockTypes.ABS_LOCK_TIME + ): locktime = lock_value - refund_txn = ci.createRefundTxn(prevout, addr_refund_out, amount_out, locktime, sequence, txn_script) + refund_txn = ci.createRefundTxn( + prevout, addr_refund_out, amount_out, locktime, sequence, txn_script + ) options = {} - if self.coin_clients[coin_type]['use_segwit']: - options['force_segwit'] = True + if self.coin_clients[coin_type]["use_segwit"]: + options["force_segwit"] = True if coin_type in (Coins.NAV, Coins.DCR): privkey_wif = ci.encodeKey(privkey) refund_sig = ci.getTxSignature(refund_txn, prevout, privkey_wif) else: privkey_wif = self.ci(Coins.PART).encodeKey(privkey) - refund_sig = self.callcoinrpc(Coins.PART, 'createsignaturewithkey', [refund_txn, prevout, privkey_wif, 'ALL', options]) - if coin_type in (Coins.PART, Coins.DCR) or self.coin_clients[coin_type]['use_segwit']: - witness_stack = [ - bytes.fromhex(refund_sig), - pubkey, - b'', - txn_script] - refund_txn = ci.setTxSignature(bytes.fromhex(refund_txn), witness_stack).hex() + refund_sig = self.callcoinrpc( + Coins.PART, + "createsignaturewithkey", + [refund_txn, prevout, privkey_wif, "ALL", options], + ) + if ( + coin_type in (Coins.PART, Coins.DCR) + or self.coin_clients[coin_type]["use_segwit"] + ): + witness_stack = [bytes.fromhex(refund_sig), pubkey, b"", txn_script] + refund_txn = ci.setTxSignature( + bytes.fromhex(refund_txn), witness_stack + ).hex() else: - script = (len(refund_sig) // 2).to_bytes(1, 'big') + bytes.fromhex(refund_sig) - script += (33).to_bytes(1, 'big') + pubkey - script += (OpCodes.OP_0).to_bytes(1, 'big') - script += (OpCodes.OP_PUSHDATA1).to_bytes(1, 'big') + (len(txn_script)).to_bytes(1, 'big') + txn_script + script = (len(refund_sig) // 2).to_bytes(1, "big") + bytes.fromhex( + refund_sig + ) + script += (33).to_bytes(1, "big") + pubkey + script += (OpCodes.OP_0).to_bytes(1, "big") + script += ( + (OpCodes.OP_PUSHDATA1).to_bytes(1, "big") + + (len(txn_script)).to_bytes(1, "big") + + txn_script + ) refund_txn = ci.setTxScriptSig(bytes.fromhex(refund_txn), 0, script) if coin_type in (Coins.NAV, Coins.DCR): # Only checks signature ro = ci.verifyRawTransaction(refund_txn, [prevout]) else: - ro = self.callcoinrpc(Coins.PART, 'verifyrawtransaction', [refund_txn, [prevout]]) + ro = self.callcoinrpc( + Coins.PART, "verifyrawtransaction", [refund_txn, [prevout]] + ) - ensure(ro['inputs_valid'] is True, 'inputs_valid is false') + ensure(ro["inputs_valid"] is True, "inputs_valid is false") # outputs_valid will be false if not a Particl txn # ensure(ro['complete'] is True, 'complete is false') - ensure(ro['validscripts'] == 1, 'validscripts != 1') + ensure(ro["validscripts"] == 1, "validscripts != 1") if self.debug: # Check fee - if ci.get_connection_type() == 'rpc': - refund_txjs = self.callcoinrpc(coin_type, 'decoderawtransaction', [refund_txn,]) - if coin_type in (Coins.DCR, ): + if ci.get_connection_type() == "rpc": + refund_txjs = self.callcoinrpc( + coin_type, + "decoderawtransaction", + [ + refund_txn, + ], + ) + if coin_type in (Coins.DCR,): txsize = len(refund_txn) // 2 - self.log.debug('size paid, actual size %d %d', tx_vsize, txsize) - ensure(tx_vsize >= txsize, 'underpaid fee') + self.log.debug("size paid, actual size %d %d", tx_vsize, txsize) + ensure(tx_vsize >= txsize, "underpaid fee") elif ci.use_tx_vsize(): - self.log.debug('vsize paid, actual vsize %d %d', tx_vsize, refund_txjs['vsize']) - ensure(tx_vsize >= refund_txjs['vsize'], 'underpaid fee') + self.log.debug( + "vsize paid, actual vsize %d %d", tx_vsize, refund_txjs["vsize"] + ) + ensure(tx_vsize >= refund_txjs["vsize"], "underpaid fee") else: - self.log.debug('size paid, actual size %d %d', tx_vsize, refund_txjs['size']) - ensure(tx_vsize >= refund_txjs['size'], 'underpaid fee') + self.log.debug( + "size paid, actual size %d %d", tx_vsize, refund_txjs["size"] + ) + ensure(tx_vsize >= refund_txjs["size"], "underpaid fee") refund_txid = ci.getTxid(bytes.fromhex(refund_txn)) prev_txid = ci.getTxid(bytes.fromhex(txn)) - self.log.debug('Have valid refund txn %s for contract tx %s', refund_txid.hex(), prev_txid.hex()) + self.log.debug( + "Have valid refund txn %s for contract tx %s", + refund_txid.hex(), + prev_txid.hex(), + ) return refund_txn def initiateTxnConfirmed(self, bid_id: bytes, bid, offer) -> None: - self.log.debug('initiateTxnConfirmed for bid %s', bid_id.hex()) + self.log.debug("initiateTxnConfirmed for bid %s", bid_id.hex()) bid.setState(BidStates.SWAP_INITIATED) bid.setITxState(TxStates.TX_CONFIRMED) if bid.debug_ind == DebugTypes.BUYER_STOP_AFTER_ITX: - self.log.debug('bid %s: Abandoning bid for testing: %d, %s.', bid_id.hex(), bid.debug_ind, DebugTypes(bid.debug_ind).name) + self.log.debug( + "bid %s: Abandoning bid for testing: %d, %s.", + bid_id.hex(), + bid.debug_ind, + DebugTypes(bid.debug_ind).name, + ) bid.setState(BidStates.BID_ABANDONED) - self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), None) + self.logBidEvent( + bid.bid_id, + EventLogTypes.DEBUG_TWEAK_APPLIED, + "ind {}".format(bid.debug_ind), + None, + ) return # Bid saved in checkBidState # Seller first mode, buyer participates participate_script = self.deriveParticipateScript(bid_id, bid, offer) if bid.was_sent: if bid.participate_tx is not None: - self.log.warning('Participate txn %s already exists for bid %s', bid.participate_tx.txid, bid_id.hex()) + self.log.warning( + "Participate txn %s already exists for bid %s", + bid.participate_tx.txid, + bid_id.hex(), + ) else: - self.log.debug('Preparing participate txn for bid %s', bid_id.hex()) + self.log.debug("Preparing participate txn for bid %s", bid_id.hex()) coin_to = Coins(offer.coin_to) txn = self.createParticipateTxn(bid_id, bid, offer, participate_script) txid = self.ci(coin_to).publishTx(bytes.fromhex(txn)) - self.log.debug('Submitted participate txn %s to %s chain for bid %s', txid, chainparams[coin_to]['name'], bid_id.hex()) + self.log.debug( + "Submitted participate txn %s to %s chain for bid %s", + txid, + chainparams[coin_to]["name"], + bid_id.hex(), + ) bid.setPTxState(TxStates.TX_SENT) - self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.PTX_PUBLISHED, '', None) + self.logEvent( + Concepts.BID, bid.bid_id, EventLogTypes.PTX_PUBLISHED, "", None + ) else: bid.participate_tx = SwapTx( bid_id=bid_id, @@ -3630,10 +4849,21 @@ class BasicSwap(BaseApp): ) ci = self.ci(offer.coin_to) if ci.watch_blocks_for_scripts() is True: - chain_a_block_header = self.ci(offer.coin_from).getBlockHeaderFromHeight(bid.initiate_tx.chain_height) - chain_b_block_header = self.ci(offer.coin_to).getBlockHeaderAt(chain_a_block_header['time']) - self.setLastHeightCheckedStart(offer.coin_to, chain_b_block_header['height']) - self.addWatchedScript(offer.coin_to, bid_id, ci.getScriptDest(participate_script), TxTypes.PTX) + chain_a_block_header = self.ci( + offer.coin_from + ).getBlockHeaderFromHeight(bid.initiate_tx.chain_height) + chain_b_block_header = self.ci(offer.coin_to).getBlockHeaderAt( + chain_a_block_header["time"] + ) + self.setLastHeightCheckedStart( + offer.coin_to, chain_b_block_header["height"] + ) + self.addWatchedScript( + offer.coin_to, + bid_id, + ci.getScriptDest(participate_script), + TxTypes.PTX, + ) # Bid saved in checkBidState @@ -3644,24 +4874,33 @@ class BasicSwap(BaseApp): tx_height = self.lookupChainHeight(coin_type) block_header = ci.getBlockHeaderFromHeight(tx_height) - block_time = block_header['time'] + block_time = block_header["time"] cc = self.coin_clients[coin_type] - if len(cc['watched_outputs']) == 0 and len(cc['watched_scripts']) == 0: - cc['last_height_checked'] = tx_height - cc['block_check_min_time'] = block_time - self.setIntKV('block_check_min_time_' + coin_name, block_time, session) - self.log.debug('Start checking %s chain at height %d', coin_name, tx_height) - elif cc['last_height_checked'] > tx_height: - cc['last_height_checked'] = tx_height - cc['block_check_min_time'] = block_time - self.setIntKV('block_check_min_time_' + coin_name, block_time, session) - self.log.debug('Rewind %s chain last height checked to %d', coin_name, tx_height) + if len(cc["watched_outputs"]) == 0 and len(cc["watched_scripts"]) == 0: + cc["last_height_checked"] = tx_height + cc["block_check_min_time"] = block_time + self.setIntKV("block_check_min_time_" + coin_name, block_time, session) + self.log.debug("Start checking %s chain at height %d", coin_name, tx_height) + elif cc["last_height_checked"] > tx_height: + cc["last_height_checked"] = tx_height + cc["block_check_min_time"] = block_time + self.setIntKV("block_check_min_time_" + coin_name, block_time, session) + self.log.debug( + "Rewind %s chain last height checked to %d", coin_name, tx_height + ) else: - self.log.debug('Not setting %s chain last height checked to %d, leaving on %d', coin_name, tx_height, cc['last_height_checked']) + self.log.debug( + "Not setting %s chain last height checked to %d, leaving on %d", + coin_name, + tx_height, + cc["last_height_checked"], + ) return tx_height - def addParticipateTxn(self, bid_id: bytes, bid, coin_type, txid_hex: str, vout, tx_height) -> None: + def addParticipateTxn( + self, bid_id: bytes, bid, coin_type, txid_hex: str, vout, tx_height + ) -> None: # TODO: Check connection type participate_txn_height = self.setLastHeightCheckedStart(coin_type, tx_height) @@ -3677,14 +4916,21 @@ class BasicSwap(BaseApp): # Start checking for spends of participate_txn before fully confirmed ci = self.ci(coin_type) - self.log.debug('Watching %s chain for spend of output %s %d', ci.coin_name().lower(), txid_hex, vout) - self.addWatchedOutput(coin_type, bid_id, txid_hex, vout, BidStates.SWAP_PARTICIPATING) + self.log.debug( + "Watching %s chain for spend of output %s %d", + ci.coin_name().lower(), + txid_hex, + vout, + ) + self.addWatchedOutput( + coin_type, bid_id, txid_hex, vout, BidStates.SWAP_PARTICIPATING + ) def participateTxnConfirmed(self, bid_id: bytes, bid, offer) -> None: - self.log.debug('participateTxnConfirmed for bid %s', bid_id.hex()) + self.log.debug("participateTxnConfirmed for bid %s", bid_id.hex()) if bid.debug_ind == DebugTypes.DONT_CONFIRM_PTX: - self.log.debug('Not confirming PTX for debugging', bid_id.hex()) + self.log.debug("Not confirming PTX for debugging", bid_id.hex()) return bid.setState(BidStates.SWAP_PARTICIPATING) @@ -3695,16 +4941,23 @@ class BasicSwap(BaseApp): ci_to = self.ci(offer.coin_to) txn = self.createRedeemTxn(ci_to.coin_type(), bid) txid = ci_to.publishTx(bytes.fromhex(txn)) - self.log.debug('Submitted participate redeem txn %s to %s chain for bid %s', txid, ci_to.coin_name(), bid_id.hex()) - self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.PTX_REDEEM_PUBLISHED, '', None) + self.log.debug( + "Submitted participate redeem txn %s to %s chain for bid %s", + txid, + ci_to.coin_name(), + bid_id.hex(), + ) + self.logEvent( + Concepts.BID, bid.bid_id, EventLogTypes.PTX_REDEEM_PUBLISHED, "", None + ) # TX_REDEEMED will be set when spend is detected # TODO: Wait for depth? # bid saved in checkBidState def getAddressBalance(self, coin_type, address: str) -> int: - if self.coin_clients[coin_type]['chain_lookups'] == 'explorer': - explorers = self.coin_clients[coin_type]['explorers'] + if self.coin_clients[coin_type]["chain_lookups"] == "explorer": + explorers = self.coin_clients[coin_type]["explorers"] # TODO: random offset into explorers, try blocks for exp in explorers: @@ -3712,13 +4965,20 @@ class BasicSwap(BaseApp): return self.lookupUnspentByAddress(coin_type, address, sum_output=True) def lookupChainHeight(self, coin_type) -> int: - return self.callcoinrpc(coin_type, 'getblockcount') + return self.callcoinrpc(coin_type, "getblockcount") - def lookupUnspentByAddress(self, coin_type, address: str, sum_output: bool = False, assert_amount=None, assert_txid=None) -> int: + def lookupUnspentByAddress( + self, + coin_type, + address: str, + sum_output: bool = False, + assert_amount=None, + assert_txid=None, + ) -> int: ci = self.ci(coin_type) - if self.coin_clients[coin_type]['chain_lookups'] == 'explorer': - explorers = self.coin_clients[coin_type]['explorers'] + if self.coin_clients[coin_type]["chain_lookups"] == "explorer": + explorers = self.coin_clients[coin_type]["explorers"] # TODO: random offset into explorers, try blocks for exp in explorers: @@ -3726,53 +4986,73 @@ class BasicSwap(BaseApp): rv = exp.lookupUnspentByAddress(address) if assert_amount is not None: - ensure(rv['value'] == int(assert_amount), 'Incorrect output amount in txn {}: {} != {}.'.format(assert_txid, rv['value'], int(assert_amount))) + ensure( + rv["value"] == int(assert_amount), + "Incorrect output amount in txn {}: {} != {}.".format( + assert_txid, rv["value"], int(assert_amount) + ), + ) if assert_txid is not None: - ensure(rv['txid)'] == assert_txid, 'Incorrect txid') + ensure(rv["txid)"] == assert_txid, "Incorrect txid") return rv - raise ValueError('No explorer for lookupUnspentByAddress {}'.format(Coins(coin_type).name)) + raise ValueError( + "No explorer for lookupUnspentByAddress {}".format( + Coins(coin_type).name + ) + ) - if self.coin_clients[coin_type]['connection_type'] != 'rpc': - raise ValueError('No RPC connection for lookupUnspentByAddress {}'.format(Coins(coin_type).name)) + if self.coin_clients[coin_type]["connection_type"] != "rpc": + raise ValueError( + "No RPC connection for lookupUnspentByAddress {}".format( + Coins(coin_type).name + ) + ) if assert_txid is not None: try: - ro = self.callcoinrpc(coin_type, 'getmempoolentry', [assert_txid]) - self.log.debug('Tx %s found in mempool, fee %s', assert_txid, ro['fee']) + ro = self.callcoinrpc(coin_type, "getmempoolentry", [assert_txid]) + self.log.debug("Tx %s found in mempool, fee %s", assert_txid, ro["fee"]) # TODO: Save info return None except Exception: pass - num_blocks = self.callcoinrpc(coin_type, 'getblockcount') + num_blocks = self.callcoinrpc(coin_type, "getblockcount") sum_unspent = 0 - self.log.debug('[rm] scantxoutset start') # scantxoutset is slow - ro = self.callcoinrpc(coin_type, 'scantxoutset', ['start', ['addr({})'.format(address)]]) # TODO: Use combo(address) where possible - self.log.debug('[rm] scantxoutset end') - for o in ro['unspents']: - if assert_txid and o['txid'] != assert_txid: + self.log.debug("[rm] scantxoutset start") # scantxoutset is slow + ro = self.callcoinrpc( + coin_type, "scantxoutset", ["start", ["addr({})".format(address)]] + ) # TODO: Use combo(address) where possible + self.log.debug("[rm] scantxoutset end") + for o in ro["unspents"]: + if assert_txid and o["txid"] != assert_txid: continue # Verify amount if assert_amount: - ensure(make_int(o['amount']) == int(assert_amount), 'Incorrect output amount in txn {}: {} != {}.'.format(assert_txid, make_int(o['amount']), int(assert_amount))) + ensure( + make_int(o["amount"]) == int(assert_amount), + "Incorrect output amount in txn {}: {} != {}.".format( + assert_txid, make_int(o["amount"]), int(assert_amount) + ), + ) if not sum_output: - if o['height'] > 0: - n_conf = num_blocks - o['height'] + if o["height"] > 0: + n_conf = num_blocks - o["height"] else: n_conf = -1 return { - 'txid': o['txid'], - 'index': o['vout'], - 'height': o['height'], - 'n_conf': n_conf, - 'value': ci.make_int(o['amount']), + "txid": o["txid"], + "index": o["vout"], + "height": o["height"], + "n_conf": n_conf, + "value": ci.make_int(o["amount"]), } else: - sum_unspent += ci.make_int(o['amount']) + sum_unspent += ci.make_int(o["amount"]) if sum_output: return sum_unspent return None @@ -3787,22 +5067,46 @@ class BasicSwap(BaseApp): pass else: dest_address = ci_to.pkh_to_address(ci_to.pkh(xmr_swap.pkbs)) - found_tx = ci_to.getLockTxHeight(bid.xmr_b_lock_tx.txid, dest_address, bid.amount_to, bid.chain_b_height_start, vout=bid.xmr_b_lock_tx.vout) + found_tx = ci_to.getLockTxHeight( + bid.xmr_b_lock_tx.txid, + dest_address, + bid.amount_to, + bid.chain_b_height_start, + vout=bid.xmr_b_lock_tx.vout, + ) else: # Have to use findTxB instead of relying on the first seen height to detect chain reorgs - found_tx = ci_to.findTxB(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, ci_to.blocks_confirmed, bid.chain_b_height_start, bid_sender) + found_tx = ci_to.findTxB( + xmr_swap.vkbv, + xmr_swap.pkbs, + bid.amount_to, + ci_to.blocks_confirmed, + bid.chain_b_height_start, + bid_sender, + ) if isinstance(found_tx, int) and found_tx == -1: if self.countBidEvents(bid, EventLogTypes.LOCK_TX_B_INVALID, session) < 1: - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_INVALID, 'Detected invalid lock tx B', session) + self.logBidEvent( + bid.bid_id, + EventLogTypes.LOCK_TX_B_INVALID, + "Detected invalid lock tx B", + session, + ) bid_changed = True elif found_tx is not None: - if found_tx['height'] != 0 and (bid.xmr_b_lock_tx is None or not bid.xmr_b_lock_tx.chain_height): - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SEEN, '', session) + if found_tx["height"] != 0 and ( + bid.xmr_b_lock_tx is None or not bid.xmr_b_lock_tx.chain_height + ): + self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SEEN, "", session) - found_txid = bytes.fromhex(found_tx['txid']) - if bid.xmr_b_lock_tx is None or bid.xmr_b_lock_tx.chain_height is None or xmr_swap.b_lock_tx_id != found_txid: - self.log.debug('Found lock tx B in {} chain'.format(ci_to.coin_name())) + found_txid = bytes.fromhex(found_tx["txid"]) + if ( + bid.xmr_b_lock_tx is None + or bid.xmr_b_lock_tx.chain_height is None + or xmr_swap.b_lock_tx_id != found_txid + ): + self.log.debug("Found lock tx B in {} chain".format(ci_to.coin_name())) xmr_swap.b_lock_tx_id = found_txid if bid.xmr_b_lock_tx is None: bid.xmr_b_lock_tx = SwapTx( @@ -3811,10 +5115,14 @@ class BasicSwap(BaseApp): txid=xmr_swap.b_lock_tx_id, ) if bid.xmr_b_lock_tx.txid != found_txid: - self.log.debug('Updating {} lock txid: {}'.format(ci_to.coin_name(), found_txid.hex())) + self.log.debug( + "Updating {} lock txid: {}".format( + ci_to.coin_name(), found_txid.hex() + ) + ) bid.xmr_b_lock_tx.txid = found_txid - bid.xmr_b_lock_tx.chain_height = found_tx['height'] + bid.xmr_b_lock_tx.chain_height = found_tx["height"] bid_changed = True return bid_changed @@ -3831,33 +5139,68 @@ class BasicSwap(BaseApp): session = None try: session = self.openSession() - xmr_offer = session.query(XmrOffer).filter_by(offer_id=offer.offer_id).first() - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(offer.offer_id.hex())) + xmr_offer = ( + session.query(XmrOffer).filter_by(offer_id=offer.offer_id).first() + ) + ensure( + xmr_offer, + "Adaptor-sig offer not found: {}.".format(offer.offer_id.hex()), + ) xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid.bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid.bid_id.hex())) if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns: refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] if was_received: if bid.debug_ind == DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND: - self.log.debug('Adaptor-sig bid %s: Stalling bid for testing: %d.', bid_id.hex(), bid.debug_ind) + self.log.debug( + "Adaptor-sig bid %s: Stalling bid for testing: %d.", + bid_id.hex(), + bid.debug_ind, + ) bid.setState(BidStates.BID_STALLED_FOR_TEST) rv = True self.saveBidInSession(bid_id, bid, session, xmr_swap) - self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session) + self.logBidEvent( + bid.bid_id, + EventLogTypes.DEBUG_TWEAK_APPLIED, + "ind {}".format(bid.debug_ind), + session, + ) session.commit() return rv if TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND not in bid.txns: try: - if self.haveDebugInd(bid.bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND2): - raise TemporaryError('Debug: BID_DONT_SPEND_COIN_A_LOCK_REFUND2') - if bid.xmr_b_lock_tx is None and self.haveDebugInd(bid.bid_id, DebugTypes.WAIT_FOR_COIN_B_LOCK_BEFORE_REFUND): - raise TemporaryError('Debug: Waiting for Coin B Lock Tx') - txid_str = ci_from.publishTx(xmr_swap.a_lock_refund_spend_tx) - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED, '', session) + if self.haveDebugInd( + bid.bid_id, + DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND2, + ): + raise TemporaryError( + "Debug: BID_DONT_SPEND_COIN_A_LOCK_REFUND2" + ) + if bid.xmr_b_lock_tx is None and self.haveDebugInd( + bid.bid_id, + DebugTypes.WAIT_FOR_COIN_B_LOCK_BEFORE_REFUND, + ): + raise TemporaryError( + "Debug: Waiting for Coin B Lock Tx" + ) + txid_str = ci_from.publishTx( + xmr_swap.a_lock_refund_spend_tx + ) + self.logBidEvent( + bid.bid_id, + EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED, + "", + session, + ) - self.log.info('Submitted coin a lock refund spend tx for bid {}, txid {}'.format(bid_id.hex(), txid_str)) + self.log.info( + "Submitted coin a lock refund spend tx for bid {}, txid {}".format( + bid_id.hex(), txid_str + ) + ) bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND] = SwapTx( bid_id=bid_id, tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND, @@ -3866,19 +5209,33 @@ class BasicSwap(BaseApp): self.saveBidInSession(bid_id, bid, session, xmr_swap) session.commit() except Exception as ex: - self.log.debug('Trying to publish coin a lock refund spend tx: %s', str(ex)) + self.log.debug( + "Trying to publish coin a lock refund spend tx: %s", + str(ex), + ) if was_sent: if xmr_swap.a_lock_refund_swipe_tx is None: - self.createCoinALockRefundSwipeTx(ci_from, bid, offer, xmr_swap, xmr_offer) + self.createCoinALockRefundSwipeTx( + ci_from, bid, offer, xmr_swap, xmr_offer + ) self.saveBidInSession(bid_id, bid, session, xmr_swap) session.commit() if TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE not in bid.txns: try: txid = ci_from.publishTx(xmr_swap.a_lock_refund_swipe_tx) - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED, '', session) - self.log.info('Submitted coin a lock refund swipe tx for bid {}'.format(bid_id.hex())) + self.logBidEvent( + bid.bid_id, + EventLogTypes.LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED, + "", + session, + ) + self.log.info( + "Submitted coin a lock refund swipe tx for bid {}".format( + bid_id.hex() + ) + ) bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE] = SwapTx( bid_id=bid_id, tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE, @@ -3886,53 +5243,105 @@ class BasicSwap(BaseApp): ) if self.isBchXmrSwap(offer): if ci_from.altruistic(): - for_ed25519: bool = True if ci_to.curve_type() == Curves.ed25519 else False - kbsf = self.getPathKey(ci_from.coin_type(), ci_to.coin_type(), bid.created_at, xmr_swap.contract_count, KeyTypes.KBSF, for_ed25519) + for_ed25519: bool = ( + True + if ci_to.curve_type() == Curves.ed25519 + else False + ) + kbsf = self.getPathKey( + ci_from.coin_type(), + ci_to.coin_type(), + bid.created_at, + xmr_swap.contract_count, + KeyTypes.KBSF, + for_ed25519, + ) - mercy_tx = ci_from.createMercyTx(xmr_swap.a_lock_refund_swipe_tx, h2b(txid), xmr_swap.a_lock_refund_tx_script, kbsf) + mercy_tx = ci_from.createMercyTx( + xmr_swap.a_lock_refund_swipe_tx, + h2b(txid), + xmr_swap.a_lock_refund_tx_script, + kbsf, + ) txid_hex: str = ci_from.publishTx(mercy_tx) bid.txns[TxTypes.BCH_MERCY] = SwapTx( bid_id=bid_id, tx_type=TxTypes.BCH_MERCY, txid=bytes.fromhex(txid_hex), ) - self.log.info('Submitted mercy tx for bid {}, txid {}'.format(bid_id.hex(), txid_hex)) - self.logBidEvent(bid_id, EventLogTypes.BCH_MERCY_TX_PUBLISHED, '', session) + self.log.info( + "Submitted mercy tx for bid {}, txid {}".format( + bid_id.hex(), txid_hex + ) + ) + self.logBidEvent( + bid_id, + EventLogTypes.BCH_MERCY_TX_PUBLISHED, + "", + session, + ) else: - self.log.info('Not sending mercy tx for bid {}'.format(bid_id.hex())) + self.log.info( + "Not sending mercy tx for bid {}".format( + bid_id.hex() + ) + ) self.saveBidInSession(bid_id, bid, session, xmr_swap) session.commit() except Exception as ex: - self.log.debug('Trying to publish coin a lock refund swipe tx: %s', str(ex)) + self.log.debug( + "Trying to publish coin a lock refund swipe tx: %s", + str(ex), + ) if BidStates(bid.state) == BidStates.XMR_SWAP_NOSCRIPT_TX_RECOVERED: txid_hex = bid.xmr_b_lock_tx.spend_txid.hex() found_tx = ci_to.findTxnByHash(txid_hex) if found_tx is not None: - self.log.info('Found coin b lock recover tx bid %s', bid_id.hex()) + self.log.info( + "Found coin b lock recover tx bid %s", bid_id.hex() + ) rv = True # Remove from swaps_in_progress bid.setState(BidStates.XMR_SWAP_FAILED_REFUNDED) self.saveBidInSession(bid_id, bid, session, xmr_swap) session.commit() return rv else: # not XMR_SWAP_A_LOCK_REFUND in bid.txns - if len(xmr_swap.al_lock_refund_tx_sig) > 0 and len(xmr_swap.af_lock_refund_tx_sig) > 0: + if ( + len(xmr_swap.al_lock_refund_tx_sig) > 0 + and len(xmr_swap.af_lock_refund_tx_sig) > 0 + ): try: txid = ci_from.publishTx(xmr_swap.a_lock_refund_tx) # bch txids change if self.isBchXmrSwap(offer): - self.log.debug('Recomputing refund spend transaction and txid after submitting lock tx spend.') + self.log.debug( + "Recomputing refund spend transaction and txid after submitting lock tx spend." + ) tx = ci_from.loadTx(xmr_swap.a_lock_refund_spend_tx) tx.vin[0].prevout.hash = b2i(xmr_swap.a_lock_refund_tx_id) - xmr_swap.a_lock_refund_spend_tx = tx.serialize_without_witness() - xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_spend_tx) + xmr_swap.a_lock_refund_spend_tx = ( + tx.serialize_without_witness() + ) + xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxid( + xmr_swap.a_lock_refund_spend_tx + ) - self.log.info('Submitted coin a lock refund tx for bid {}'.format(bid_id.hex())) - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_TX_PUBLISHED, '', session) + self.log.info( + "Submitted coin a lock refund tx for bid {}".format( + bid_id.hex() + ) + ) + self.logBidEvent( + bid.bid_id, + EventLogTypes.LOCK_TX_A_REFUND_TX_PUBLISHED, + "", + session, + ) bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx( bid_id=bid_id, tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND, @@ -3943,7 +5352,11 @@ class BasicSwap(BaseApp): return rv except Exception as ex: if ci_from.isTxExistsError(str(ex)): - self.log.info('Found coin a lock refund tx for bid {}'.format(bid_id.hex())) + self.log.info( + "Found coin a lock refund tx for bid {}".format( + bid_id.hex() + ) + ) txid = ci_from.getTxid(xmr_swap.a_lock_refund_tx) if TxTypes.XMR_SWAP_A_LOCK_REFUND not in bid.txns: bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx( @@ -3964,10 +5377,24 @@ class BasicSwap(BaseApp): rv = True # Remove from swaps_in_progress elif state == BidStates.XMR_SWAP_FAILED: if was_sent and bid.xmr_b_lock_tx: - if self.countQueuedActions(session, bid_id, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B) < 1: + if ( + self.countQueuedActions( + session, bid_id, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B + ) + < 1 + ): delay = self.get_delay_event_seconds() - self.log.info('Recovering adaptor-sig swap chain B lock tx for bid %s in %d seconds', bid_id.hex(), delay) - self.createActionInSession(delay, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B, bid_id, session) + self.log.info( + "Recovering adaptor-sig swap chain B lock tx for bid %s in %d seconds", + bid_id.hex(), + delay, + ) + self.createActionInSession( + delay, + ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B, + bid_id, + session, + ) session.commit() elif state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX: if bid.xmr_a_lock_tx is None or bid.xmr_a_lock_tx.txid is None: @@ -3975,25 +5402,40 @@ class BasicSwap(BaseApp): # TODO: Timeout waiting for transactions bid_changed: bool = False - a_lock_tx_addr = ci_from.getSCLockScriptAddress(xmr_swap.a_lock_tx_script) - lock_tx_chain_info = ci_from.getLockTxHeight(bid.xmr_a_lock_tx.txid, a_lock_tx_addr, bid.amount, bid.chain_a_height_start, vout=bid.xmr_a_lock_tx.vout) + a_lock_tx_addr = ci_from.getSCLockScriptAddress( + xmr_swap.a_lock_tx_script + ) + lock_tx_chain_info = ci_from.getLockTxHeight( + bid.xmr_a_lock_tx.txid, + a_lock_tx_addr, + bid.amount, + bid.chain_a_height_start, + vout=bid.xmr_a_lock_tx.vout, + ) if lock_tx_chain_info is None: return rv - if 'txid' in lock_tx_chain_info and (xmr_swap.a_lock_tx_id is None or lock_tx_chain_info['txid'] != b2h(xmr_swap.a_lock_tx_id)): + if "txid" in lock_tx_chain_info and ( + xmr_swap.a_lock_tx_id is None + or lock_tx_chain_info["txid"] != b2h(xmr_swap.a_lock_tx_id) + ): # BCH: If we find that txid was changed (by funding or otherwise), we need to update it to track correctly - xmr_swap.a_lock_tx_id = h2b(lock_tx_chain_info['txid']) + xmr_swap.a_lock_tx_id = h2b(lock_tx_chain_info["txid"]) tx = ci_from.loadTx(xmr_swap.a_lock_refund_tx) tx.vin[0].prevout.hash = b2i(xmr_swap.a_lock_tx_id) xmr_swap.a_lock_refund_tx = tx.serialize_without_witness() - xmr_swap.a_lock_refund_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_tx) + xmr_swap.a_lock_refund_tx_id = ci_from.getTxid( + xmr_swap.a_lock_refund_tx + ) tx = ci_from.loadTx(xmr_swap.a_lock_spend_tx) tx.vin[0].prevout.hash = b2i(xmr_swap.a_lock_tx_id) xmr_swap.a_lock_spend_tx = tx.serialize_without_witness() - xmr_swap.a_lock_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_spend_tx) + xmr_swap.a_lock_spend_tx_id = ci_from.getTxid( + xmr_swap.a_lock_spend_tx + ) if bid.xmr_a_lock_tx: bid.xmr_a_lock_tx.txid = xmr_swap.a_lock_tx_id @@ -4006,21 +5448,36 @@ class BasicSwap(BaseApp): self.watchXmrSwap(bid, offer, xmr_swap, session) bid_changed = True - if bid.xmr_a_lock_tx.state == TxStates.TX_NONE and lock_tx_chain_info['height'] == 0: + if ( + bid.xmr_a_lock_tx.state == TxStates.TX_NONE + and lock_tx_chain_info["height"] == 0 + ): bid.xmr_a_lock_tx.setState(TxStates.TX_IN_MEMPOOL) - if not bid.xmr_a_lock_tx.chain_height and lock_tx_chain_info['height'] != 0: - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_SEEN, '', session) - self.setTxBlockInfoFromHeight(ci_from, bid.xmr_a_lock_tx, lock_tx_chain_info['height']) + if ( + not bid.xmr_a_lock_tx.chain_height + and lock_tx_chain_info["height"] != 0 + ): + self.logBidEvent( + bid.bid_id, EventLogTypes.LOCK_TX_A_SEEN, "", session + ) + self.setTxBlockInfoFromHeight( + ci_from, bid.xmr_a_lock_tx, lock_tx_chain_info["height"] + ) bid.xmr_a_lock_tx.setState(TxStates.TX_IN_CHAIN) bid_changed = True - if bid.xmr_a_lock_tx.chain_height != lock_tx_chain_info['height'] and lock_tx_chain_info['height'] != 0: - bid.xmr_a_lock_tx.chain_height = lock_tx_chain_info['height'] + if ( + bid.xmr_a_lock_tx.chain_height != lock_tx_chain_info["height"] + and lock_tx_chain_info["height"] != 0 + ): + bid.xmr_a_lock_tx.chain_height = lock_tx_chain_info["height"] bid_changed = True - if lock_tx_chain_info['depth'] >= ci_from.blocks_confirmed: - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_CONFIRMED, '', session) + if lock_tx_chain_info["depth"] >= ci_from.blocks_confirmed: + self.logBidEvent( + bid.bid_id, EventLogTypes.LOCK_TX_A_CONFIRMED, "", session + ) bid.xmr_a_lock_tx.setState(TxStates.TX_CONFIRMED) bid.setState(BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED) @@ -4028,44 +5485,97 @@ class BasicSwap(BaseApp): if was_sent: delay = self.get_delay_event_seconds() - self.log.info('Sending adaptor-sig swap chain B lock tx for bid %s in %d seconds', bid_id.hex(), delay) - self.createActionInSession(delay, ActionTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, session) + self.log.info( + "Sending adaptor-sig swap chain B lock tx for bid %s in %d seconds", + bid_id.hex(), + delay, + ) + self.createActionInSession( + delay, ActionTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, session + ) # bid.setState(BidStates.SWAP_DELAYING) elif ci_to.watch_blocks_for_scripts(): - chain_a_block_header = ci_from.getBlockHeaderFromHeight(bid.xmr_a_lock_tx.chain_height) - block_time = chain_a_block_header['time'] + chain_a_block_header = ci_from.getBlockHeaderFromHeight( + bid.xmr_a_lock_tx.chain_height + ) + block_time = chain_a_block_header["time"] chain_b_block_header = ci_to.getBlockHeaderAt(block_time) - self.log.debug('chain a block_time {}, chain b block height {}'.format(block_time, chain_b_block_header['height'])) + self.log.debug( + "chain a block_time {}, chain b block height {}".format( + block_time, chain_b_block_header["height"] + ) + ) dest_script = ci_to.getPkDest(xmr_swap.pkbs) - self.setLastHeightCheckedStart(ci_to.coin_type(), chain_b_block_header['height'], session) - self.addWatchedScript(ci_to.coin_type(), bid.bid_id, dest_script, TxTypes.XMR_SWAP_B_LOCK) + self.setLastHeightCheckedStart( + ci_to.coin_type(), chain_b_block_header["height"], session + ) + self.addWatchedScript( + ci_to.coin_type(), + bid.bid_id, + dest_script, + TxTypes.XMR_SWAP_B_LOCK, + ) if bid_changed: self.saveBidInSession(bid_id, bid, session, xmr_swap) session.commit() - elif state in (BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED, BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND): + elif state in ( + BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED, + BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND, + ): bid_changed = self.findTxB(ci_to, xmr_swap, bid, session, was_sent) - if bid.xmr_b_lock_tx and bid.xmr_b_lock_tx.chain_height is not None and bid.xmr_b_lock_tx.chain_height > 0: + if ( + bid.xmr_b_lock_tx + and bid.xmr_b_lock_tx.chain_height is not None + and bid.xmr_b_lock_tx.chain_height > 0 + ): chain_height = ci_to.getChainHeight() if bid.debug_ind == DebugTypes.BID_STOP_AFTER_COIN_B_LOCK: - self.log.debug('Adaptor-sig bid %s: Stalling bid for testing: %d.', bid_id.hex(), bid.debug_ind) + self.log.debug( + "Adaptor-sig bid %s: Stalling bid for testing: %d.", + bid_id.hex(), + bid.debug_ind, + ) bid.setState(BidStates.BID_STALLED_FOR_TEST) - self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session) - elif chain_height - bid.xmr_b_lock_tx.chain_height >= ci_to.blocks_confirmed: - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_CONFIRMED, '', session) + self.logBidEvent( + bid.bid_id, + EventLogTypes.DEBUG_TWEAK_APPLIED, + "ind {}".format(bid.debug_ind), + session, + ) + elif ( + chain_height - bid.xmr_b_lock_tx.chain_height + >= ci_to.blocks_confirmed + ): + self.logBidEvent( + bid.bid_id, EventLogTypes.LOCK_TX_B_CONFIRMED, "", session + ) bid.xmr_b_lock_tx.setState(TxStates.TX_CONFIRMED) bid.setState(BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED) if was_received: if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns: - self.log.warning('Not releasing ads script coin lock tx for bid {}: Chain A lock refund tx already exists.'.format(bid_id.hex())) + self.log.warning( + "Not releasing ads script coin lock tx for bid {}: Chain A lock refund tx already exists.".format( + bid_id.hex() + ) + ) else: delay = self.get_delay_event_seconds() - self.log.info('Releasing ads script coin lock tx for bid %s in %d seconds', bid_id.hex(), delay) - self.createActionInSession(delay, ActionTypes.SEND_XMR_LOCK_RELEASE, bid_id, session) + self.log.info( + "Releasing ads script coin lock tx for bid %s in %d seconds", + bid_id.hex(), + delay, + ) + self.createActionInSession( + delay, + ActionTypes.SEND_XMR_LOCK_RELEASE, + bid_id, + session, + ) if bid_changed: self.saveBidInSession(bid_id, bid, session, xmr_swap) @@ -4077,21 +5587,53 @@ class BasicSwap(BaseApp): if was_received: try: txn_hex = ci_from.getMempoolTx(xmr_swap.a_lock_spend_tx_id) - self.log.info('Found lock spend txn in %s mempool, %s', ci_from.coin_name(), xmr_swap.a_lock_spend_tx_id.hex()) - self.process_XMR_SWAP_A_LOCK_tx_spend(bid_id, xmr_swap.a_lock_spend_tx_id.hex(), txn_hex, session) + self.log.info( + "Found lock spend txn in %s mempool, %s", + ci_from.coin_name(), + xmr_swap.a_lock_spend_tx_id.hex(), + ) + self.process_XMR_SWAP_A_LOCK_tx_spend( + bid_id, xmr_swap.a_lock_spend_tx_id.hex(), txn_hex, session + ) except Exception as e: - self.log.debug('getrawtransaction lock spend tx failed: %s', str(e)) + self.log.debug( + "getrawtransaction lock spend tx failed: %s", str(e) + ) elif state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED: - if was_received and self.countQueuedActions(session, bid_id, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B) < 1: + if ( + was_received + and self.countQueuedActions( + session, bid_id, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B + ) + < 1 + ): if self.haveDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_B_LOCK): - self.log.debug('Adaptor-sig bid %s: Stalling bid for testing: %d.', bid_id.hex(), bid.debug_ind) + self.log.debug( + "Adaptor-sig bid %s: Stalling bid for testing: %d.", + bid_id.hex(), + bid.debug_ind, + ) # If BID_STALLED_FOR_TEST is set process_XMR_SWAP_A_LOCK_tx_spend would fail - self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session) + self.logBidEvent( + bid.bid_id, + EventLogTypes.DEBUG_TWEAK_APPLIED, + "ind {}".format(bid.debug_ind), + session, + ) else: bid.setState(BidStates.SWAP_DELAYING) delay = self.get_delay_event_seconds() - self.log.info('Redeeming coin b lock tx for bid %s in %d seconds', bid_id.hex(), delay) - self.createActionInSession(delay, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, session) + self.log.info( + "Redeeming coin b lock tx for bid %s in %d seconds", + bid_id.hex(), + delay, + ) + self.createActionInSession( + delay, + ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B, + bid_id, + session, + ) self.saveBidInSession(bid_id, bid, session, xmr_swap) session.commit() elif state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED: @@ -4099,7 +5641,7 @@ class BasicSwap(BaseApp): found_tx = ci_to.findTxnByHash(txid_hex) if found_tx is not None: - self.log.info('Found coin b lock spend tx bid %s', bid_id.hex()) + self.log.info("Found coin b lock spend tx bid %s", bid_id.hex()) rv = True # Remove from swaps_in_progress bid.setState(BidStates.SWAP_COMPLETED) self.saveBidInSession(bid_id, bid, session, xmr_swap) @@ -4108,11 +5650,24 @@ class BasicSwap(BaseApp): if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns: refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] if refund_tx.block_time is None: - refund_tx_addr = ci_from.getSCLockScriptAddress(xmr_swap.a_lock_refund_tx_script) - lock_refund_tx_chain_info = ci_from.getLockTxHeight(refund_tx.txid, refund_tx_addr, 0, bid.chain_a_height_start, vout=refund_tx.vout) + refund_tx_addr = ci_from.getSCLockScriptAddress( + xmr_swap.a_lock_refund_tx_script + ) + lock_refund_tx_chain_info = ci_from.getLockTxHeight( + refund_tx.txid, + refund_tx_addr, + 0, + bid.chain_a_height_start, + vout=refund_tx.vout, + ) - if lock_refund_tx_chain_info is not None and lock_refund_tx_chain_info.get('height', 0) > 0: - self.setTxBlockInfoFromHeight(ci_from, refund_tx, lock_refund_tx_chain_info['height']) + if ( + lock_refund_tx_chain_info is not None + and lock_refund_tx_chain_info.get("height", 0) > 0 + ): + self.setTxBlockInfoFromHeight( + ci_from, refund_tx, lock_refund_tx_chain_info["height"] + ) self.saveBidInSession(bid_id, bid, session, xmr_swap) session.commit() @@ -4129,7 +5684,7 @@ class BasicSwap(BaseApp): # Return True to remove bid from in-progress list state = BidStates(bid.state) - self.log.debug('checkBidState %s %s', bid_id.hex(), str(state)) + self.log.debug("checkBidState %s %s", bid_id.hex(), str(state)) if offer.swap_type == SwapTypes.XMR_SWAP: return self.checkXmrBidState(bid_id, bid, offer) @@ -4143,7 +5698,7 @@ class BasicSwap(BaseApp): # TODO: Batch calls to scantxoutset # TODO: timeouts if state == BidStates.BID_ABANDONED: - self.log.info('Deactivating abandoned bid: %s', bid_id.hex()) + self.log.info("Deactivating abandoned bid: %s", bid_id.hex()) return True # Mark bid for archiving if state == BidStates.BID_ACCEPTED: # Waiting for initiate txn to be confirmed in 'from' chain @@ -4155,16 +5710,23 @@ class BasicSwap(BaseApp): if coin_from == Coins.PART: # Has txindex try: p2sh = ci_from.encode_p2sh(bid.initiate_tx.script) - initiate_txn = self.callcoinrpc(coin_from, 'getrawtransaction', [initiate_txnid_hex, True]) + initiate_txn = self.callcoinrpc( + coin_from, "getrawtransaction", [initiate_txnid_hex, True] + ) # Verify amount vout = getVoutByAddress(initiate_txn, p2sh) - out_value = make_int(initiate_txn['vout'][vout]['value']) - ensure(out_value == int(bid.amount), 'Incorrect output amount in initiate txn {}: {} != {}.'.format(initiate_txnid_hex, out_value, int(bid.amount))) + out_value = make_int(initiate_txn["vout"][vout]["value"]) + ensure( + out_value == int(bid.amount), + "Incorrect output amount in initiate txn {}: {} != {}.".format( + initiate_txnid_hex, out_value, int(bid.amount) + ), + ) - bid.initiate_tx.conf = initiate_txn['confirmations'] + bid.initiate_tx.conf = initiate_txn["confirmations"] try: - tx_height = initiate_txn['height'] + tx_height = initiate_txn["height"] except Exception: tx_height = -1 index = vout @@ -4177,13 +5739,20 @@ class BasicSwap(BaseApp): else: addr = ci_from.encode_p2sh(bid.initiate_tx.script) - found = ci_from.getLockTxHeight(bid.initiate_tx.txid, addr, bid.amount, bid.chain_a_height_start, find_index=True, vout=bid.initiate_tx.vout) + found = ci_from.getLockTxHeight( + bid.initiate_tx.txid, + addr, + bid.amount, + bid.chain_a_height_start, + find_index=True, + vout=bid.initiate_tx.vout, + ) index = None if found: - bid.initiate_tx.conf = found['depth'] - if 'index' in found: - index = found['index'] - tx_height = found['height'] + bid.initiate_tx.conf = found["depth"] + if "index" in found: + index = found["index"] + tx_height = found["height"] if bid.initiate_tx.conf != last_initiate_txn_conf: save_bid = True @@ -4193,27 +5762,53 @@ class BasicSwap(BaseApp): save_bid = True if bid.initiate_tx.conf is not None: - self.log.debug('initiate_txnid %s confirms %d', initiate_txnid_hex, bid.initiate_tx.conf) + self.log.debug( + "initiate_txnid %s confirms %d", + initiate_txnid_hex, + bid.initiate_tx.conf, + ) - if (last_initiate_txn_conf is None or last_initiate_txn_conf < 1) and tx_height > 0: + if ( + last_initiate_txn_conf is None or last_initiate_txn_conf < 1 + ) and tx_height > 0: # Start checking for spends of initiate_txn before fully confirmed - bid.initiate_tx.chain_height = self.setLastHeightCheckedStart(coin_from, tx_height) + bid.initiate_tx.chain_height = self.setLastHeightCheckedStart( + coin_from, tx_height + ) self.setTxBlockInfoFromHeight(ci_from, bid.initiate_tx, tx_height) - self.addWatchedOutput(coin_from, bid_id, initiate_txnid_hex, bid.initiate_tx.vout, BidStates.SWAP_INITIATED) - if bid.getITxState() is None or bid.getITxState() < TxStates.TX_SENT: + self.addWatchedOutput( + coin_from, + bid_id, + initiate_txnid_hex, + bid.initiate_tx.vout, + BidStates.SWAP_INITIATED, + ) + if ( + bid.getITxState() is None + or bid.getITxState() < TxStates.TX_SENT + ): bid.setITxState(TxStates.TX_SENT) save_bid = True - if bid.initiate_tx.conf >= self.coin_clients[coin_from]['blocks_confirmed']: + if ( + bid.initiate_tx.conf + >= self.coin_clients[coin_from]["blocks_confirmed"] + ): self.initiateTxnConfirmed(bid_id, bid, offer) save_bid = True # Bid times out if buyer doesn't see tx in chain within INITIATE_TX_TIMEOUT seconds - if bid.initiate_tx is None and \ - bid.state_time + atomic_swap_1.INITIATE_TX_TIMEOUT < self.getTime(): - self.log.info('Swap timed out waiting for initiate tx for bid %s', bid_id.hex()) - bid.setState(BidStates.SWAP_TIMEDOUT, 'Timed out waiting for initiate tx') + if ( + bid.initiate_tx is None + and bid.state_time + atomic_swap_1.INITIATE_TX_TIMEOUT < self.getTime() + ): + self.log.info( + "Swap timed out waiting for initiate tx for bid %s", bid_id.hex() + ) + bid.setState( + BidStates.SWAP_TIMEDOUT, "Timed out waiting for initiate tx" + ) self.saveBid(bid_id, bid) return True # Mark bid for archiving elif state == BidStates.SWAP_INITIATED: @@ -4225,29 +5820,69 @@ class BasicSwap(BaseApp): addr = ci_to.encode_p2sh(bid.participate_tx.script) ci_to = self.ci(coin_to) - participate_txid = None if bid.participate_tx is None or bid.participate_tx.txid is None else bid.participate_tx.txid - participate_txvout = None if bid.participate_tx is None or bid.participate_tx.vout is None else bid.participate_tx.vout - found = ci_to.getLockTxHeight(participate_txid, addr, bid.amount_to, bid.chain_b_height_start, find_index=True, vout=participate_txvout) + participate_txid = ( + None + if bid.participate_tx is None or bid.participate_tx.txid is None + else bid.participate_tx.txid + ) + participate_txvout = ( + None + if bid.participate_tx is None or bid.participate_tx.vout is None + else bid.participate_tx.vout + ) + found = ci_to.getLockTxHeight( + participate_txid, + addr, + bid.amount_to, + bid.chain_b_height_start, + find_index=True, + vout=participate_txvout, + ) if found: - index = found.get('index', participate_txvout) - if bid.participate_tx.conf != found['depth']: + index = found.get("index", participate_txvout) + if bid.participate_tx.conf != found["depth"]: save_bid = True - if bid.participate_tx.conf is None and bid.participate_tx.state != TxStates.TX_SENT: - txid = found.get('txid', None if participate_txid is None else participate_txid.hex()) - self.log.debug('Found bid %s participate txn %s in chain %s', bid_id.hex(), txid, Coins(coin_to).name) - self.addParticipateTxn(bid_id, bid, coin_to, txid, index, found['height']) + if ( + bid.participate_tx.conf is None + and bid.participate_tx.state != TxStates.TX_SENT + ): + txid = found.get( + "txid", + None if participate_txid is None else participate_txid.hex(), + ) + self.log.debug( + "Found bid %s participate txn %s in chain %s", + bid_id.hex(), + txid, + Coins(coin_to).name, + ) + self.addParticipateTxn( + bid_id, bid, coin_to, txid, index, found["height"] + ) # Only update tx state if tx hasn't already been seen - if bid.participate_tx.state is None or bid.participate_tx.state < TxStates.TX_SENT: + if ( + bid.participate_tx.state is None + or bid.participate_tx.state < TxStates.TX_SENT + ): bid.setPTxState(TxStates.TX_SENT) - bid.participate_tx.conf = found['depth'] - if found['height'] > 0 and bid.participate_tx.block_height is None: - self.setTxBlockInfoFromHeight(ci_to, bid.participate_tx, found['height']) + bid.participate_tx.conf = found["depth"] + if found["height"] > 0 and bid.participate_tx.block_height is None: + self.setTxBlockInfoFromHeight( + ci_to, bid.participate_tx, found["height"] + ) if bid.participate_tx.conf is not None: - self.log.debug('participate txid %s confirms %d', bid.participate_tx.txid.hex(), bid.participate_tx.conf) - if bid.participate_tx.conf >= self.coin_clients[coin_to]['blocks_confirmed']: + self.log.debug( + "participate txid %s confirms %d", + bid.participate_tx.txid.hex(), + bid.participate_tx.conf, + ) + if ( + bid.participate_tx.conf + >= self.coin_clients[coin_to]["blocks_confirmed"] + ): self.participateTxnConfirmed(bid_id, bid, offer) save_bid = True elif state == BidStates.SWAP_PARTICIPATING: @@ -4257,18 +5892,33 @@ class BasicSwap(BaseApp): # Wait for user input pass else: - self.log.warning('checkBidState unknown state %s', state) + self.log.warning("checkBidState unknown state %s", state) if state > BidStates.BID_ACCEPTED: # Wait for spend of all known swap txns itx_state = bid.getITxState() ptx_state = bid.getPTxState() - if (itx_state is None or itx_state >= TxStates.TX_REDEEMED) and \ - (ptx_state is None or ptx_state >= TxStates.TX_REDEEMED): - self.log.info('Swap completed for bid %s', bid_id.hex()) + if (itx_state is None or itx_state >= TxStates.TX_REDEEMED) and ( + ptx_state is None or ptx_state >= TxStates.TX_REDEEMED + ): + self.log.info("Swap completed for bid %s", bid_id.hex()) - self.returnAddressToPool(bid_id, TxTypes.ITX_REFUND if itx_state == TxStates.TX_REDEEMED else TxTypes.PTX_REDEEM) - self.returnAddressToPool(bid_id, TxTypes.ITX_REFUND if ptx_state == TxStates.TX_REDEEMED else TxTypes.PTX_REDEEM) + self.returnAddressToPool( + bid_id, + ( + TxTypes.ITX_REFUND + if itx_state == TxStates.TX_REDEEMED + else TxTypes.PTX_REDEEM + ), + ) + self.returnAddressToPool( + bid_id, + ( + TxTypes.ITX_REFUND + if ptx_state == TxStates.TX_REDEEMED + else TxTypes.PTX_REDEEM + ), + ) bid.setState(BidStates.SWAP_COMPLETED) self.saveBid(bid_id, bid) @@ -4281,91 +5931,166 @@ class BasicSwap(BaseApp): return False # Bid is still active # Try refund, keep trying until sent tx is spent - if bid.getITxState() in (TxStates.TX_SENT, TxStates.TX_CONFIRMED) \ - and bid.initiate_txn_refund is not None: + if ( + bid.getITxState() in (TxStates.TX_SENT, TxStates.TX_CONFIRMED) + and bid.initiate_txn_refund is not None + ): try: txid = ci_from.publishTx(bid.initiate_txn_refund) - self.log.debug('Submitted initiate refund txn %s to %s chain for bid %s', txid, chainparams[coin_from]['name'], bid_id.hex()) - self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.ITX_REFUND_PUBLISHED, '', None) + self.log.debug( + "Submitted initiate refund txn %s to %s chain for bid %s", + txid, + chainparams[coin_from]["name"], + bid_id.hex(), + ) + self.logEvent( + Concepts.BID, + bid.bid_id, + EventLogTypes.ITX_REFUND_PUBLISHED, + "", + None, + ) # State will update when spend is detected except Exception as ex: if ci_from.isTxNonFinalError(str(ex)) is False: - self.log.warning('Error trying to submit initiate refund txn: %s', str(ex)) + self.log.warning( + "Error trying to submit initiate refund txn: %s", str(ex) + ) - if bid.getPTxState() in (TxStates.TX_SENT, TxStates.TX_CONFIRMED) \ - and bid.participate_txn_refund is not None: + if ( + bid.getPTxState() in (TxStates.TX_SENT, TxStates.TX_CONFIRMED) + and bid.participate_txn_refund is not None + ): try: txid = ci_to.publishTx(bid.participate_txn_refund) - self.log.debug('Submitted participate refund txn %s to %s chain for bid %s', txid, chainparams[coin_to]['name'], bid_id.hex()) - self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.PTX_REFUND_PUBLISHED, '', None) + self.log.debug( + "Submitted participate refund txn %s to %s chain for bid %s", + txid, + chainparams[coin_to]["name"], + bid_id.hex(), + ) + self.logEvent( + Concepts.BID, + bid.bid_id, + EventLogTypes.PTX_REFUND_PUBLISHED, + "", + None, + ) # State will update when spend is detected except Exception as ex: if ci_to.isTxNonFinalError(str(ex)): - self.log.warning('Error trying to submit participate refund txn: %s', str(ex)) + self.log.warning( + "Error trying to submit participate refund txn: %s", str(ex) + ) return False # Bid is still active def extractSecret(self, coin_type, bid, spend_in): try: - if coin_type in (Coins.DCR, ): - script_sig = spend_in['scriptSig']['asm'].split(' ') - ensure(len(script_sig) == 5, 'Bad witness size') + if coin_type in (Coins.DCR,): + script_sig = spend_in["scriptSig"]["asm"].split(" ") + ensure(len(script_sig) == 5, "Bad witness size") return bytes.fromhex(script_sig[2]) - elif coin_type in (Coins.PART, ) or self.coin_clients[coin_type]['use_segwit']: - ensure(len(spend_in['txinwitness']) == 5, 'Bad witness size') - return bytes.fromhex(spend_in['txinwitness'][2]) + elif ( + coin_type in (Coins.PART,) or self.coin_clients[coin_type]["use_segwit"] + ): + ensure(len(spend_in["txinwitness"]) == 5, "Bad witness size") + return bytes.fromhex(spend_in["txinwitness"][2]) else: - script_sig = spend_in['scriptSig']['asm'].split(' ') - ensure(len(script_sig) == 5, 'Bad witness size') + script_sig = spend_in["scriptSig"]["asm"].split(" ") + ensure(len(script_sig) == 5, "Bad witness size") return bytes.fromhex(script_sig[2]) except Exception: return None - def addWatchedOutput(self, coin_type, bid_id, txid_hex, vout, tx_type, swap_type=None): - self.log.debug('Adding watched output %s bid %s tx %s type %s', Coins(coin_type).name, bid_id.hex(), txid_hex, tx_type) + def addWatchedOutput( + self, coin_type, bid_id, txid_hex, vout, tx_type, swap_type=None + ): + self.log.debug( + "Adding watched output %s bid %s tx %s type %s", + Coins(coin_type).name, + bid_id.hex(), + txid_hex, + tx_type, + ) - watched = self.coin_clients[coin_type]['watched_outputs'] + watched = self.coin_clients[coin_type]["watched_outputs"] for wo in watched: if wo.bid_id == bid_id and wo.txid_hex == txid_hex and wo.vout == vout: - self.log.debug('Output already being watched.') + self.log.debug("Output already being watched.") return watched.append(WatchedOutput(bid_id, txid_hex, vout, tx_type, swap_type)) def removeWatchedOutput(self, coin_type, bid_id: bytes, txid_hex: str) -> None: # Remove all for bid if txid is None - self.log.debug('removeWatchedOutput %s %s %s', Coins(coin_type).name, bid_id.hex(), txid_hex) - old_len = len(self.coin_clients[coin_type]['watched_outputs']) + self.log.debug( + "removeWatchedOutput %s %s %s", + Coins(coin_type).name, + bid_id.hex(), + txid_hex, + ) + old_len = len(self.coin_clients[coin_type]["watched_outputs"]) for i in range(old_len - 1, -1, -1): - wo = self.coin_clients[coin_type]['watched_outputs'][i] + wo = self.coin_clients[coin_type]["watched_outputs"][i] if wo.bid_id == bid_id and (txid_hex is None or wo.txid_hex == txid_hex): - del self.coin_clients[coin_type]['watched_outputs'][i] - self.log.debug('Removed watched output %s %s %s', Coins(coin_type).name, bid_id.hex(), wo.txid_hex) + del self.coin_clients[coin_type]["watched_outputs"][i] + self.log.debug( + "Removed watched output %s %s %s", + Coins(coin_type).name, + bid_id.hex(), + wo.txid_hex, + ) - def addWatchedScript(self, coin_type, bid_id, script: bytes, tx_type, swap_type=None): - self.log.debug('Adding watched script %s bid %s type %s', Coins(coin_type).name, bid_id.hex(), tx_type) + def addWatchedScript( + self, coin_type, bid_id, script: bytes, tx_type, swap_type=None + ): + self.log.debug( + "Adding watched script %s bid %s type %s", + Coins(coin_type).name, + bid_id.hex(), + tx_type, + ) - watched = self.coin_clients[coin_type]['watched_scripts'] + watched = self.coin_clients[coin_type]["watched_scripts"] for ws in watched: if ws.bid_id == bid_id and ws.tx_type == tx_type and ws.script == script: - self.log.debug('Script already being watched.') + self.log.debug("Script already being watched.") return watched.append(WatchedScript(bid_id, script, tx_type, swap_type)) - def removeWatchedScript(self, coin_type, bid_id: bytes, script: bytes, tx_type: TxTypes = None) -> None: + def removeWatchedScript( + self, coin_type, bid_id: bytes, script: bytes, tx_type: TxTypes = None + ) -> None: # Remove all for bid if script and type_ind is None - self.log.debug('removeWatchedScript {} {}{}'.format(Coins(coin_type).name, bid_id.hex(), (' type ' + str(tx_type)) if tx_type is not None else '')) - old_len = len(self.coin_clients[coin_type]['watched_scripts']) + self.log.debug( + "removeWatchedScript {} {}{}".format( + Coins(coin_type).name, + bid_id.hex(), + (" type " + str(tx_type)) if tx_type is not None else "", + ) + ) + old_len = len(self.coin_clients[coin_type]["watched_scripts"]) for i in range(old_len - 1, -1, -1): - ws = self.coin_clients[coin_type]['watched_scripts'][i] - if ws.bid_id == bid_id and (script is None or ws.script == script) and (tx_type is None or ws.tx_type == tx_type): - del self.coin_clients[coin_type]['watched_scripts'][i] - self.log.debug('Removed watched script %s %s', Coins(coin_type).name, bid_id.hex()) + ws = self.coin_clients[coin_type]["watched_scripts"][i] + if ( + ws.bid_id == bid_id + and (script is None or ws.script == script) + and (tx_type is None or ws.tx_type == tx_type) + ): + del self.coin_clients[coin_type]["watched_scripts"][i] + self.log.debug( + "Removed watched script %s %s", Coins(coin_type).name, bid_id.hex() + ) - def initiateTxnSpent(self, bid_id: bytes, spend_txid: str, spend_n: int, spend_txn) -> None: - self.log.debug('Bid %s initiate txn spent by %s %d', bid_id.hex(), spend_txid, spend_n) + def initiateTxnSpent( + self, bid_id: bytes, spend_txid: str, spend_n: int, spend_txn + ) -> None: + self.log.debug( + "Bid %s initiate txn spent by %s %d", bid_id.hex(), spend_txid, spend_n + ) if bid_id in self.swaps_in_progress: bid = self.swaps_in_progress[bid_id][0] @@ -4373,26 +6098,39 @@ class BasicSwap(BaseApp): bid.initiate_tx.spend_txid = bytes.fromhex(spend_txid) bid.initiate_tx.spend_n = spend_n - spend_in = spend_txn['vin'][spend_n] + spend_in = spend_txn["vin"][spend_n] coin_from = Coins(offer.coin_from) - coin_to = Coins(offer.coin_to) secret = self.extractSecret(coin_from, bid, spend_in) if secret is None: - self.log.info('Bid %s initiate txn refunded by %s %d', bid_id.hex(), spend_txid, spend_n) + self.log.info( + "Bid %s initiate txn refunded by %s %d", + bid_id.hex(), + spend_txid, + spend_n, + ) # TODO: Wait for depth? bid.setITxState(TxStates.TX_REFUNDED) else: - self.log.info('Bid %s initiate txn redeemed by %s %d', bid_id.hex(), spend_txid, spend_n) + self.log.info( + "Bid %s initiate txn redeemed by %s %d", + bid_id.hex(), + spend_txid, + spend_n, + ) # TODO: Wait for depth? bid.setITxState(TxStates.TX_REDEEMED) self.removeWatchedOutput(coin_from, bid_id, bid.initiate_tx.txid.hex()) self.saveBid(bid_id, bid) - def participateTxnSpent(self, bid_id: bytes, spend_txid: str, spend_n: int, spend_txn) -> None: - self.log.debug('Bid %s participate txn spent by %s %d', bid_id.hex(), spend_txid, spend_n) + def participateTxnSpent( + self, bid_id: bytes, spend_txid: str, spend_n: int, spend_txn + ) -> None: + self.log.debug( + "Bid %s participate txn spent by %s %d", bid_id.hex(), spend_txid, spend_n + ) # TODO: More SwapTypes if bid_id in self.swaps_in_progress: @@ -4401,56 +6139,86 @@ class BasicSwap(BaseApp): bid.participate_tx.spend_txid = bytes.fromhex(spend_txid) bid.participate_tx.spend_n = spend_n - spend_in = spend_txn['vin'][spend_n] + spend_in = spend_txn["vin"][spend_n] - coin_from = Coins(offer.coin_from) coin_to = Coins(offer.coin_to) secret = self.extractSecret(coin_to, bid, spend_in) if secret is None: - self.log.info('Bid %s participate txn refunded by %s %d', bid_id.hex(), spend_txid, spend_n) + self.log.info( + "Bid %s participate txn refunded by %s %d", + bid_id.hex(), + spend_txid, + spend_n, + ) # TODO: Wait for depth? bid.setPTxState(TxStates.TX_REFUNDED) else: - self.log.debug('Secret %s extracted from participate spend %s %d', secret.hex(), spend_txid, spend_n) + self.log.debug( + "Secret %s extracted from participate spend %s %d", + secret.hex(), + spend_txid, + spend_n, + ) bid.recovered_secret = secret # TODO: Wait for depth? bid.setPTxState(TxStates.TX_REDEEMED) if bid.was_sent: if bid.debug_ind == DebugTypes.DONT_SPEND_ITX: - self.log.debug('bid %s: Abandoning bid for testing: %d, %s.', bid_id.hex(), bid.debug_ind, DebugTypes(bid.debug_ind).name) + self.log.debug( + "bid %s: Abandoning bid for testing: %d, %s.", + bid_id.hex(), + bid.debug_ind, + DebugTypes(bid.debug_ind).name, + ) bid.setState(BidStates.BID_ABANDONED) - self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), None) + self.logBidEvent( + bid.bid_id, + EventLogTypes.DEBUG_TWEAK_APPLIED, + "ind {}".format(bid.debug_ind), + None, + ) else: delay = self.get_short_delay_event_seconds() - self.log.info('Redeeming ITX for bid %s in %d seconds', bid_id.hex(), delay) + self.log.info( + "Redeeming ITX for bid %s in %d seconds", + bid_id.hex(), + delay, + ) self.createAction(delay, ActionTypes.REDEEM_ITX, bid_id) # TODO: Wait for depth? new state SWAP_TXI_REDEEM_SENT? self.removeWatchedOutput(coin_to, bid_id, bid.participate_tx.txid.hex()) self.saveBid(bid_id, bid) - def process_XMR_SWAP_A_LOCK_tx_spend(self, bid_id: bytes, spend_txid_hex, spend_txn_hex, session=None) -> None: - self.log.debug('Detected spend of Adaptor-sig swap coin a lock tx for bid %s', bid_id.hex()) + def process_XMR_SWAP_A_LOCK_tx_spend( + self, bid_id: bytes, spend_txid_hex, spend_txn_hex, session=None + ) -> None: + self.log.debug( + "Detected spend of Adaptor-sig swap coin a lock tx for bid %s", bid_id.hex() + ) try: use_session = self.openSession(session) bid, xmr_swap = self.getXmrBidFromSession(use_session, bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) if BidStates(bid.state) == BidStates.BID_STALLED_FOR_TEST: - self.log.debug('Bid stalled %s', bid_id.hex()) + self.log.debug("Bid stalled %s", bid_id.hex()) return - offer, xmr_offer = self.getXmrOfferFromSession(use_session, bid.offer_id, sent=False) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + offer, xmr_offer = self.getXmrOfferFromSession( + use_session, bid.offer_id, sent=False + ) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure( + xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex()) + ) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) was_received: bool = bid.was_sent if reverse_bid else bid.was_received coin_from = Coins(offer.coin_to if reverse_bid else offer.coin_from) - coin_to = Coins(offer.coin_from if reverse_bid else offer.coin_to) state = BidStates(bid.state) spending_txid = bytes.fromhex(spend_txid_hex) @@ -4464,7 +6232,10 @@ class BasicSwap(BaseApp): if self.isBchXmrSwap(offer): is_spending_lock_tx = self.ci(coin_from).isSpendingLockTx(spend_tx) - if spending_txid == xmr_swap.a_lock_spend_tx_id or (i2b(spend_tx.vin[0].prevout.hash) == xmr_swap.a_lock_tx_id and is_spending_lock_tx): + if spending_txid == xmr_swap.a_lock_spend_tx_id or ( + i2b(spend_tx.vin[0].prevout.hash) == xmr_swap.a_lock_tx_id + and is_spending_lock_tx + ): # bch txids change if self.isBchXmrSwap(offer): xmr_swap.a_lock_spend_tx_id = spending_txid @@ -4472,7 +6243,9 @@ class BasicSwap(BaseApp): if state == BidStates.XMR_SWAP_LOCK_RELEASED: xmr_swap.a_lock_spend_tx = bytes.fromhex(spend_txn_hex) - bid.setState(BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED) # TODO: Wait for confirmation? + bid.setState( + BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED + ) # TODO: Wait for confirmation? if bid.xmr_a_lock_tx: bid.xmr_a_lock_tx.setState(TxStates.TX_REDEEMED) @@ -4481,13 +6254,22 @@ class BasicSwap(BaseApp): bid.setState(BidStates.SWAP_COMPLETED) else: # Could already be processed if spend was detected in the mempool - self.log.warning('Coin a lock tx spend ignored due to bid state for bid {}'.format(bid_id.hex())) + self.log.warning( + "Coin a lock tx spend ignored due to bid state for bid {}".format( + bid_id.hex() + ) + ) - elif spending_txid == xmr_swap.a_lock_refund_tx_id or (i2b(spend_tx.vin[0].prevout.hash) == xmr_swap.a_lock_tx_id and not is_spending_lock_tx): - self.log.debug('Coin a lock tx spent by lock refund tx.') + elif spending_txid == xmr_swap.a_lock_refund_tx_id or ( + i2b(spend_tx.vin[0].prevout.hash) == xmr_swap.a_lock_tx_id + and not is_spending_lock_tx + ): + self.log.debug("Coin a lock tx spent by lock refund tx.") # bch txids change if self.isBchXmrSwap(offer): - self.log.debug('Recomputing refund spend transaction and txid after lock tx spent.') + self.log.debug( + "Recomputing refund spend transaction and txid after lock tx spent." + ) xmr_swap.a_lock_refund_tx_id = spending_txid xmr_swap.a_lock_refund_tx = bytes.fromhex(spend_txn_hex) @@ -4495,35 +6277,60 @@ class BasicSwap(BaseApp): tx = ci_from.loadTx(xmr_swap.a_lock_refund_spend_tx) tx.vin[0].prevout.hash = b2i(xmr_swap.a_lock_refund_tx_id) xmr_swap.a_lock_refund_spend_tx = tx.serialize_without_witness() - xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_spend_tx) + xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxid( + xmr_swap.a_lock_refund_spend_tx + ) if was_received: refund_to_script = ci_from.getRefundOutputScript(xmr_swap) - self.addWatchedScript(ci_from.coin_type(), bid_id, refund_to_script, TxTypes.BCH_MERCY) + self.addWatchedScript( + ci_from.coin_type(), + bid_id, + refund_to_script, + TxTypes.BCH_MERCY, + ) bid.setState(BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND) - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_TX_SEEN, '', use_session) + self.logBidEvent( + bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_TX_SEEN, "", use_session + ) else: - self.setBidError(bid.bid_id, bid, 'Unexpected txn spent coin a lock tx: {}'.format(spend_txid_hex), save_bid=False) + self.setBidError( + bid.bid_id, + bid, + "Unexpected txn spent coin a lock tx: {}".format(spend_txid_hex), + save_bid=False, + ) - self.saveBidInSession(bid_id, bid, use_session, xmr_swap, save_in_progress=offer) + self.saveBidInSession( + bid_id, bid, use_session, xmr_swap, save_in_progress=offer + ) except Exception as ex: - self.logException(f'process_XMR_SWAP_A_LOCK_tx_spend {ex}') + self.logException(f"process_XMR_SWAP_A_LOCK_tx_spend {ex}") finally: if session is None: self.closeSession(use_session) - def process_XMR_SWAP_A_LOCK_REFUND_tx_spend(self, bid_id: bytes, spend_txid_hex: str, spend_txn) -> None: - self.log.debug('Detected spend of Adaptor-sig swap coin a lock refund tx for bid %s', bid_id.hex()) + def process_XMR_SWAP_A_LOCK_REFUND_tx_spend( + self, bid_id: bytes, spend_txid_hex: str, spend_txn + ) -> None: + self.log.debug( + "Detected spend of Adaptor-sig swap coin a lock refund tx for bid %s", + bid_id.hex(), + ) try: session = self.openSession() bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession(session, bid.offer_id, sent=False) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + offer, xmr_offer = self.getXmrOfferFromSession( + session, bid.offer_id, sent=False + ) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure( + xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex()) + ) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) coin_from = Coins(offer.coin_to if reverse_bid else offer.coin_from) @@ -4533,18 +6340,22 @@ class BasicSwap(BaseApp): ci_from = self.ci(coin_from) - state = BidStates(bid.state) spending_txid = bytes.fromhex(spend_txid_hex) - spend_txn_hex = spend_txn['hex'] + spend_txn_hex = spend_txn["hex"] spend_tx = ci_from.loadTx(h2b(spend_txn_hex)) is_spending_lock_refund_tx = False if self.isBchXmrSwap(offer): is_spending_lock_refund_tx = ci_from.isSpendingLockRefundTx(spend_tx) - if spending_txid == xmr_swap.a_lock_refund_spend_tx_id or (i2b(spend_tx.vin[0].prevout.hash) == xmr_swap.a_lock_refund_tx_id and is_spending_lock_refund_tx): - self.log.info('Found coin a lock refund spend tx, bid {}'.format(bid_id.hex())) + if spending_txid == xmr_swap.a_lock_refund_spend_tx_id or ( + i2b(spend_tx.vin[0].prevout.hash) == xmr_swap.a_lock_refund_tx_id + and is_spending_lock_refund_tx + ): + self.log.info( + "Found coin a lock refund spend tx, bid {}".format(bid_id.hex()) + ) # bch txids change if self.isBchXmrSwap(offer): @@ -4552,15 +6363,24 @@ class BasicSwap(BaseApp): xmr_swap.a_lock_refund_spend_tx = bytes.fromhex(spend_txn_hex) if was_received: - self.removeWatchedScript(coin_from, bid_id, None, TxTypes.BCH_MERCY) + self.removeWatchedScript( + coin_from, bid_id, None, TxTypes.BCH_MERCY + ) - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_SEEN, '', session) + self.logBidEvent( + bid.bid_id, + EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_SEEN, + "", + session, + ) if bid.xmr_a_lock_tx: bid.xmr_a_lock_tx.setState(TxStates.TX_REFUNDED) if was_sent: - xmr_swap.a_lock_refund_spend_tx = bytes.fromhex(spend_txn_hex) # Replace with fully signed tx + xmr_swap.a_lock_refund_spend_tx = bytes.fromhex( + spend_txn_hex + ) # Replace with fully signed tx if TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND not in bid.txns: bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND] = SwapTx( bid_id=bid_id, @@ -4569,8 +6389,17 @@ class BasicSwap(BaseApp): ) if bid.xmr_b_lock_tx is not None: delay = self.get_delay_event_seconds() - self.log.info('Recovering adaptor-sig swap chain B lock tx for bid %s in %d seconds', bid_id.hex(), delay) - self.createActionInSession(delay, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B, bid_id, session) + self.log.info( + "Recovering adaptor-sig swap chain B lock tx for bid %s in %d seconds", + bid_id.hex(), + delay, + ) + self.createActionInSession( + delay, + ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B, + bid_id, + session, + ) else: # Other side refunded before swap lock tx was sent bid.setState(BidStates.XMR_SWAP_FAILED) @@ -4580,7 +6409,11 @@ class BasicSwap(BaseApp): bid.setState(BidStates.XMR_SWAP_FAILED_REFUNDED) else: - self.log.info('Coin a lock refund spent by unknown tx, bid {}'.format(bid_id.hex())) + self.log.info( + "Coin a lock refund spent by unknown tx, bid {}".format( + bid_id.hex() + ) + ) mercy_keyshare = None if was_received: @@ -4592,17 +6425,33 @@ class BasicSwap(BaseApp): try: mercy_keyshare = ci_from.inspectSwipeTx(spend_txn) if mercy_keyshare is None: - raise ValueError('Not found') - ensure(self.ci(coin_to).verifyKey(mercy_keyshare), 'Invalid keyshare') + raise ValueError("Not found") + ensure( + self.ci(coin_to).verifyKey(mercy_keyshare), + "Invalid keyshare", + ) except Exception as e: - self.log.warning('Could not extract mercy output from swipe tx: {}, {}'.format(spend_txid_hex, e)) + self.log.warning( + "Could not extract mercy output from swipe tx: {}, {}".format( + spend_txid_hex, e + ) + ) if mercy_keyshare is None: bid.setState(BidStates.XMR_SWAP_FAILED_SWIPED) else: delay = self.get_delay_event_seconds() - self.log.info('Redeeming coin b lock tx for bid %s in %d seconds', bid_id.hex(), delay) - self.createActionInSession(delay, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, session) + self.log.info( + "Redeeming coin b lock tx for bid %s in %d seconds", + bid_id.hex(), + delay, + ) + self.createActionInSession( + delay, + ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B, + bid_id, + session, + ) else: bid.setState(BidStates.XMR_SWAP_FAILED_SWIPED) @@ -4613,30 +6462,48 @@ class BasicSwap(BaseApp): txid=spending_txid, ) if mercy_keyshare: - bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE].tx_data = mercy_keyshare + bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE].tx_data = ( + mercy_keyshare + ) - self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) + self.saveBidInSession( + bid_id, bid, session, xmr_swap, save_in_progress=offer + ) except Exception as ex: - self.logException(f'process_XMR_SWAP_A_LOCK_REFUND_tx_spend {ex}') + self.logException(f"process_XMR_SWAP_A_LOCK_REFUND_tx_spend {ex}") finally: self.closeSession(session) - def processSpentOutput(self, coin_type, watched_output, spend_txid_hex, spend_n, spend_txn) -> None: + def processSpentOutput( + self, coin_type, watched_output, spend_txid_hex, spend_n, spend_txn + ) -> None: if watched_output.swap_type == SwapTypes.XMR_SWAP: if watched_output.tx_type == TxTypes.XMR_SWAP_A_LOCK: - self.process_XMR_SWAP_A_LOCK_tx_spend(watched_output.bid_id, spend_txid_hex, spend_txn['hex']) + self.process_XMR_SWAP_A_LOCK_tx_spend( + watched_output.bid_id, spend_txid_hex, spend_txn["hex"] + ) elif watched_output.tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND: - self.process_XMR_SWAP_A_LOCK_REFUND_tx_spend(watched_output.bid_id, spend_txid_hex, spend_txn) + self.process_XMR_SWAP_A_LOCK_REFUND_tx_spend( + watched_output.bid_id, spend_txid_hex, spend_txn + ) - self.removeWatchedOutput(coin_type, watched_output.bid_id, watched_output.txid_hex) + self.removeWatchedOutput( + coin_type, watched_output.bid_id, watched_output.txid_hex + ) return if watched_output.tx_type == BidStates.SWAP_PARTICIPATING: - self.participateTxnSpent(watched_output.bid_id, spend_txid_hex, spend_n, spend_txn) + self.participateTxnSpent( + watched_output.bid_id, spend_txid_hex, spend_n, spend_txn + ) else: - self.initiateTxnSpent(watched_output.bid_id, spend_txid_hex, spend_n, spend_txn) + self.initiateTxnSpent( + watched_output.bid_id, spend_txid_hex, spend_n, spend_txn + ) - def processFoundScript(self, coin_type, watched_script, txid: bytes, vout: int) -> None: + def processFoundScript( + self, coin_type, watched_script, txid: bytes, vout: int + ) -> None: if watched_script.tx_type == TxTypes.PTX: if watched_script.bid_id in self.swaps_in_progress: bid = self.swaps_in_progress[watched_script.bid_id][0] @@ -4647,17 +6514,33 @@ class BasicSwap(BaseApp): self.saveBid(watched_script.bid_id, bid) else: - self.log.warning('Could not find active bid for found watched script: {}'.format(watched_script.bid_id.hex())) + self.log.warning( + "Could not find active bid for found watched script: {}".format( + watched_script.bid_id.hex() + ) + ) elif watched_script.tx_type == TxTypes.XMR_SWAP_A_LOCK: - self.log.info('Found chain A lock txid {} for bid: {}'.format(txid.hex(), watched_script.bid_id.hex())) + self.log.info( + "Found chain A lock txid {} for bid: {}".format( + txid.hex(), watched_script.bid_id.hex() + ) + ) bid = self.swaps_in_progress[watched_script.bid_id][0] if bid.xmr_a_lock_tx.txid != txid: - self.log.debug('Updating xmr_a_lock_tx from {} to {}'.format(hex_or_none(bid.xmr_a_lock_tx.txid), txid.hex())) + self.log.debug( + "Updating xmr_a_lock_tx from {} to {}".format( + hex_or_none(bid.xmr_a_lock_tx.txid), txid.hex() + ) + ) bid.xmr_a_lock_tx.txid = txid bid.xmr_a_lock_tx.vout = vout self.saveBid(watched_script.bid_id, bid) elif watched_script.tx_type == TxTypes.XMR_SWAP_B_LOCK: - self.log.info('Found chain B lock txid {} for bid: {}'.format(txid.hex(), watched_script.bid_id.hex())) + self.log.info( + "Found chain B lock txid {} for bid: {}".format( + txid.hex(), watched_script.bid_id.hex() + ) + ) bid = self.swaps_in_progress[watched_script.bid_id][0] bid.xmr_b_lock_tx = SwapTx( bid_id=watched_script.bid_id, @@ -4666,35 +6549,54 @@ class BasicSwap(BaseApp): vout=vout, ) if bid.xmr_b_lock_tx.txid != txid: - self.log.debug('Updating xmr_b_lock_tx from {} to {}'.format(hex_or_none(bid.xmr_b_lock_tx.txid), txid.hex())) + self.log.debug( + "Updating xmr_b_lock_tx from {} to {}".format( + hex_or_none(bid.xmr_b_lock_tx.txid), txid.hex() + ) + ) bid.xmr_b_lock_tx.txid = txid bid.xmr_b_lock_tx.vout = vout bid.xmr_b_lock_tx.setState(TxStates.TX_IN_CHAIN) self.saveBid(watched_script.bid_id, bid) else: - self.log.warning('Unknown found watched script tx type for bid {}'.format(watched_script.bid_id.hex())) + self.log.warning( + "Unknown found watched script tx type for bid {}".format( + watched_script.bid_id.hex() + ) + ) - self.removeWatchedScript(coin_type, watched_script.bid_id, watched_script.script) + self.removeWatchedScript( + coin_type, watched_script.bid_id, watched_script.script + ) - def processMercyTx(self, coin_type, watched_script, txid: bytes, vout: int, tx) -> None: + def processMercyTx( + self, coin_type, watched_script, txid: bytes, vout: int, tx + ) -> None: bid_id = watched_script.bid_id ci = self.ci(coin_type) - if len(tx['vout']) < 2 or \ - ci.make_int(tx['vout'][vout]['value']) != 546: # Dust limit + if ( + len(tx["vout"]) < 2 or ci.make_int(tx["vout"][vout]["value"]) != 546 + ): # Dust limit - self.log.info('Found tx is not a mercy tx for bid: {}'.format(bid_id.hex())) + self.log.info("Found tx is not a mercy tx for bid: {}".format(bid_id.hex())) self.removeWatchedScript(coin_type, bid_id, watched_script.script) return - self.log.info('Found mercy tx for bid: {}'.format(bid_id.hex())) + self.log.info("Found mercy tx for bid: {}".format(bid_id.hex())) - self.logBidEvent(bid_id, EventLogTypes.BCH_MERCY_TX_FOUND, txid.hex(), session=None) + self.logBidEvent( + bid_id, EventLogTypes.BCH_MERCY_TX_FOUND, txid.hex(), session=None + ) if bid_id not in self.swaps_in_progress: - self.log.warning('Could not find active bid for found mercy tx: {}'.format(bid_id.hex())) + self.log.warning( + "Could not find active bid for found mercy tx: {}".format(bid_id.hex()) + ) else: - mercy_keyshare = bytes.fromhex(tx['vout'][0]['scriptPubKey']['asm'].split(' ')[2]) - ensure(ci.verifyKey(mercy_keyshare), 'Invalid keyshare') + mercy_keyshare = bytes.fromhex( + tx["vout"][0]["scriptPubKey"]["asm"].split(" ")[2] + ) + ensure(ci.verifyKey(mercy_keyshare), "Invalid keyshare") bid = self.swaps_in_progress[bid_id][0] bid.txns[TxTypes.BCH_MERCY] = SwapTx( @@ -4706,7 +6608,9 @@ class BasicSwap(BaseApp): self.saveBid(bid_id, bid) delay = self.get_delay_event_seconds() - self.log.info('Redeeming coin b lock tx for bid %s in %d seconds', bid_id.hex(), delay) + self.log.info( + "Redeeming coin b lock tx for bid %s in %d seconds", bid_id.hex(), delay + ) self.createAction(delay, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id) self.removeWatchedScript(coin_type, bid_id, watched_script.script) @@ -4715,11 +6619,16 @@ class BasicSwap(BaseApp): pass def haveCheckedPrevBlock(self, ci, c, block, session=None) -> bool: - previousblockhash = bytes.fromhex(block['previousblockhash']) + previousblockhash = bytes.fromhex(block["previousblockhash"]) try: use_session = self.openSession(session) - q = use_session.execute(text('SELECT COUNT(*) FROM checkedblocks WHERE block_hash = :block_hash'), {'block_hash': previousblockhash}).first() + q = use_session.execute( + text( + "SELECT COUNT(*) FROM checkedblocks WHERE block_hash = :block_hash" + ), + {"block_hash": previousblockhash}, + ).first() if q[0] > 0: return True @@ -4734,14 +6643,27 @@ class BasicSwap(BaseApp): try: use_session = self.openSession(session) - block_height = int(block['height']) - if cc['last_height_checked'] != block_height: - cc['last_height_checked'] = block_height - self.setIntKV('last_height_checked_' + ci.coin_name().lower(), block_height, session=use_session) + block_height = int(block["height"]) + if cc["last_height_checked"] != block_height: + cc["last_height_checked"] = block_height + self.setIntKV( + "last_height_checked_" + ci.coin_name().lower(), + block_height, + session=use_session, + ) - query = '''INSERT INTO checkedblocks (created_at, coin_type, block_height, block_hash, block_time) - VALUES (:now, :coin_type, :block_height, :block_hash, :block_time)''' - use_session.execute(text(query), {'now': now, 'coin_type': int(ci.coin_type()), 'block_height': block_height, 'block_hash': bytes.fromhex(block['hash']), 'block_time': int(block['time'])}) + query = """INSERT INTO checkedblocks (created_at, coin_type, block_height, block_hash, block_time) + VALUES (:now, :coin_type, :block_height, :block_hash, :block_time)""" + use_session.execute( + text(query), + { + "now": now, + "coin_type": int(ci.coin_type()), + "block_height": block_height, + "block_hash": bytes.fromhex(block["hash"]), + "block_time": int(block["time"]), + }, + ) finally: if session is None: @@ -4749,31 +6671,49 @@ class BasicSwap(BaseApp): def checkForSpends(self, coin_type, c): # assert (self.mxDB.locked()) - self.log.debug('checkForSpends %s', Coins(coin_type).name) + self.log.debug("checkForSpends %s", Coins(coin_type).name) # TODO: Check for spends on watchonly txns where possible - if self.coin_clients[coin_type].get('have_spent_index', False): + if self.coin_clients[coin_type].get("have_spent_index", False): # TODO: batch getspentinfo - for o in c['watched_outputs']: + for o in c["watched_outputs"]: found_spend = None try: - found_spend = self.callcoinrpc(Coins.PART, 'getspentinfo', [{'txid': o.txid_hex, 'index': o.vout}]) + found_spend = self.callcoinrpc( + Coins.PART, + "getspentinfo", + [{"txid": o.txid_hex, "index": o.vout}], + ) except Exception as ex: - if 'Unable to get spent info' not in str(ex): - self.log.warning('getspentinfo %s', str(ex)) + if "Unable to get spent info" not in str(ex): + self.log.warning("getspentinfo %s", str(ex)) if found_spend is not None: - self.log.debug('Found spend in spentindex %s %d in %s %d', o.txid_hex, o.vout, found_spend['txid'], found_spend['index']) - spend_txid = found_spend['txid'] - spend_n = found_spend['index'] - spend_txn = self.callcoinrpc(Coins.PART, 'getrawtransaction', [spend_txid, True]) - self.processSpentOutput(coin_type, o, spend_txid, spend_n, spend_txn) + self.log.debug( + "Found spend in spentindex %s %d in %s %d", + o.txid_hex, + o.vout, + found_spend["txid"], + found_spend["index"], + ) + spend_txid = found_spend["txid"] + spend_n = found_spend["index"] + spend_txn = self.callcoinrpc( + Coins.PART, "getrawtransaction", [spend_txid, True] + ) + self.processSpentOutput( + coin_type, o, spend_txid, spend_n, spend_txn + ) return ci = self.ci(coin_type) chain_blocks = ci.getChainHeight() - last_height_checked: int = c['last_height_checked'] - block_check_min_time: int = c['block_check_min_time'] - self.log.debug('{} chain_blocks, last_height_checked {} {}'.format(ci.ticker(), chain_blocks, last_height_checked)) + last_height_checked: int = c["last_height_checked"] + block_check_min_time: int = c["block_check_min_time"] + self.log.debug( + "{} chain_blocks, last_height_checked {} {}".format( + ci.ticker(), chain_blocks, last_height_checked + ) + ) blocks_checked: int = 0 while last_height_checked < chain_blocks: @@ -4781,59 +6721,91 @@ class BasicSwap(BaseApp): break blocks_checked += 1 if blocks_checked % 10000 == 0: - self.log.debug('{} chain_blocks, last_height_checked, blocks_checked {} {} {}'.format(ci.ticker(), chain_blocks, last_height_checked, blocks_checked)) + self.log.debug( + "{} chain_blocks, last_height_checked, blocks_checked {} {} {}".format( + ci.ticker(), chain_blocks, last_height_checked, blocks_checked + ) + ) if blocks_checked > self._max_check_loop_blocks: - self.log.debug('Hit max_check_loop_blocks for {} chain_blocks, last_height_checked {} {}'.format(ci.ticker(), chain_blocks, last_height_checked)) + self.log.debug( + "Hit max_check_loop_blocks for {} chain_blocks, last_height_checked {} {}".format( + ci.ticker(), chain_blocks, last_height_checked + ) + ) break - block_hash = ci.rpc('getblockhash', [last_height_checked + 1]) + block_hash = ci.rpc("getblockhash", [last_height_checked + 1]) try: block = ci.getBlockWithTxns(block_hash) except Exception as e: - if 'Block not available (pruned data)' in str(e): + if "Block not available (pruned data)" in str(e): # TODO: Better solution? bci = ci.getBlockchainInfo() - self.log.error('Coin %s last_height_checked %d set to pruneheight %d', ci.coin_name(), last_height_checked, bci['pruneheight']) - last_height_checked = bci['pruneheight'] + self.log.error( + "Coin %s last_height_checked %d set to pruneheight %d", + ci.coin_name(), + last_height_checked, + bci["pruneheight"], + ) + last_height_checked = bci["pruneheight"] continue else: - self.logException(f'getblock error {e}') + self.logException(f"getblock error {e}") break - if block_check_min_time > block['time'] or last_height_checked < 1: + if block_check_min_time > block["time"] or last_height_checked < 1: pass elif not self.haveCheckedPrevBlock(ci, c, block): last_height_checked -= 1 - self.log.debug('Have not seen previousblockhash {} for block {}'.format(block['previousblockhash'], block['hash'])) + self.log.debug( + "Have not seen previousblockhash {} for block {}".format( + block["previousblockhash"], block["hash"] + ) + ) continue - for tx in block['tx']: - for s in c['watched_scripts']: - for i, txo in enumerate(tx['vout']): - if 'scriptPubKey' in txo and 'hex' in txo['scriptPubKey']: + for tx in block["tx"]: + for s in c["watched_scripts"]: + for i, txo in enumerate(tx["vout"]): + if "scriptPubKey" in txo and "hex" in txo["scriptPubKey"]: # TODO: Optimise by loading rawtx in CTransaction - if bytes.fromhex(txo['scriptPubKey']['hex']) == s.script: - self.log.debug('Found script from search for bid %s: %s %d', s.bid_id.hex(), tx['txid'], i) + if bytes.fromhex(txo["scriptPubKey"]["hex"]) == s.script: + self.log.debug( + "Found script from search for bid %s: %s %d", + s.bid_id.hex(), + tx["txid"], + i, + ) if s.tx_type == TxTypes.BCH_MERCY: - self.processMercyTx(coin_type, s, bytes.fromhex(tx['txid']), i, tx) + self.processMercyTx( + coin_type, s, bytes.fromhex(tx["txid"]), i, tx + ) else: - self.processFoundScript(coin_type, s, bytes.fromhex(tx['txid']), i) + self.processFoundScript( + coin_type, s, bytes.fromhex(tx["txid"]), i + ) - for o in c['watched_outputs']: - for i, inp in enumerate(tx['vin']): - inp_txid = inp.get('txid', None) + for o in c["watched_outputs"]: + for i, inp in enumerate(tx["vin"]): + inp_txid = inp.get("txid", None) if inp_txid is None: # Coinbase continue - if inp_txid == o.txid_hex and inp['vout'] == o.vout: - self.log.debug('Found spend from search %s %d in %s %d', o.txid_hex, o.vout, tx['txid'], i) - self.processSpentOutput(coin_type, o, tx['txid'], i, tx) + if inp_txid == o.txid_hex and inp["vout"] == o.vout: + self.log.debug( + "Found spend from search %s %d in %s %d", + o.txid_hex, + o.vout, + tx["txid"], + i, + ) + self.processSpentOutput(coin_type, o, tx["txid"], i, tx) last_height_checked += 1 self.updateCheckedBlock(ci, c, block) def expireMessages(self) -> None: if self._is_locked is True: - self.log.debug('Not expiring messages while system locked') + self.log.debug("Not expiring messages while system locked") return self.mxDB.acquire() @@ -4848,27 +6820,33 @@ class BasicSwap(BaseApp): nonlocal num_messages, num_removed try: num_messages += 1 - expire_at: int = msg['sent'] + msg['ttl'] + expire_at: int = msg["sent"] + msg["ttl"] if expire_at < now: - options = {'encoding': 'none', 'delete': True} - del_msg = ci_part.json_request(rpc_conn, 'smsg', [msg['msgid'], options]) + options = {"encoding": "none", "delete": True} + ci_part.json_request(rpc_conn, "smsg", [msg["msgid"], options]) num_removed += 1 - except Exception as e: + except Exception as e: # noqa: F841 if self.debug: self.log.error(traceback.format_exc()) - self.log.error(f'Failed to process message {msg}') + self.log.error(f"Failed to process message {msg}") now: int = self.getTime() - options = {'encoding': 'none', 'setread': False} - inbox_messages = ci_part.json_request(rpc_conn, 'smsginbox', ['all', '', options])['messages'] + options = {"encoding": "none", "setread": False} + inbox_messages = ci_part.json_request( + rpc_conn, "smsginbox", ["all", "", options] + )["messages"] for msg in inbox_messages: remove_if_expired(msg) - outbox_messages = ci_part.json_request(rpc_conn, 'smsgoutbox', ['all', '', options])['messages'] + outbox_messages = ci_part.json_request( + rpc_conn, "smsgoutbox", ["all", "", options] + )["messages"] for msg in outbox_messages: remove_if_expired(msg) if num_messages + num_removed > 0: - self.log.info('Expired {} / {} messages.'.format(num_removed, num_messages)) + self.log.info( + "Expired {} / {} messages.".format(num_removed, num_messages) + ) finally: if rpc_conn: @@ -4877,7 +6855,7 @@ class BasicSwap(BaseApp): def expireDBRecords(self) -> None: if self._is_locked is True: - self.log.debug('Not expiring database records while system locked') + self.log.debug("Not expiring database records while system locked") return if not self._expire_db_records: return @@ -4886,7 +6864,7 @@ class BasicSwap(BaseApp): def checkAcceptedBids(self) -> None: # Check for bids stuck as accepted (not yet in-progress) if self._is_locked is True: - self.log.debug('Not checking accepted bids while system locked') + self.log.debug("Not checking accepted bids while system locked") return now: int = self.getTime() @@ -4894,19 +6872,30 @@ class BasicSwap(BaseApp): grace_period: int = 60 * 60 try: - query_str = 'SELECT bid_id FROM bids ' + \ - 'WHERE active_ind = 1 AND state = :accepted_state AND expire_at + :grace_period <= :now ' - q = session.execute(text(query_str), {'accepted_state': int(BidStates.BID_ACCEPTED), 'now': now, 'grace_period': grace_period}) + query_str = ( + "SELECT bid_id FROM bids " + + "WHERE active_ind = 1 AND state = :accepted_state AND expire_at + :grace_period <= :now " + ) + q = session.execute( + text(query_str), + { + "accepted_state": int(BidStates.BID_ACCEPTED), + "now": now, + "grace_period": grace_period, + }, + ) for row in q: bid_id = row[0] - self.log.info('Timing out bid {}.'.format(bid_id.hex())) + self.log.info("Timing out bid {}.".format(bid_id.hex())) self.timeoutBid(bid_id, session) finally: self.closeSession(session) def countQueuedActions(self, session, bid_id: bytes, action_type) -> int: - q = session.query(Action).filter(sa.and_(Action.active_ind == 1, Action.linked_id == bid_id)) + q = session.query(Action).filter( + sa.and_(Action.active_ind == 1, Action.linked_id == bid_id) + ) if action_type is not None: q.filter(Action.action_type == int(action_type)) return q.count() @@ -4917,7 +6906,9 @@ class BasicSwap(BaseApp): try: session = self.openSession() - q = session.query(Action).filter(sa.and_(Action.active_ind == 1, Action.trigger_at <= now)) + q = session.query(Action).filter( + sa.and_(Action.active_ind == 1, Action.trigger_at <= now) + ) for row in q: accepting_bid: bool = False try: @@ -4949,26 +6940,32 @@ class BasicSwap(BaseApp): accepting_bid = True self.acceptADSReverseBid(row.linked_id, session) else: - self.log.warning('Unknown event type: %d', row.event_type) + self.log.warning("Unknown event type: %d", row.event_type) except Exception as ex: - err_msg = f'checkQueuedActions failed: {ex}' + err_msg = f"checkQueuedActions failed: {ex}" self.logException(err_msg) bid_id = row.linked_id # Failing to accept a bid should not set an error state as the bid has not begun yet if accepting_bid: - self.logEvent(Concepts.BID, - bid_id, - EventLogTypes.ERROR, - err_msg, - session) + self.logEvent( + Concepts.BID, bid_id, EventLogTypes.ERROR, err_msg, session + ) # If delaying with no (further) queued actions reset state if self.countQueuedActions(session, bid_id, None) < 2: bid, offer = self.getBidAndOffer(bid_id, session) last_state = getLastBidState(bid.states) - if bid and bid.state == BidStates.SWAP_DELAYING and last_state == BidStates.BID_RECEIVED: - new_state = BidStates.BID_ERROR if offer.bid_reversed else BidStates.BID_RECEIVED + if ( + bid + and bid.state == BidStates.SWAP_DELAYING + and last_state == BidStates.BID_RECEIVED + ): + new_state = ( + BidStates.BID_ERROR + if offer.bid_reversed + else BidStates.BID_RECEIVED + ) bid.setState(new_state) self.saveBidInSession(bid_id, bid, session) else: @@ -4977,13 +6974,13 @@ class BasicSwap(BaseApp): bid.setState(BidStates.BID_ERROR, err_msg) self.saveBidInSession(bid_id, bid, session) - query: str = 'DELETE FROM actions WHERE trigger_at <= :now' + query: str = "DELETE FROM actions WHERE trigger_at <= :now" if self.debug: - query = 'UPDATE actions SET active_ind = 2 WHERE trigger_at <= :now' - session.execute(text(query), {'now': now}) + query = "UPDATE actions SET active_ind = 2 WHERE trigger_at <= :now" + session.execute(text(query), {"now": now}) except Exception as ex: - self.handleSessionErrors(ex, session, 'checkQueuedActions') + self.handleSessionErrors(ex, session, "checkQueuedActions") reload_in_progress = True finally: self.closeSession(session) @@ -4998,27 +6995,47 @@ class BasicSwap(BaseApp): session = self.openSession() q = session.query(Bid).filter(Bid.state == BidStates.BID_RECEIVING) for bid in q: - q = session.execute(text('SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x\'{}\' AND msg_type = {}'.format(bid.bid_id.hex(), XmrSplitMsgTypes.BID))).first() + q = session.execute( + text( + "SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x'{}' AND msg_type = {}".format( + bid.bid_id.hex(), XmrSplitMsgTypes.BID + ) + ) + ).first() num_segments = q[0] if num_segments > 1: try: self.receiveXmrBid(bid, session) except Exception as ex: - self.log.info('Verify adaptor-sig bid {} failed: {}'.format(bid.bid_id.hex(), str(ex))) + self.log.info( + "Verify adaptor-sig bid {} failed: {}".format( + bid.bid_id.hex(), str(ex) + ) + ) if self.debug: self.log.error(traceback.format_exc()) - bid.setState(BidStates.BID_ERROR, 'Failed validation: ' + str(ex)) + bid.setState( + BidStates.BID_ERROR, "Failed validation: " + str(ex) + ) session.add(bid) self.updateBidInProgress(bid) continue if bid.created_at + ttl_xmr_split_messages < now: - self.log.debug('Expiring partially received bid: {}'.format(bid.bid_id.hex())) - bid.setState(BidStates.BID_ERROR, 'Timed out') + self.log.debug( + "Expiring partially received bid: {}".format(bid.bid_id.hex()) + ) + bid.setState(BidStates.BID_ERROR, "Timed out") session.add(bid) q = session.query(Bid).filter(Bid.state == BidStates.BID_RECEIVING_ACC) for bid in q: - q = session.execute(text('SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x\'{}\' AND msg_type = {}'.format(bid.bid_id.hex(), XmrSplitMsgTypes.BID_ACCEPT))).first() + q = session.execute( + text( + "SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x'{}' AND msg_type = {}".format( + bid.bid_id.hex(), XmrSplitMsgTypes.BID_ACCEPT + ) + ) + ).first() num_segments = q[0] if num_segments > 1: try: @@ -5026,37 +7043,61 @@ class BasicSwap(BaseApp): except Exception as ex: if self.debug: self.log.error(traceback.format_exc()) - self.log.info('Verify adaptor-sig bid accept {} failed: {}'.format(bid.bid_id.hex(), str(ex))) - bid.setState(BidStates.BID_ERROR, 'Failed accept validation: ' + str(ex)) + self.log.info( + "Verify adaptor-sig bid accept {} failed: {}".format( + bid.bid_id.hex(), str(ex) + ) + ) + bid.setState( + BidStates.BID_ERROR, "Failed accept validation: " + str(ex) + ) session.add(bid) self.updateBidInProgress(bid) continue if bid.created_at + ttl_xmr_split_messages < now: - self.log.debug('Expiring partially received bid accept: {}'.format(bid.bid_id.hex())) - bid.setState(BidStates.BID_ERROR, 'Timed out') + self.log.debug( + "Expiring partially received bid accept: {}".format( + bid.bid_id.hex() + ) + ) + bid.setState(BidStates.BID_ERROR, "Timed out") session.add(bid) # Expire old records - q = session.query(XmrSplitData).filter(XmrSplitData.created_at + ttl_xmr_split_messages < now) + q = session.query(XmrSplitData).filter( + XmrSplitData.created_at + ttl_xmr_split_messages < now + ) q.delete(synchronize_session=False) finally: self.closeSession(session) def processOffer(self, msg) -> None: - offer_bytes = bytes.fromhex(msg['hex'][2:-2]) + offer_bytes = bytes.fromhex(msg["hex"][2:-2]) offer_data = OfferMessage(init_all=False) try: offer_data.from_bytes(offer_bytes[:2], init_all=False) - ensure(offer_data.protocol_version >= MINPROTO_VERSION and offer_data.protocol_version <= MAXPROTO_VERSION, 'protocol_version out of range') - except Exception as e: - self.log.warning('Incoming offer invalid protocol version: {}.'.format(getattr(offer_data, 'protocol_version', -1))) + ensure( + offer_data.protocol_version >= MINPROTO_VERSION + and offer_data.protocol_version <= MAXPROTO_VERSION, + "protocol_version out of range", + ) + except Exception as e: # noqa: F841 + self.log.warning( + "Incoming offer invalid protocol version: {}.".format( + getattr(offer_data, "protocol_version", -1) + ) + ) return try: offer_data.from_bytes(offer_bytes) except Exception as e: - self.log.warning('Failed to decode offer, protocol version: {}, {}.'.format(getattr(offer_data, 'protocol_version', -1), str(e))) + self.log.warning( + "Failed to decode offer, protocol version: {}, {}.".format( + getattr(offer_data, "protocol_version", -1), str(e) + ) + ) return # Validate offer data @@ -5065,62 +7106,93 @@ class BasicSwap(BaseApp): ci_from = self.ci(coin_from) coin_to = Coins(offer_data.coin_to) ci_to = self.ci(coin_to) - ensure(offer_data.coin_from != offer_data.coin_to, 'coin_from == coin_to') + ensure(offer_data.coin_from != offer_data.coin_to, "coin_from == coin_to") self.validateSwapType(coin_from, coin_to, offer_data.swap_type) - self.validateOfferAmounts(coin_from, coin_to, offer_data.amount_from, offer_data.amount_to, offer_data.min_bid_amount) - self.validateOfferLockValue(offer_data.swap_type, coin_from, coin_to, offer_data.lock_type, offer_data.lock_value) - self.validateOfferValidTime(offer_data.swap_type, coin_from, coin_to, offer_data.time_valid) + self.validateOfferAmounts( + coin_from, + coin_to, + offer_data.amount_from, + offer_data.amount_to, + offer_data.min_bid_amount, + ) + self.validateOfferLockValue( + offer_data.swap_type, + coin_from, + coin_to, + offer_data.lock_type, + offer_data.lock_value, + ) + self.validateOfferValidTime( + offer_data.swap_type, coin_from, coin_to, offer_data.time_valid + ) - ensure(msg['sent'] + offer_data.time_valid >= now, 'Offer expired') + ensure(msg["sent"] + offer_data.time_valid >= now, "Offer expired") - offer_rate: int = ci_from.make_int(offer_data.amount_to / offer_data.amount_from, r=1) + offer_rate: int = ci_from.make_int( + offer_data.amount_to / offer_data.amount_from, r=1 + ) reverse_bid: bool = self.is_reverse_ads_bid(coin_from, coin_to) if offer_data.swap_type == SwapTypes.SELLER_FIRST: - ensure(offer_data.protocol_version >= MINPROTO_VERSION_SECRET_HASH, 'Invalid protocol version') - ensure(len(offer_data.proof_address) == 0, 'Unexpected data') - ensure(len(offer_data.proof_signature) == 0, 'Unexpected data') - ensure(len(offer_data.pkhash_seller) == 0, 'Unexpected data') - ensure(len(offer_data.secret_hash) == 0, 'Unexpected data') + ensure( + offer_data.protocol_version >= MINPROTO_VERSION_SECRET_HASH, + "Invalid protocol version", + ) + ensure(len(offer_data.proof_address) == 0, "Unexpected data") + ensure(len(offer_data.proof_signature) == 0, "Unexpected data") + ensure(len(offer_data.pkhash_seller) == 0, "Unexpected data") + ensure(len(offer_data.secret_hash) == 0, "Unexpected data") elif offer_data.swap_type == SwapTypes.BUYER_FIRST: - raise ValueError('TODO') + raise ValueError("TODO") elif offer_data.swap_type == SwapTypes.XMR_SWAP: - ensure(offer_data.protocol_version >= MINPROTO_VERSION_ADAPTOR_SIG, 'Invalid protocol version') + ensure( + offer_data.protocol_version >= MINPROTO_VERSION_ADAPTOR_SIG, + "Invalid protocol version", + ) if reverse_bid: - ensure(ci_to.has_segwit(), 'Coin-to must support segwit for reverse bid offers') + ensure( + ci_to.has_segwit(), + "Coin-to must support segwit for reverse bid offers", + ) else: - ensure(ci_from.has_segwit(), 'Coin-from must support segwit') - ensure(len(offer_data.proof_address) == 0, 'Unexpected data') - ensure(len(offer_data.proof_signature) == 0, 'Unexpected data') - ensure(len(offer_data.pkhash_seller) == 0, 'Unexpected data') - ensure(len(offer_data.secret_hash) == 0, 'Unexpected data') + ensure(ci_from.has_segwit(), "Coin-from must support segwit") + ensure(len(offer_data.proof_address) == 0, "Unexpected data") + ensure(len(offer_data.proof_signature) == 0, "Unexpected data") + ensure(len(offer_data.pkhash_seller) == 0, "Unexpected data") + ensure(len(offer_data.secret_hash) == 0, "Unexpected data") else: - raise ValueError('Unknown swap type {}.'.format(offer_data.swap_type)) + raise ValueError("Unknown swap type {}.".format(offer_data.swap_type)) - offer_id = bytes.fromhex(msg['msgid']) + offer_id = bytes.fromhex(msg["msgid"]) - if self.isOfferRevoked(offer_id, msg['from']): - raise ValueError('Offer has been revoked {}.'.format(offer_id.hex())) + if self.isOfferRevoked(offer_id, msg["from"]): + raise ValueError("Offer has been revoked {}.".format(offer_id.hex())) try: session = self.openSession() # Offers must be received on the public network_addr or manually created addresses - if msg['to'] != self.network_addr: + if msg["to"] != self.network_addr: # Double check active_ind, shouldn't be possible to receive message if not active - query_str = 'SELECT COUNT(addr_id) FROM smsgaddresses WHERE addr = "{}" AND use_type = {} AND active_ind = 1'.format(msg['to'], AddressTypes.RECV_OFFER) + query_str = 'SELECT COUNT(addr_id) FROM smsgaddresses WHERE addr = "{}" AND use_type = {} AND active_ind = 1'.format( + msg["to"], AddressTypes.RECV_OFFER + ) rv = session.execute(text(query_str)).first() if rv[0] < 1: - raise ValueError('Offer received on incorrect address') + raise ValueError("Offer received on incorrect address") # Check for sent existing_offer = self.getOffer(offer_id, session=session) if existing_offer is None: - bid_reversed: bool = offer_data.swap_type == SwapTypes.XMR_SWAP and self.is_reverse_ads_bid(offer_data.coin_from, offer_data.coin_to) + bid_reversed: bool = ( + offer_data.swap_type == SwapTypes.XMR_SWAP + and self.is_reverse_ads_bid( + offer_data.coin_from, offer_data.coin_to + ) + ) offer = Offer( offer_id=offer_id, active_ind=1, - protocol_version=offer_data.protocol_version, coin_from=offer_data.coin_from, coin_to=offer_data.coin_to, @@ -5134,13 +7206,13 @@ class BasicSwap(BaseApp): swap_type=offer_data.swap_type, amount_negotiable=offer_data.amount_negotiable, rate_negotiable=offer_data.rate_negotiable, - - addr_to=msg['to'], - addr_from=msg['from'], - created_at=msg['sent'], - expire_at=msg['sent'] + offer_data.time_valid, + addr_to=msg["to"], + addr_from=msg["from"], + created_at=msg["sent"], + expire_at=msg["sent"] + offer_data.time_valid, was_sent=False, - bid_reversed=bid_reversed) + bid_reversed=bid_reversed, + ) offer.setState(OfferStates.OFFER_RECEIVED) session.add(offer) @@ -5153,15 +7225,19 @@ class BasicSwap(BaseApp): lock_value_2 = offer_data.lock_value if (None, DebugTypes.OFFER_LOCK_2_VALUE_INC) in self._debug_cases: lock_value_2 += 1000 - xmr_offer.lock_time_1 = chain_a_ci.getExpectedSequence(offer_data.lock_type, offer_data.lock_value) - xmr_offer.lock_time_2 = chain_a_ci.getExpectedSequence(offer_data.lock_type, lock_value_2) + xmr_offer.lock_time_1 = chain_a_ci.getExpectedSequence( + offer_data.lock_type, offer_data.lock_value + ) + xmr_offer.lock_time_2 = chain_a_ci.getExpectedSequence( + offer_data.lock_type, lock_value_2 + ) xmr_offer.a_fee_rate = offer_data.fee_rate_from xmr_offer.b_fee_rate = offer_data.fee_rate_to session.add(xmr_offer) - self.notify(NT.OFFER_RECEIVED, {'offer_id': offer_id.hex()}, session) + self.notify(NT.OFFER_RECEIVED, {"offer_id": offer_id.hex()}, session) else: existing_offer.setState(OfferStates.OFFER_RECEIVED) session.add(existing_offer) @@ -5169,9 +7245,9 @@ class BasicSwap(BaseApp): self.closeSession(session) def processOfferRevoke(self, msg) -> None: - ensure(msg['to'] == self.network_addr, 'Message received on wrong address') + ensure(msg["to"] == self.network_addr, "Message received on wrong address") - msg_bytes = bytes.fromhex(msg['hex'][2:-2]) + msg_bytes = bytes.fromhex(msg["hex"][2:-2]) msg_data = OfferRevokeMessage(init_all=False) msg_data.from_bytes(msg_bytes) @@ -5180,26 +7256,42 @@ class BasicSwap(BaseApp): session = self.openSession() if len(msg_data.offer_msg_id) != 28: - raise ValueError('Invalid msg_id length') + raise ValueError("Invalid msg_id length") if len(msg_data.signature) != 65: - raise ValueError('Invalid signature length') + raise ValueError("Invalid signature length") - offer = session.query(Offer).filter_by(offer_id=msg_data.offer_msg_id).first() + offer = ( + session.query(Offer).filter_by(offer_id=msg_data.offer_msg_id).first() + ) if offer is None: self.storeOfferRevoke(msg_data.offer_msg_id, msg_data.signature) # Offer may not have been received yet, or involved an inactive coin on this node. - self.log.debug('Offer not found to revoke: {}'.format(msg_data.offer_msg_id.hex())) + self.log.debug( + "Offer not found to revoke: {}".format(msg_data.offer_msg_id.hex()) + ) return if offer.expire_at <= now: - self.log.debug('Offer is already expired, no need to revoke: {}'.format(msg_data.offer_msg_id.hex())) + self.log.debug( + "Offer is already expired, no need to revoke: {}".format( + msg_data.offer_msg_id.hex() + ) + ) return - signature_enc = base64.b64encode(msg_data.signature).decode('utf-8') + signature_enc = base64.b64encode(msg_data.signature).decode("utf-8") - passed = self.callcoinrpc(Coins.PART, 'verifymessage', [offer.addr_from, signature_enc, msg_data.offer_msg_id.hex() + '_revoke']) - ensure(passed is True, 'Signature invalid') + passed = self.callcoinrpc( + Coins.PART, + "verifymessage", + [ + offer.addr_from, + signature_enc, + msg_data.offer_msg_id.hex() + "_revoke", + ], + ) + ensure(passed is True, "Signature invalid") offer.active_ind = 2 # TODO: Remove message, or wait for expire @@ -5211,15 +7303,23 @@ class BasicSwap(BaseApp): def getCompletedAndActiveBidsValue(self, offer, session): bids = [] total_value = 0 - q = session.execute(text( - '''SELECT bid_id, amount, state FROM bids + q = session.execute( + text( + """SELECT bid_id, amount, state FROM bids JOIN bidstates ON bidstates.state_id = bids.state AND (bidstates.state_id = {1} OR bidstates.in_progress > 0) WHERE bids.active_ind = 1 AND bids.offer_id = x\'{0}\' UNION SELECT bid_id, amount, state FROM bids JOIN actions ON actions.linked_id = bids.bid_id AND actions.active_ind = 1 AND (actions.action_type = {2} OR actions.action_type = {3}) WHERE bids.active_ind = 1 AND bids.offer_id = x\'{0}\' - '''.format(offer.offer_id.hex(), BidStates.SWAP_COMPLETED, ActionTypes.ACCEPT_XMR_BID, ActionTypes.ACCEPT_BID))) + """.format( + offer.offer_id.hex(), + BidStates.SWAP_COMPLETED, + ActionTypes.ACCEPT_XMR_BID, + ActionTypes.ACCEPT_BID, + ) + ) + ) for row in q: bid_id, amount, state = row bids.append((bid_id, amount, state)) @@ -5228,115 +7328,158 @@ class BasicSwap(BaseApp): def evaluateKnownIdentityForAutoAccept(self, strategy, identity_stats) -> bool: if identity_stats: - if identity_stats.automation_override == AutomationOverrideOptions.NEVER_ACCEPT: - raise AutomationConstraint('From address is marked never accept') - if identity_stats.automation_override == AutomationOverrideOptions.ALWAYS_ACCEPT: + if ( + identity_stats.automation_override + == AutomationOverrideOptions.NEVER_ACCEPT + ): + raise AutomationConstraint("From address is marked never accept") + if ( + identity_stats.automation_override + == AutomationOverrideOptions.ALWAYS_ACCEPT + ): return True if strategy.only_known_identities: if not identity_stats: - raise AutomationConstraint('Unknown bidder') + raise AutomationConstraint("Unknown bidder") # TODO: More options if identity_stats.num_recv_bids_successful < 1: - raise AutomationConstraint('Bidder has too few successful swaps') - if identity_stats.num_recv_bids_successful <= identity_stats.num_recv_bids_failed: - raise AutomationConstraint('Bidder has too many failed swaps') + raise AutomationConstraint("Bidder has too few successful swaps") + if ( + identity_stats.num_recv_bids_successful + <= identity_stats.num_recv_bids_failed + ): + raise AutomationConstraint("Bidder has too many failed swaps") return True def shouldAutoAcceptBid(self, offer, bid, session=None, options={}) -> bool: try: use_session = self.openSession(session) - link = use_session.query(AutomationLink).filter_by(active_ind=1, linked_type=Concepts.OFFER, linked_id=offer.offer_id).first() + link = ( + use_session.query(AutomationLink) + .filter_by( + active_ind=1, linked_type=Concepts.OFFER, linked_id=offer.offer_id + ) + .first() + ) if not link: return False - strategy = use_session.query(AutomationStrategy).filter_by(active_ind=1, record_id=link.strategy_id).first() - opts = json.loads(strategy.data.decode('utf-8')) + strategy = ( + use_session.query(AutomationStrategy) + .filter_by(active_ind=1, record_id=link.strategy_id) + .first() + ) + opts = json.loads(strategy.data.decode("utf-8")) - coin_from = Coins(offer.coin_from) bid_amount: int = bid.amount bid_rate: int = bid.rate - if options.get('reverse_bid', False): + if options.get("reverse_bid", False): bid_amount = bid.amount_to - bid_rate = options.get('bid_rate') + bid_rate = options.get("bid_rate") - self.log.debug('Evaluating against strategy {}'.format(strategy.record_id)) + self.log.debug("Evaluating against strategy {}".format(strategy.record_id)) if not offer.amount_negotiable: if bid_amount != offer.amount_from: - raise AutomationConstraint('Need exact amount match') + raise AutomationConstraint("Need exact amount match") if bid_amount < offer.min_bid_amount: - raise AutomationConstraint('Bid amount below offer minimum') + raise AutomationConstraint("Bid amount below offer minimum") - if opts.get('exact_rate_only', False) is True: + if opts.get("exact_rate_only", False) is True: if bid_rate != offer.rate: - raise AutomationConstraint('Need exact rate match') + raise AutomationConstraint("Need exact rate match") - active_bids, total_bids_value = self.getCompletedAndActiveBidsValue(offer, use_session) + active_bids, total_bids_value = self.getCompletedAndActiveBidsValue( + offer, use_session + ) - total_bids_value_multiplier = opts.get('total_bids_value_multiplier', 1.0) + total_bids_value_multiplier = opts.get("total_bids_value_multiplier", 1.0) if total_bids_value_multiplier > 0.0: - if total_bids_value + bid_amount > offer.amount_from * total_bids_value_multiplier: - raise AutomationConstraint('Over remaining offer value {}'.format(offer.amount_from * total_bids_value_multiplier - total_bids_value)) + if ( + total_bids_value + bid_amount + > offer.amount_from * total_bids_value_multiplier + ): + raise AutomationConstraint( + "Over remaining offer value {}".format( + offer.amount_from * total_bids_value_multiplier + - total_bids_value + ) + ) num_not_completed = 0 for active_bid in active_bids: if active_bid[2] != BidStates.SWAP_COMPLETED: num_not_completed += 1 - max_concurrent_bids = opts.get('max_concurrent_bids', 1) + max_concurrent_bids = opts.get("max_concurrent_bids", 1) if num_not_completed >= max_concurrent_bids: - raise AutomationConstraint('Already have {} bids to complete'.format(num_not_completed)) + raise AutomationConstraint( + "Already have {} bids to complete".format(num_not_completed) + ) - identity_stats = use_session.query(KnownIdentity).filter_by(address=bid.bid_addr).first() + identity_stats = ( + use_session.query(KnownIdentity).filter_by(address=bid.bid_addr).first() + ) self.evaluateKnownIdentityForAutoAccept(strategy, identity_stats) - self.logEvent(Concepts.BID, - bid.bid_id, - EventLogTypes.AUTOMATION_ACCEPTING_BID, - '', - use_session) + self.logEvent( + Concepts.BID, + bid.bid_id, + EventLogTypes.AUTOMATION_ACCEPTING_BID, + "", + use_session, + ) return True except AutomationConstraint as e: - self.log.info('Not auto accepting bid {}, {}'.format(bid.bid_id.hex(), str(e))) + self.log.info( + "Not auto accepting bid {}, {}".format(bid.bid_id.hex(), str(e)) + ) if self.debug: - self.logEvent(Concepts.BID, - bid.bid_id, - EventLogTypes.AUTOMATION_CONSTRAINT, - str(e), - use_session) + self.logEvent( + Concepts.BID, + bid.bid_id, + EventLogTypes.AUTOMATION_CONSTRAINT, + str(e), + use_session, + ) return False except Exception as e: - self.logException(f'shouldAutoAcceptBid {e}') + self.logException(f"shouldAutoAcceptBid {e}") return False finally: if session is None: self.closeSession(use_session) def processBid(self, msg) -> None: - self.log.debug('Processing bid msg %s', msg['msgid']) + self.log.debug("Processing bid msg %s", msg["msgid"]) now: int = self.getTime() - bid_bytes = bytes.fromhex(msg['hex'][2:-2]) + bid_bytes = bytes.fromhex(msg["hex"][2:-2]) bid_data = BidMessage(init_all=False) bid_data.from_bytes(bid_bytes) # Validate bid data - ensure(bid_data.protocol_version >= MINPROTO_VERSION_SECRET_HASH, 'Invalid protocol version') - ensure(len(bid_data.offer_msg_id) == 28, 'Bad offer_id length') + ensure( + bid_data.protocol_version >= MINPROTO_VERSION_SECRET_HASH, + "Invalid protocol version", + ) + ensure(len(bid_data.offer_msg_id) == 28, "Bad offer_id length") offer_id = bid_data.offer_msg_id offer = self.getOffer(offer_id, sent=True) - ensure(offer and offer.was_sent, 'Unknown offer') + ensure(offer and offer.was_sent, "Unknown offer") - ensure(offer.state == OfferStates.OFFER_RECEIVED, 'Bad offer state') - ensure(msg['to'] == offer.addr_from, 'Received on incorrect address') - ensure(now <= offer.expire_at, 'Offer expired') - self.validateBidValidTime(offer.swap_type, offer.coin_from, offer.coin_to, bid_data.time_valid) - ensure(now <= msg['sent'] + bid_data.time_valid, 'Bid expired') + ensure(offer.state == OfferStates.OFFER_RECEIVED, "Bad offer state") + ensure(msg["to"] == offer.addr_from, "Received on incorrect address") + ensure(now <= offer.expire_at, "Offer expired") + self.validateBidValidTime( + offer.swap_type, offer.coin_from, offer.coin_to, bid_data.time_valid + ) + ensure(now <= msg["sent"] + bid_data.time_valid, "Bid expired") coin_to = Coins(offer.coin_to) ci_from = self.ci(offer.coin_from) @@ -5349,19 +7492,25 @@ class BasicSwap(BaseApp): swap_type = offer.swap_type if swap_type == SwapTypes.SELLER_FIRST: - ensure(len(bid_data.pkhash_buyer) == 20, 'Bad pkhash_buyer length') + ensure(len(bid_data.pkhash_buyer) == 20, "Bad pkhash_buyer length") proof_utxos = ci_to.decodeProofUtxos(bid_data.proof_utxos) - sum_unspent = ci_to.verifyProofOfFunds(bid_data.proof_address, bid_data.proof_signature, proof_utxos, offer_id) - self.log.debug('Proof of funds %s %s', bid_data.proof_address, self.ci(coin_to).format_amount(sum_unspent)) - ensure(sum_unspent >= bid_data.amount_to, 'Proof of funds failed') + sum_unspent = ci_to.verifyProofOfFunds( + bid_data.proof_address, bid_data.proof_signature, proof_utxos, offer_id + ) + self.log.debug( + "Proof of funds %s %s", + bid_data.proof_address, + self.ci(coin_to).format_amount(sum_unspent), + ) + ensure(sum_unspent >= bid_data.amount_to, "Proof of funds failed") elif swap_type == SwapTypes.BUYER_FIRST: - raise ValueError('TODO') + raise ValueError("TODO") else: - raise ValueError('Unknown swap type {}.'.format(swap_type)) + raise ValueError("Unknown swap type {}.".format(swap_type)) - bid_id = bytes.fromhex(msg['msgid']) + bid_id = bytes.fromhex(msg["msgid"]) bid = self.getBid(bid_id) if bid is None: @@ -5376,10 +7525,9 @@ class BasicSwap(BaseApp): pkhash_buyer=bid_data.pkhash_buyer, proof_address=bid_data.proof_address, proof_utxos=bid_data.proof_utxos, - - created_at=msg['sent'], - expire_at=msg['sent'] + bid_data.time_valid, - bid_addr=msg['from'], + created_at=msg["sent"], + expire_at=msg["sent"] + bid_data.time_valid, + bid_addr=msg["from"], was_received=True, chain_a_height_start=ci_from.getChainHeight(), chain_b_height_start=ci_to.getChainHeight(), @@ -5388,9 +7536,12 @@ class BasicSwap(BaseApp): if len(bid_data.pkhash_buyer_to) > 0: bid.pkhash_buyer_to = bid_data.pkhash_buyer_to else: - ensure(bid.state == BidStates.BID_SENT, 'Wrong bid state: {}'.format(BidStates(bid.state).name)) - bid.created_at = msg['sent'] - bid.expire_at = msg['sent'] + bid_data.time_valid + ensure( + bid.state == BidStates.BID_SENT, + "Wrong bid state: {}".format(BidStates(bid.state).name), + ) + bid.created_at = msg["sent"] + bid.expire_at = msg["sent"] + bid_data.time_valid bid.was_received = True if len(bid_data.proof_address) > 0: bid.proof_address = bid_data.proof_address @@ -5398,76 +7549,124 @@ class BasicSwap(BaseApp): bid.setState(BidStates.BID_RECEIVED) self.saveBid(bid_id, bid) - self.notify(NT.BID_RECEIVED, {'type': 'secrethash', 'bid_id': bid_id.hex(), 'offer_id': bid_data.offer_msg_id.hex()}) + self.notify( + NT.BID_RECEIVED, + { + "type": "secrethash", + "bid_id": bid_id.hex(), + "offer_id": bid_data.offer_msg_id.hex(), + }, + ) if self.shouldAutoAcceptBid(offer, bid): delay = self.get_delay_event_seconds() - self.log.info('Auto accepting bid %s in %d seconds', bid_id.hex(), delay) + self.log.info("Auto accepting bid %s in %d seconds", bid_id.hex(), delay) self.createAction(delay, ActionTypes.ACCEPT_BID, bid_id) def processBidAccept(self, msg) -> None: - self.log.debug('Processing bid accepted msg %s', msg['msgid']) + self.log.debug("Processing bid accepted msg %s", msg["msgid"]) now: int = self.getTime() - bid_accept_bytes = bytes.fromhex(msg['hex'][2:-2]) + bid_accept_bytes = bytes.fromhex(msg["hex"][2:-2]) bid_accept_data = BidAcceptMessage(init_all=False) bid_accept_data.from_bytes(bid_accept_bytes) - ensure(len(bid_accept_data.bid_msg_id) == 28, 'Bad bid_msg_id length') - ensure(len(bid_accept_data.initiate_txid) == 32, 'Bad initiate_txid length') - ensure(len(bid_accept_data.contract_script) < 100, 'Bad contract_script length') + ensure(len(bid_accept_data.bid_msg_id) == 28, "Bad bid_msg_id length") + ensure(len(bid_accept_data.initiate_txid) == 32, "Bad initiate_txid length") + ensure(len(bid_accept_data.contract_script) < 100, "Bad contract_script length") - self.log.debug('for bid %s', bid_accept_data.bid_msg_id.hex()) + self.log.debug("for bid %s", bid_accept_data.bid_msg_id.hex()) bid_id = bid_accept_data.bid_msg_id bid, offer = self.getBidAndOffer(bid_id) - ensure(bid is not None and bid.was_sent is True, 'Unknown bid_id') - ensure(offer, 'Offer not found ' + bid.offer_id.hex()) + ensure(bid is not None and bid.was_sent is True, "Unknown bid_id") + ensure(offer, "Offer not found " + bid.offer_id.hex()) - ensure(bid.expire_at > now + self._bid_expired_leeway, 'Bid expired') - ensure(msg['to'] == bid.bid_addr, 'Received on incorrect address') - ensure(msg['from'] == offer.addr_from, 'Sent from incorrect address') + ensure(bid.expire_at > now + self._bid_expired_leeway, "Bid expired") + ensure(msg["to"] == bid.bid_addr, "Received on incorrect address") + ensure(msg["from"] == offer.addr_from, "Sent from incorrect address") coin_from = Coins(offer.coin_from) ci_from = self.ci(coin_from) if bid.state >= BidStates.BID_ACCEPTED: if bid.was_received: # Sent to self - accept_msg_id: bytes = self.getLinkedMessageId(Concepts.BID, bid_id, MessageTypes.BID_ACCEPT) + accept_msg_id: bytes = self.getLinkedMessageId( + Concepts.BID, bid_id, MessageTypes.BID_ACCEPT + ) - self.log.info('Received valid bid accept %s for bid %s sent to self', accept_msg_id.hex(), bid_id.hex()) + self.log.info( + "Received valid bid accept %s for bid %s sent to self", + accept_msg_id.hex(), + bid_id.hex(), + ) return - raise ValueError('Wrong bid state: {}'.format(BidStates(bid.state).name)) + raise ValueError("Wrong bid state: {}".format(BidStates(bid.state).name)) use_csv = True if offer.lock_type < TxLockTypes.ABS_LOCK_BLOCKS else False - if coin_from in (Coins.DCR, ): + if coin_from in (Coins.DCR,): op_hash = OpCodes.OP_SHA256_DECRED else: op_hash = OpCodes.OP_SHA256 - op_lock = OpCodes.OP_CHECKSEQUENCEVERIFY if use_csv else OpCodes.OP_CHECKLOCKTIMEVERIFY - script_valid, script_hash, script_pkhash1, script_lock_val, script_pkhash2 = atomic_swap_1.verifyContractScript(bid_accept_data.contract_script, op_lock=op_lock, op_hash=op_hash) + op_lock = ( + OpCodes.OP_CHECKSEQUENCEVERIFY + if use_csv + else OpCodes.OP_CHECKLOCKTIMEVERIFY + ) + script_valid, script_hash, script_pkhash1, script_lock_val, script_pkhash2 = ( + atomic_swap_1.verifyContractScript( + bid_accept_data.contract_script, op_lock=op_lock, op_hash=op_hash + ) + ) if not script_valid: - raise ValueError('Bad script') + raise ValueError("Bad script") - ensure(script_pkhash1 == bid.pkhash_buyer, 'pkhash_buyer mismatch') + ensure(script_pkhash1 == bid.pkhash_buyer, "pkhash_buyer mismatch") if use_csv: - expect_sequence = ci_from.getExpectedSequence(offer.lock_type, offer.lock_value) - ensure(script_lock_val == expect_sequence, 'sequence mismatch') + expect_sequence = ci_from.getExpectedSequence( + offer.lock_type, offer.lock_value + ) + ensure(script_lock_val == expect_sequence, "sequence mismatch") else: if offer.lock_type == TxLockTypes.ABS_LOCK_BLOCKS: block_header_from = ci_from.getBlockHeaderAt(now) - chain_height_at_bid_creation = block_header_from['height'] - ensure(script_lock_val <= chain_height_at_bid_creation + offer.lock_value + atomic_swap_1.ABS_LOCK_BLOCKS_LEEWAY, 'script lock height too high') - ensure(script_lock_val >= chain_height_at_bid_creation + offer.lock_value - atomic_swap_1.ABS_LOCK_BLOCKS_LEEWAY, 'script lock height too low') + chain_height_at_bid_creation = block_header_from["height"] + ensure( + script_lock_val + <= chain_height_at_bid_creation + + offer.lock_value + + atomic_swap_1.ABS_LOCK_BLOCKS_LEEWAY, + "script lock height too high", + ) + ensure( + script_lock_val + >= chain_height_at_bid_creation + + offer.lock_value + - atomic_swap_1.ABS_LOCK_BLOCKS_LEEWAY, + "script lock height too low", + ) else: - ensure(script_lock_val <= now + offer.lock_value + atomic_swap_1.INITIATE_TX_TIMEOUT, 'script lock time too high') - ensure(script_lock_val >= now + offer.lock_value - atomic_swap_1.ABS_LOCK_TIME_LEEWAY, 'script lock time too low') + ensure( + script_lock_val + <= now + offer.lock_value + atomic_swap_1.INITIATE_TX_TIMEOUT, + "script lock time too high", + ) + ensure( + script_lock_val + >= now + offer.lock_value - atomic_swap_1.ABS_LOCK_TIME_LEEWAY, + "script lock time too low", + ) - ensure(self.countMessageLinks(Concepts.BID, bid_id, MessageTypes.BID_ACCEPT) == 0, 'Bid already accepted') + ensure( + self.countMessageLinks(Concepts.BID, bid_id, MessageTypes.BID_ACCEPT) == 0, + "Bid already accepted", + ) - bid_accept_msg_id = bytes.fromhex(msg['msgid']) - self.addMessageLink(Concepts.BID, bid_id, MessageTypes.BID_ACCEPT, bid_accept_msg_id) + bid_accept_msg_id = bytes.fromhex(msg["msgid"]) + self.addMessageLink( + Concepts.BID, bid_id, MessageTypes.BID_ACCEPT, bid_accept_msg_id + ) bid.initiate_tx = SwapTx( bid_id=bid_id, @@ -5488,28 +7687,27 @@ class BasicSwap(BaseApp): self.saveBid(bid_id, bid) self.swaps_in_progress[bid_id] = (bid, offer) - self.notify(NT.BID_ACCEPTED, {'bid_id': bid_id.hex()}) + self.notify(NT.BID_ACCEPTED, {"bid_id": bid_id.hex()}) def receiveXmrBid(self, bid, session) -> None: - self.log.debug('Receiving adaptor-sig bid %s', bid.bid_id.hex()) - now: int = self.getTime() + self.log.debug("Receiving adaptor-sig bid %s", bid.bid_id.hex()) offer, xmr_offer = self.getXmrOfferFromSession(session, bid.offer_id, sent=True) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid.bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid.bid_id.hex())) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) - addr_expect_from: str = '' + addr_expect_from: str = "" if reverse_bid: ci_from = self.ci(Coins(offer.coin_to)) ci_to = self.ci(Coins(offer.coin_from)) addr_expect_from = bid.bid_addr addr_expect_to = offer.addr_from else: - ensure(offer.was_sent, 'Offer not sent: {}.'.format(bid.offer_id.hex())) + ensure(offer.was_sent, "Offer not sent: {}.".format(bid.offer_id.hex())) ci_from = self.ci(Coins(offer.coin_from)) ci_to = self.ci(Coins(offer.coin_to)) addr_expect_from = offer.addr_from @@ -5517,56 +7715,91 @@ class BasicSwap(BaseApp): if ci_to.curve_type() == Curves.ed25519: if len(xmr_swap.kbsf_dleag) < ci_to.lengthDLEAG(): - q = session.query(XmrSplitData).filter(sa.and_(XmrSplitData.bid_id == bid.bid_id, XmrSplitData.msg_type == XmrSplitMsgTypes.BID)).order_by(XmrSplitData.msg_sequence.asc()) + q = ( + session.query(XmrSplitData) + .filter( + sa.and_( + XmrSplitData.bid_id == bid.bid_id, + XmrSplitData.msg_type == XmrSplitMsgTypes.BID, + ) + ) + .order_by(XmrSplitData.msg_sequence.asc()) + ) for row in q: - ensure(row.addr_to == addr_expect_from, 'Received on incorrect address, segment_id {}'.format(row.record_id)) - ensure(row.addr_from == addr_expect_to, 'Sent from incorrect address, segment_id {}'.format(row.record_id)) + ensure( + row.addr_to == addr_expect_from, + "Received on incorrect address, segment_id {}".format( + row.record_id + ), + ) + ensure( + row.addr_from == addr_expect_to, + "Sent from incorrect address, segment_id {}".format( + row.record_id + ), + ) xmr_swap.kbsf_dleag += row.dleag if not ci_to.verifyDLEAG(xmr_swap.kbsf_dleag): - raise ValueError('Invalid DLEAG proof.') + raise ValueError("Invalid DLEAG proof.") # Extract pubkeys from MSG1L DLEAG - xmr_swap.pkasf = xmr_swap.kbsf_dleag[0: 33] + xmr_swap.pkasf = xmr_swap.kbsf_dleag[0:33] if not ci_from.verifyPubkey(xmr_swap.pkasf): - raise ValueError('Invalid coin a pubkey.') - xmr_swap.pkbsf = xmr_swap.kbsf_dleag[33: 33 + 32] + raise ValueError("Invalid coin a pubkey.") + xmr_swap.pkbsf = xmr_swap.kbsf_dleag[33 : 33 + 32] if not ci_to.verifyPubkey(xmr_swap.pkbsf): - raise ValueError('Invalid coin b pubkey.') + raise ValueError("Invalid coin b pubkey.") elif ci_to.curve_type() == Curves.secp256k1: - xmr_swap.pkasf = ci_to.verifySigAndRecover(xmr_swap.kbsf_dleag, 'proof kbsf owned for swap') + xmr_swap.pkasf = ci_to.verifySigAndRecover( + xmr_swap.kbsf_dleag, "proof kbsf owned for swap" + ) if not ci_from.verifyPubkey(xmr_swap.pkasf): - raise ValueError('Invalid coin a pubkey.') + raise ValueError("Invalid coin a pubkey.") xmr_swap.pkbsf = xmr_swap.pkasf else: - raise ValueError('Unknown curve') + raise ValueError("Unknown curve") - ensure(ci_to.verifyKey(xmr_swap.vkbvf), 'Invalid key, vkbvf') - ensure(ci_from.verifyPubkey(xmr_swap.pkaf), 'Invalid pubkey, pkaf') + ensure(ci_to.verifyKey(xmr_swap.vkbvf), "Invalid key, vkbvf") + ensure(ci_from.verifyPubkey(xmr_swap.pkaf), "Invalid pubkey, pkaf") if not reverse_bid: # notify already ran in processADSBidReversed - self.notify(NT.BID_RECEIVED, {'type': 'ads', 'bid_id': bid.bid_id.hex(), 'offer_id': bid.offer_id.hex()}, session) + self.notify( + NT.BID_RECEIVED, + { + "type": "ads", + "bid_id": bid.bid_id.hex(), + "offer_id": bid.offer_id.hex(), + }, + session, + ) bid.setState(BidStates.BID_RECEIVED) if reverse_bid or self.shouldAutoAcceptBid(offer, bid, session): delay = self.get_delay_event_seconds() - self.log.info('Auto accepting %sadaptor-sig bid %s in %d seconds', 'reverse ' if reverse_bid else '', bid.bid_id.hex(), delay) - self.createActionInSession(delay, ActionTypes.ACCEPT_XMR_BID, bid.bid_id, session) + self.log.info( + "Auto accepting %sadaptor-sig bid %s in %d seconds", + "reverse " if reverse_bid else "", + bid.bid_id.hex(), + delay, + ) + self.createActionInSession( + delay, ActionTypes.ACCEPT_XMR_BID, bid.bid_id, session + ) bid.setState(BidStates.SWAP_DELAYING) self.saveBidInSession(bid.bid_id, bid, session, xmr_swap) def receiveXmrBidAccept(self, bid, session) -> None: # Follower receiving MSG1F and MSG2F - self.log.debug('Receiving adaptor-sig bid accept %s', bid.bid_id.hex()) - now: int = self.getTime() + self.log.debug("Receiving adaptor-sig bid accept %s", bid.bid_id.hex()) offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=True, session=session) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first() - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid.bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid.bid_id.hex())) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) ci_from = self.ci(offer.coin_to if reverse_bid else offer.coin_from) @@ -5576,92 +7809,130 @@ class BasicSwap(BaseApp): if ci_to.curve_type() == Curves.ed25519: if len(xmr_swap.kbsl_dleag) < ci_to.lengthDLEAG(): - q = session.query(XmrSplitData).filter(sa.and_(XmrSplitData.bid_id == bid.bid_id, XmrSplitData.msg_type == XmrSplitMsgTypes.BID_ACCEPT)).order_by(XmrSplitData.msg_sequence.asc()) + q = ( + session.query(XmrSplitData) + .filter( + sa.and_( + XmrSplitData.bid_id == bid.bid_id, + XmrSplitData.msg_type == XmrSplitMsgTypes.BID_ACCEPT, + ) + ) + .order_by(XmrSplitData.msg_sequence.asc()) + ) for row in q: - ensure(row.addr_to == addr_to, 'Received on incorrect address, segment_id {}'.format(row.record_id)) - ensure(row.addr_from == addr_from, 'Sent from incorrect address, segment_id {}'.format(row.record_id)) + ensure( + row.addr_to == addr_to, + "Received on incorrect address, segment_id {}".format( + row.record_id + ), + ) + ensure( + row.addr_from == addr_from, + "Sent from incorrect address, segment_id {}".format( + row.record_id + ), + ) xmr_swap.kbsl_dleag += row.dleag if not ci_to.verifyDLEAG(xmr_swap.kbsl_dleag): - raise ValueError('Invalid DLEAG proof.') + raise ValueError("Invalid DLEAG proof.") # Extract pubkeys from MSG1F DLEAG - xmr_swap.pkasl = xmr_swap.kbsl_dleag[0: 33] + xmr_swap.pkasl = xmr_swap.kbsl_dleag[0:33] if not ci_from.verifyPubkey(xmr_swap.pkasl): - raise ValueError('Invalid coin a pubkey.') - xmr_swap.pkbsl = xmr_swap.kbsl_dleag[33: 33 + 32] + raise ValueError("Invalid coin a pubkey.") + xmr_swap.pkbsl = xmr_swap.kbsl_dleag[33 : 33 + 32] if not ci_to.verifyPubkey(xmr_swap.pkbsl): - raise ValueError('Invalid coin b pubkey.') + raise ValueError("Invalid coin b pubkey.") elif ci_to.curve_type() == Curves.secp256k1: - xmr_swap.pkasl = ci_to.verifySigAndRecover(xmr_swap.kbsl_dleag, 'proof kbsl owned for swap') + xmr_swap.pkasl = ci_to.verifySigAndRecover( + xmr_swap.kbsl_dleag, "proof kbsl owned for swap" + ) if not ci_from.verifyPubkey(xmr_swap.pkasl): - raise ValueError('Invalid coin a pubkey.') + raise ValueError("Invalid coin a pubkey.") xmr_swap.pkbsl = xmr_swap.pkasl else: - raise ValueError('Unknown curve') + raise ValueError("Unknown curve") # vkbv and vkbvl are verified in processXmrBidAccept xmr_swap.pkbv = ci_to.sumPubkeys(xmr_swap.pkbvl, xmr_swap.pkbvf) xmr_swap.pkbs = ci_to.sumPubkeys(xmr_swap.pkbsl, xmr_swap.pkbsf) if not ci_from.verifyPubkey(xmr_swap.pkal): - raise ValueError('Invalid pubkey.') + raise ValueError("Invalid pubkey.") if xmr_swap.pkbvl == xmr_swap.pkbvf: - raise ValueError('Duplicate scriptless view pubkey.') + raise ValueError("Duplicate scriptless view pubkey.") if xmr_swap.pkbsl == xmr_swap.pkbsf: - raise ValueError('Duplicate scriptless spend pubkey.') + raise ValueError("Duplicate scriptless spend pubkey.") if xmr_swap.pkal == xmr_swap.pkaf: - raise ValueError('Duplicate script spend pubkey.') + raise ValueError("Duplicate script spend pubkey.") bid.setState(BidStates.BID_ACCEPTED) # ADS self.saveBidInSession(bid.bid_id, bid, session, xmr_swap) if reverse_bid is False: - self.notify(NT.BID_ACCEPTED, {'bid_id': bid.bid_id.hex()}, session) + self.notify(NT.BID_ACCEPTED, {"bid_id": bid.bid_id.hex()}, session) delay = self.get_delay_event_seconds() - self.log.info('Responding to adaptor-sig bid accept %s in %d seconds', bid.bid_id.hex(), delay) - self.createActionInSession(delay, ActionTypes.SIGN_XMR_SWAP_LOCK_TX_A, bid.bid_id, session) + self.log.info( + "Responding to adaptor-sig bid accept %s in %d seconds", + bid.bid_id.hex(), + delay, + ) + self.createActionInSession( + delay, ActionTypes.SIGN_XMR_SWAP_LOCK_TX_A, bid.bid_id, session + ) def processXmrBid(self, msg) -> None: # MSG1L - self.log.debug('Processing adaptor-sig bid msg %s', msg['msgid']) + self.log.debug("Processing adaptor-sig bid msg %s", msg["msgid"]) now: int = self.getTime() - bid_bytes = bytes.fromhex(msg['hex'][2:-2]) + bid_bytes = bytes.fromhex(msg["hex"][2:-2]) bid_data = XmrBidMessage(init_all=False) bid_data.from_bytes(bid_bytes) # Validate data - ensure(bid_data.protocol_version >= MINPROTO_VERSION_ADAPTOR_SIG, 'Invalid protocol version') - ensure(len(bid_data.offer_msg_id) == 28, 'Bad offer_id length') + ensure( + bid_data.protocol_version >= MINPROTO_VERSION_ADAPTOR_SIG, + "Invalid protocol version", + ) + ensure(len(bid_data.offer_msg_id) == 28, "Bad offer_id length") offer_id = bid_data.offer_msg_id offer, xmr_offer = self.getXmrOffer(offer_id, sent=True) - ensure(offer and offer.was_sent, 'Offer not found: {}.'.format(offer_id.hex())) - ensure(offer.swap_type == SwapTypes.XMR_SWAP, 'Bid/offer swap type mismatch') - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(offer_id.hex())) + ensure(offer and offer.was_sent, "Offer not found: {}.".format(offer_id.hex())) + ensure(offer.swap_type == SwapTypes.XMR_SWAP, "Bid/offer swap type mismatch") + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(offer_id.hex())) ci_from = self.ci(offer.coin_from) ci_to = self.ci(offer.coin_to) if not validOfferStateToReceiveBid(offer.state): - raise ValueError('Bad offer state') - ensure(msg['to'] == offer.addr_from, 'Received on incorrect address') - ensure(now <= offer.expire_at, 'Offer expired') - self.validateBidValidTime(offer.swap_type, offer.coin_from, offer.coin_to, bid_data.time_valid) - ensure(now <= msg['sent'] + bid_data.time_valid, 'Bid expired') + raise ValueError("Bad offer state") + ensure(msg["to"] == offer.addr_from, "Received on incorrect address") + ensure(now <= offer.expire_at, "Offer expired") + self.validateBidValidTime( + offer.swap_type, offer.coin_from, offer.coin_to, bid_data.time_valid + ) + ensure(now <= msg["sent"] + bid_data.time_valid, "Bid expired") bid_rate: int = ci_from.make_int(bid_data.amount_to / bid_data.amount, r=1) self.validateBidAmount(offer, bid_data.amount, bid_rate) - ensure(ci_to.verifyKey(bid_data.kbvf), 'Invalid chain B follower view key') - ensure(ci_from.verifyPubkey(bid_data.pkaf), 'Invalid chain A follower public key') - ensure(ci_from.isValidAddressHash(bid_data.dest_af) or ci_from.isValidPubkey(bid_data.dest_af), 'Invalid destination address') + ensure(ci_to.verifyKey(bid_data.kbvf), "Invalid chain B follower view key") + ensure( + ci_from.verifyPubkey(bid_data.pkaf), "Invalid chain A follower public key" + ) + ensure( + ci_from.isValidAddressHash(bid_data.dest_af) + or ci_from.isValidPubkey(bid_data.dest_af), + "Invalid destination address", + ) if ci_to.curve_type() == Curves.ed25519: - ensure(len(bid_data.kbsf_dleag) == 16000, 'Invalid kbsf_dleag size') + ensure(len(bid_data.kbsf_dleag) == 16000, "Invalid kbsf_dleag size") - bid_id = bytes.fromhex(msg['msgid']) + bid_id = bytes.fromhex(msg["msgid"]) bid, xmr_swap = self.getXmrBid(bid_id) if bid is None: @@ -5673,9 +7944,9 @@ class BasicSwap(BaseApp): amount=bid_data.amount, amount_to=bid_data.amount_to, rate=bid_rate, - created_at=msg['sent'], - expire_at=msg['sent'] + bid_data.time_valid, - bid_addr=msg['from'], + created_at=msg["sent"], + expire_at=msg["sent"] + bid_data.time_valid, + bid_addr=msg["from"], was_received=True, chain_a_height_start=ci_from.getChainHeight(), chain_b_height_start=ci_to.getChainHeight(), @@ -5692,16 +7963,27 @@ class BasicSwap(BaseApp): wallet_restore_height = self.getWalletRestoreHeight(ci_to) if bid.chain_b_height_start < wallet_restore_height: bid.chain_b_height_start = wallet_restore_height - self.log.warning('Adaptor-sig swap restore height clamped to {}'.format(wallet_restore_height)) + self.log.warning( + "Adaptor-sig swap restore height clamped to {}".format( + wallet_restore_height + ) + ) else: - ensure(bid.state == BidStates.BID_SENT, 'Wrong bid state: {}'.format(BidStates(bid.state).name)) + ensure( + bid.state == BidStates.BID_SENT, + "Wrong bid state: {}".format(BidStates(bid.state).name), + ) # Don't update bid.created_at, it's been used to derive kaf - bid.expire_at = msg['sent'] + bid_data.time_valid + bid.expire_at = msg["sent"] + bid_data.time_valid bid.was_received = True bid.setState(BidStates.BID_RECEIVING) - self.log.info('Receiving adaptor-sig bid %s for offer %s', bid_id.hex(), bid_data.offer_msg_id.hex()) + self.log.info( + "Receiving adaptor-sig bid %s for offer %s", + bid_id.hex(), + bid_data.offer_msg_id.hex(), + ) self.saveBid(bid_id, bid, xmr_swap=xmr_swap) if ci_to.curve_type() != Curves.ed25519: @@ -5713,22 +7995,25 @@ class BasicSwap(BaseApp): def processXmrBidAccept(self, msg) -> None: # F receiving MSG1F and MSG2F - self.log.debug('Processing adaptor-sig bid accept msg %s', msg['msgid']) - now: int = self.getTime() - msg_bytes = bytes.fromhex(msg['hex'][2:-2]) + self.log.debug("Processing adaptor-sig bid accept msg %s", msg["msgid"]) + + msg_bytes = bytes.fromhex(msg["hex"][2:-2]) msg_data = XmrBidAcceptMessage(init_all=False) msg_data.from_bytes(msg_bytes) - ensure(len(msg_data.bid_msg_id) == 28, 'Bad bid_msg_id length') + ensure(len(msg_data.bid_msg_id) == 28, "Bad bid_msg_id length") - self.log.debug('for bid %s', msg_data.bid_msg_id.hex()) + self.log.debug("for bid %s", msg_data.bid_msg_id.hex()) bid, xmr_swap = self.getXmrBid(msg_data.bid_msg_id) - ensure(bid, 'Bid not found: {}.'.format(msg_data.bid_msg_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(msg_data.bid_msg_id.hex())) + ensure(bid, "Bid not found: {}.".format(msg_data.bid_msg_id.hex())) + ensure( + xmr_swap, + "Adaptor-sig swap not found: {}.".format(msg_data.bid_msg_id.hex()), + ) offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=True) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) ci_from = self.ci(offer.coin_to if reverse_bid else offer.coin_from) @@ -5736,17 +8021,16 @@ class BasicSwap(BaseApp): addr_from: str = bid.bid_addr if reverse_bid else offer.addr_from addr_to: str = offer.addr_from if reverse_bid else bid.bid_addr a_fee_rate: int = xmr_offer.b_fee_rate if reverse_bid else xmr_offer.a_fee_rate - b_fee_rate: int = xmr_offer.a_fee_rate if reverse_bid else xmr_offer.b_fee_rate - ensure(msg['to'] == addr_to, 'Received on incorrect address') - ensure(msg['from'] == addr_from, 'Sent from incorrect address') + ensure(msg["to"] == addr_to, "Received on incorrect address") + ensure(msg["from"] == addr_from, "Sent from incorrect address") try: xmr_swap.pkal = msg_data.pkal xmr_swap.vkbvl = msg_data.kbvl - ensure(ci_to.verifyKey(xmr_swap.vkbvl), 'Invalid key, vkbvl') + ensure(ci_to.verifyKey(xmr_swap.vkbvl), "Invalid key, vkbvl") xmr_swap.vkbv = ci_to.sumKeys(xmr_swap.vkbvl, xmr_swap.vkbvf) - ensure(ci_to.verifyKey(xmr_swap.vkbv), 'Invalid key, vkbv') + ensure(ci_to.verifyKey(xmr_swap.vkbv), "Invalid key, vkbv") xmr_swap.pkbvl = ci_to.getPubkey(msg_data.kbvl) xmr_swap.kbsl_dleag = msg_data.kbsl_dleag @@ -5756,7 +8040,9 @@ class BasicSwap(BaseApp): xmr_swap.a_lock_refund_tx = msg_data.a_lock_refund_tx xmr_swap.a_lock_refund_tx_script = msg_data.a_lock_refund_tx_script xmr_swap.a_lock_refund_spend_tx = msg_data.a_lock_refund_spend_tx - xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_spend_tx) + xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxid( + xmr_swap.a_lock_refund_spend_tx + ) xmr_swap.al_lock_refund_tx_sig = msg_data.al_lock_refund_tx_sig refundExtraArgs = dict() @@ -5767,60 +8053,116 @@ class BasicSwap(BaseApp): bch_ci = self.ci(Coins.BCH) - mining_fee, out_1, out_2, public_key, timelock = bch_ci.extractScriptLockScriptValues(xmr_swap.a_lock_tx_script) - ensure(out_1 == bch_ci.getScriptForPubkeyHash(xmr_swap.dest_af), 'Invalid BCH lock tx script out_1') - ensure(out_2 == bch_ci.scriptToP2SH32LockingBytecode(xmr_swap.a_lock_refund_tx_script), 'Invalid BCH lock tx script out_2') + mining_fee, out_1, out_2, public_key, timelock = ( + bch_ci.extractScriptLockScriptValues(xmr_swap.a_lock_tx_script) + ) + ensure( + out_1 == bch_ci.getScriptForPubkeyHash(xmr_swap.dest_af), + "Invalid BCH lock tx script out_1", + ) + ensure( + out_2 + == bch_ci.scriptToP2SH32LockingBytecode( + xmr_swap.a_lock_refund_tx_script + ), + "Invalid BCH lock tx script out_2", + ) - lockExtraArgs['mining_fee'] = mining_fee - lockExtraArgs['out_1'] = out_1 - lockExtraArgs['out_2'] = out_2 - lockExtraArgs['public_key'] = public_key - lockExtraArgs['timelock'] = timelock + lockExtraArgs["mining_fee"] = mining_fee + lockExtraArgs["out_1"] = out_1 + lockExtraArgs["out_2"] = out_2 + lockExtraArgs["public_key"] = public_key + lockExtraArgs["timelock"] = timelock - mining_fee, out_1, out_2, public_key, timelock = bch_ci.extractScriptLockScriptValues(xmr_swap.a_lock_refund_tx_script) - ensure(out_2 == bch_ci.getScriptForPubkeyHash(xmr_swap.dest_af), 'Invalid BCH refund tx script out_2') + mining_fee, out_1, out_2, public_key, timelock = ( + bch_ci.extractScriptLockScriptValues( + xmr_swap.a_lock_refund_tx_script + ) + ) + ensure( + out_2 == bch_ci.getScriptForPubkeyHash(xmr_swap.dest_af), + "Invalid BCH refund tx script out_2", + ) - refundExtraArgs['mining_fee'] = mining_fee - refundExtraArgs['out_1'] = out_1 - refundExtraArgs['out_2'] = out_2 - refundExtraArgs['public_key'] = public_key - refundExtraArgs['timelock'] = timelock + refundExtraArgs["mining_fee"] = mining_fee + refundExtraArgs["out_1"] = out_1 + refundExtraArgs["out_2"] = out_2 + refundExtraArgs["public_key"] = public_key + refundExtraArgs["timelock"] = timelock # TODO: check_lock_tx_inputs without txindex check_a_lock_tx_inputs = False xmr_swap.a_lock_tx_id, xmr_swap.a_lock_tx_vout = ci_from.verifySCLockTx( - xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script, + xmr_swap.a_lock_tx, + xmr_swap.a_lock_tx_script, bid.amount, - xmr_swap.pkal, xmr_swap.pkaf, + xmr_swap.pkal, + xmr_swap.pkaf, a_fee_rate, - check_a_lock_tx_inputs, xmr_swap.vkbv, - **lockExtraArgs) - a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script) + check_a_lock_tx_inputs, + xmr_swap.vkbv, + **lockExtraArgs, + ) - xmr_swap.a_lock_refund_tx_id, xmr_swap.a_swap_refund_value, lock_refund_vout = ci_from.verifySCLockRefundTx( - xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_tx, xmr_swap.a_lock_refund_tx_script, - xmr_swap.a_lock_tx_id, xmr_swap.a_lock_tx_vout, xmr_offer.lock_time_1, xmr_swap.a_lock_tx_script, - xmr_swap.pkal, xmr_swap.pkaf, + ( + xmr_swap.a_lock_refund_tx_id, + xmr_swap.a_swap_refund_value, + lock_refund_vout, + ) = ci_from.verifySCLockRefundTx( + xmr_swap.a_lock_refund_tx, + xmr_swap.a_lock_tx, + xmr_swap.a_lock_refund_tx_script, + xmr_swap.a_lock_tx_id, + xmr_swap.a_lock_tx_vout, + xmr_offer.lock_time_1, + xmr_swap.a_lock_tx_script, + xmr_swap.pkal, + xmr_swap.pkaf, xmr_offer.lock_time_2, - bid.amount, a_fee_rate, xmr_swap.vkbv, - **refundExtraArgs) + bid.amount, + a_fee_rate, + xmr_swap.vkbv, + **refundExtraArgs, + ) ci_from.verifySCLockRefundSpendTx( - xmr_swap.a_lock_refund_spend_tx, xmr_swap.a_lock_refund_tx, - xmr_swap.a_lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script, + xmr_swap.a_lock_refund_spend_tx, + xmr_swap.a_lock_refund_tx, + xmr_swap.a_lock_refund_tx_id, + xmr_swap.a_lock_refund_tx_script, xmr_swap.pkal, - lock_refund_vout, xmr_swap.a_swap_refund_value, a_fee_rate, xmr_swap.vkbv, - **refundExtraArgs) + lock_refund_vout, + xmr_swap.a_swap_refund_value, + a_fee_rate, + xmr_swap.vkbv, + **refundExtraArgs, + ) - self.log.info('Checking leader\'s lock refund tx signature') + self.log.info("Checking leader's lock refund tx signature") prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap) - v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_tx, xmr_swap.al_lock_refund_tx_sig, xmr_swap.pkal, 0, xmr_swap.a_lock_tx_script, prevout_amount) - ensure(v, 'Invalid coin A lock refund tx leader sig') + v = ci_from.verifyTxSig( + xmr_swap.a_lock_refund_tx, + xmr_swap.al_lock_refund_tx_sig, + xmr_swap.pkal, + 0, + xmr_swap.a_lock_tx_script, + prevout_amount, + ) + ensure(v, "Invalid coin A lock refund tx leader sig") - allowed_states = [BidStates.BID_SENT, BidStates.BID_RECEIVED, BidStates.BID_REQUEST_ACCEPTED] + allowed_states = [ + BidStates.BID_SENT, + BidStates.BID_RECEIVED, + BidStates.BID_REQUEST_ACCEPTED, + ] if bid.was_sent and offer.was_sent: - allowed_states.append(BidStates.BID_ACCEPTED) # TODO: Split BID_ACCEPTED into received and sent - ensure(bid.state in allowed_states, 'Invalid state for bid {}'.format(bid.state)) + allowed_states.append( + BidStates.BID_ACCEPTED + ) # TODO: Split BID_ACCEPTED into received and sent + ensure( + bid.state in allowed_states, + "Invalid state for bid {}".format(bid.state), + ) bid.setState(BidStates.BID_RECEIVING_ACC) self.saveBid(bid.bid_id, bid, xmr_swap=xmr_swap) @@ -5836,7 +8178,7 @@ class BasicSwap(BaseApp): self.setBidError(bid.bid_id, bid, str(ex), xmr_swap=xmr_swap) def watchXmrSwap(self, bid, offer, xmr_swap, session=None) -> None: - self.log.debug('Adaptor-sig swap in progress, bid %s', bid.bid_id.hex()) + self.log.debug("Adaptor-sig swap in progress, bid %s", bid.bid_id.hex()) self.swaps_in_progress[bid.bid_id] = (bid, offer) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) @@ -5844,62 +8186,114 @@ class BasicSwap(BaseApp): self.setLastHeightCheckedStart(coin_from, bid.chain_a_height_start, session) if bid.xmr_a_lock_tx.txid: - self.addWatchedOutput(coin_from, bid.bid_id, bid.xmr_a_lock_tx.txid.hex(), bid.xmr_a_lock_tx.vout, TxTypes.XMR_SWAP_A_LOCK, SwapTypes.XMR_SWAP) + self.addWatchedOutput( + coin_from, + bid.bid_id, + bid.xmr_a_lock_tx.txid.hex(), + bid.xmr_a_lock_tx.vout, + TxTypes.XMR_SWAP_A_LOCK, + SwapTypes.XMR_SWAP, + ) if xmr_swap.a_lock_refund_tx_id: lock_refund_vout = self.ci(coin_from).getLockRefundTxSwapOutput(xmr_swap) - self.addWatchedOutput(coin_from, bid.bid_id, xmr_swap.a_lock_refund_tx_id.hex(), lock_refund_vout, TxTypes.XMR_SWAP_A_LOCK_REFUND, SwapTypes.XMR_SWAP) + self.addWatchedOutput( + coin_from, + bid.bid_id, + xmr_swap.a_lock_refund_tx_id.hex(), + lock_refund_vout, + TxTypes.XMR_SWAP_A_LOCK_REFUND, + SwapTypes.XMR_SWAP, + ) bid.in_progress = 1 # Watch outputs for chain A lock tx if txid is unknown (BCH) if bid.xmr_a_lock_tx and bid.xmr_a_lock_tx.txid is None: - find_script: bytes = self.ci(coin_from).getScriptDest(xmr_swap.a_lock_tx_script) - self.addWatchedScript(coin_from, bid.bid_id, find_script, TxTypes.XMR_SWAP_A_LOCK) + find_script: bytes = self.ci(coin_from).getScriptDest( + xmr_swap.a_lock_tx_script + ) + self.addWatchedScript( + coin_from, bid.bid_id, find_script, TxTypes.XMR_SWAP_A_LOCK + ) def sendXmrBidTxnSigsFtoL(self, bid_id, session) -> None: # F -> L: Sending MSG3L - self.log.debug('Signing adaptor-sig bid lock txns %s', bid_id.hex()) + self.log.debug("Signing adaptor-sig bid lock txns %s", bid_id.hex()) bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession(session, bid.offer_id, sent=False) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + offer, xmr_offer = self.getXmrOfferFromSession( + session, bid.offer_id, sent=False + ) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) coin_from = Coins(offer.coin_to if reverse_bid else offer.coin_from) coin_to = Coins(offer.coin_from if reverse_bid else offer.coin_to) ci_from = self.ci(coin_from) - ci_to = self.ci(coin_to) try: - kaf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KAF) + kaf = self.getPathKey( + coin_from, + coin_to, + bid.created_at, + xmr_swap.contract_count, + KeyTypes.KAF, + ) prevout_amount = ci_from.getLockRefundTxSwapOutputValue(bid, xmr_swap) - xmr_swap.af_lock_refund_spend_tx_esig = ci_from.signTxOtVES(kaf, xmr_swap.pkasl, xmr_swap.a_lock_refund_spend_tx, 0, xmr_swap.a_lock_refund_tx_script, prevout_amount) + xmr_swap.af_lock_refund_spend_tx_esig = ci_from.signTxOtVES( + kaf, + xmr_swap.pkasl, + xmr_swap.a_lock_refund_spend_tx, + 0, + xmr_swap.a_lock_refund_tx_script, + prevout_amount, + ) prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap) - xmr_swap.af_lock_refund_tx_sig = ci_from.signTx(kaf, xmr_swap.a_lock_refund_tx, 0, xmr_swap.a_lock_tx_script, prevout_amount) + xmr_swap.af_lock_refund_tx_sig = ci_from.signTx( + kaf, + xmr_swap.a_lock_refund_tx, + 0, + xmr_swap.a_lock_tx_script, + prevout_amount, + ) xmr_swap_1.addLockRefundSigs(self, xmr_swap, ci_from) msg_buf = XmrBidLockTxSigsMessage( bid_msg_id=bid_id, af_lock_refund_spend_tx_esig=xmr_swap.af_lock_refund_spend_tx_esig, - af_lock_refund_tx_sig=xmr_swap.af_lock_refund_tx_sig + af_lock_refund_tx_sig=xmr_swap.af_lock_refund_tx_sig, ) msg_bytes = msg_buf.to_bytes() - payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_TXN_SIGS_FL) + msg_bytes.hex() + payload_hex = ( + str.format("{:02x}", MessageTypes.XMR_BID_TXN_SIGS_FL) + msg_bytes.hex() + ) msg_valid: int = self.getActiveBidMsgValidTime() addr_send_from: str = offer.addr_from if reverse_bid else bid.bid_addr addr_send_to: str = bid.bid_addr if reverse_bid else offer.addr_from - coin_a_lock_tx_sigs_l_msg_id = self.sendSmsg(addr_send_from, addr_send_to, payload_hex, msg_valid) - self.addMessageLink(Concepts.BID, bid_id, MessageTypes.XMR_BID_TXN_SIGS_FL, coin_a_lock_tx_sigs_l_msg_id, session=session) - self.log.info('Sent XMR_BID_TXN_SIGS_FL %s for bid %s', coin_a_lock_tx_sigs_l_msg_id.hex(), bid_id.hex()) + coin_a_lock_tx_sigs_l_msg_id = self.sendSmsg( + addr_send_from, addr_send_to, payload_hex, msg_valid + ) + self.addMessageLink( + Concepts.BID, + bid_id, + MessageTypes.XMR_BID_TXN_SIGS_FL, + coin_a_lock_tx_sigs_l_msg_id, + session=session, + ) + self.log.info( + "Sent XMR_BID_TXN_SIGS_FL %s for bid %s", + coin_a_lock_tx_sigs_l_msg_id.hex(), + bid_id.hex(), + ) if ci_from.watch_blocks_for_scripts() and self.isBchXmrSwap(offer): # BCH doesn't have segwit @@ -5908,13 +8302,23 @@ class BasicSwap(BaseApp): a_lock_tx_id = None else: a_lock_tx_id = ci_from.getTxid(xmr_swap.a_lock_tx) - a_lock_tx_vout = ci_from.getTxOutputPos(xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script) + a_lock_tx_vout = ci_from.getTxOutputPos( + xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script + ) if a_lock_tx_id: - self.log.debug('Waiting for lock tx A {} to {} chain for bid {}'.format(a_lock_tx_id.hex(), ci_from.coin_name(), bid_id.hex())) + self.log.debug( + "Waiting for lock tx A {} to {} chain for bid {}".format( + a_lock_tx_id.hex(), ci_from.coin_name(), bid_id.hex() + ) + ) else: find_script: bytes = ci_from.getScriptDest(xmr_swap.a_lock_tx_script) - self.log.debug('Waiting for lock tx A with script {} to {} chain for bid {}'.format(find_script.hex(), ci_from.coin_name(), bid_id.hex())) + self.log.debug( + "Waiting for lock tx A with script {} to {} chain for bid {}".format( + find_script.hex(), ci_from.coin_name(), bid_id.hex() + ) + ) if bid.xmr_a_lock_tx is None: bid.xmr_a_lock_tx = SwapTx( @@ -5928,80 +8332,103 @@ class BasicSwap(BaseApp): bid.setState(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS) self.watchXmrSwap(bid, offer, xmr_swap, session) self.saveBidInSession(bid_id, bid, session, xmr_swap) - except Exception as ex: + except Exception as e: # noqa: F841 if self.debug: self.log.error(traceback.format_exc()) def sendXmrBidCoinALockTx(self, bid_id: bytes, session) -> None: # Offerer/Leader. Send coin A lock tx - self.log.debug('Sending coin A lock tx for adaptor-sig bid %s', bid_id.hex()) + self.log.debug("Sending coin A lock tx for adaptor-sig bid %s", bid_id.hex()) bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession(session, bid.offer_id, sent=False) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + offer, xmr_offer = self.getXmrOfferFromSession( + session, bid.offer_id, sent=False + ) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) coin_from = Coins(offer.coin_to if reverse_bid else offer.coin_from) coin_to = Coins(offer.coin_from if reverse_bid else offer.coin_to) ci_from = self.ci(coin_from) - ci_to = self.ci(coin_to) a_fee_rate: int = xmr_offer.b_fee_rate if reverse_bid else xmr_offer.a_fee_rate - kal = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KAL) + kal = self.getPathKey( + coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KAL + ) # Prove leader can sign for kal, sent in MSG4F - xmr_swap.kal_sig = ci_from.signCompact(kal, 'proof key owned for swap') + xmr_swap.kal_sig = ci_from.signCompact(kal, "proof key owned for swap") # Create Script lock spend tx xmr_swap.a_lock_spend_tx = ci_from.createSCLockSpendTx( - xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script, + xmr_swap.a_lock_tx, + xmr_swap.a_lock_tx_script, xmr_swap.dest_af, - a_fee_rate, xmr_swap.vkbv) + a_fee_rate, + xmr_swap.vkbv, + ) xmr_swap.a_lock_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_spend_tx) if bid.xmr_a_lock_tx: bid.xmr_a_lock_tx.spend_txid = xmr_swap.a_lock_spend_tx_id prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap) - xmr_swap.al_lock_spend_tx_esig = ci_from.signTxOtVES(kal, xmr_swap.pkasf, xmr_swap.a_lock_spend_tx, 0, xmr_swap.a_lock_tx_script, prevout_amount) - ''' + xmr_swap.al_lock_spend_tx_esig = ci_from.signTxOtVES( + kal, + xmr_swap.pkasf, + xmr_swap.a_lock_spend_tx, + 0, + xmr_swap.a_lock_tx_script, + prevout_amount, + ) + """ # Double check a_lock_spend_tx is valid # Fails for part_blind ci_from.verifySCLockSpendTx( xmr_swap.a_lock_spend_tx, xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script, xmr_swap.dest_af, a_fee_rate, xmr_swap.vkbv) - ''' + """ lock_tx_sent: bool = False # publishalocktx if bid.xmr_a_lock_tx and bid.xmr_a_lock_tx.state: if bid.xmr_a_lock_tx.state >= TxStates.TX_SENT: - self.log.warning('Lock tx has already been sent {}'.format(bid.xmr_a_lock_tx.txid.hex())) + self.log.warning( + "Lock tx has already been sent {}".format( + bid.xmr_a_lock_tx.txid.hex() + ) + ) lock_tx_sent = True if lock_tx_sent is False: lock_tx_signed = ci_from.signTxWithWallet(xmr_swap.a_lock_tx) txid_hex = ci_from.publishTx(lock_tx_signed) if txid_hex != b2h(xmr_swap.a_lock_tx_id): - self.log.info('Recomputing refund transactions and txids after lock tx publish') + self.log.info( + "Recomputing refund transactions and txids after lock tx publish" + ) xmr_swap.a_lock_tx = lock_tx_signed xmr_swap.a_lock_tx_id = bytes.fromhex(txid_hex) tx = ci_from.loadTx(xmr_swap.a_lock_refund_tx) tx.vin[0].prevout.hash = b2i(xmr_swap.a_lock_tx_id) xmr_swap.a_lock_refund_tx = tx.serialize_without_witness() - xmr_swap.a_lock_refund_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_tx) + xmr_swap.a_lock_refund_tx_id = ci_from.getTxid( + xmr_swap.a_lock_refund_tx + ) tx = ci_from.loadTx(xmr_swap.a_lock_spend_tx) tx.vin[0].prevout.hash = b2i(xmr_swap.a_lock_tx_id) xmr_swap.a_lock_spend_tx = tx.serialize_without_witness() xmr_swap.a_lock_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_spend_tx) - vout_pos = ci_from.getTxOutputPos(xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script) + vout_pos = ci_from.getTxOutputPos( + xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script + ) if not bid.xmr_a_lock_tx: bid.xmr_a_lock_tx = SwapTx( bid_id=bid_id, @@ -6014,8 +8441,15 @@ class BasicSwap(BaseApp): bid.xmr_a_lock_tx.tx_data = lock_tx_signed bid.xmr_a_lock_tx.spend_txid = xmr_swap.a_lock_spend_tx_id - vout_pos = ci_from.getTxOutputPos(xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script) - self.log.debug('Submitted lock txn %s to %s chain for bid %s', txid_hex, ci_from.coin_name(), bid_id.hex()) + vout_pos = ci_from.getTxOutputPos( + xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script + ) + self.log.debug( + "Submitted lock txn %s to %s chain for bid %s", + txid_hex, + ci_from.coin_name(), + bid_id.hex(), + ) if bid.xmr_a_lock_tx is None: bid.xmr_a_lock_tx = SwapTx( @@ -6025,87 +8459,172 @@ class BasicSwap(BaseApp): vout=vout_pos, ) bid.xmr_a_lock_tx.setState(TxStates.TX_SENT) - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_PUBLISHED, '', session) + self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_PUBLISHED, "", session) bid.setState(BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX) self.watchXmrSwap(bid, offer, xmr_swap, session) delay = self.get_short_delay_event_seconds() - self.log.info('Sending lock spend tx message for bid %s in %d seconds', bid_id.hex(), delay) - self.createActionInSession(delay, ActionTypes.SEND_XMR_SWAP_LOCK_SPEND_MSG, bid_id, session) + self.log.info( + "Sending lock spend tx message for bid %s in %d seconds", + bid_id.hex(), + delay, + ) + self.createActionInSession( + delay, ActionTypes.SEND_XMR_SWAP_LOCK_SPEND_MSG, bid_id, session + ) self.saveBidInSession(bid_id, bid, session, xmr_swap) def sendXmrBidCoinBLockTx(self, bid_id: bytes, session) -> None: # Follower sending coin B lock tx - self.log.debug('Sending coin B lock tx for adaptor-sig bid %s', bid_id.hex()) + self.log.debug("Sending coin B lock tx for adaptor-sig bid %s", bid_id.hex()) bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession(session, bid.offer_id, sent=False) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + offer, xmr_offer = self.getXmrOfferFromSession( + session, bid.offer_id, sent=False + ) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) - ci_from = self.ci(offer.coin_to if reverse_bid else offer.coin_from) ci_to = self.ci(offer.coin_from if reverse_bid else offer.coin_to) b_fee_rate: int = xmr_offer.a_fee_rate if reverse_bid else xmr_offer.b_fee_rate was_sent: bool = bid.was_received if reverse_bid else bid.was_sent if self.findTxB(ci_to, xmr_swap, bid, session, was_sent) is True: - self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) + self.saveBidInSession( + bid_id, bid, session, xmr_swap, save_in_progress=offer + ) return if bid.xmr_b_lock_tx: - self.log.warning('Coin B lock tx {} exists for adaptor-sig bid {}'.format(bid.xmr_b_lock_tx.b_lock_tx_id, bid_id.hex())) + self.log.warning( + "Coin B lock tx {} exists for adaptor-sig bid {}".format( + bid.xmr_b_lock_tx.b_lock_tx_id, bid_id.hex() + ) + ) return if bid.debug_ind == DebugTypes.BID_STOP_AFTER_COIN_A_LOCK: - self.log.debug('Adaptor-sig bid %s: Stalling bid for testing: %d.', bid_id.hex(), bid.debug_ind) + self.log.debug( + "Adaptor-sig bid %s: Stalling bid for testing: %d.", + bid_id.hex(), + bid.debug_ind, + ) bid.setState(BidStates.BID_STALLED_FOR_TEST) - self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session) - self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) + self.logBidEvent( + bid.bid_id, + EventLogTypes.DEBUG_TWEAK_APPLIED, + "ind {}".format(bid.debug_ind), + session, + ) + self.saveBidInSession( + bid_id, bid, session, xmr_swap, save_in_progress=offer + ) return unlock_time = 0 - if bid.debug_ind in (DebugTypes.CREATE_INVALID_COIN_B_LOCK, DebugTypes.B_LOCK_TX_MISSED_SEND): + if bid.debug_ind in ( + DebugTypes.CREATE_INVALID_COIN_B_LOCK, + DebugTypes.B_LOCK_TX_MISSED_SEND, + ): bid.amount_to -= int(bid.amount_to * 0.1) - self.log.debug('Adaptor-sig bid %s: Debug %d - Reducing lock b txn amount by 10%% to %s.', bid_id.hex(), bid.debug_ind, ci_to.format_amount(bid.amount_to)) - self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session) + self.log.debug( + "Adaptor-sig bid %s: Debug %d - Reducing lock b txn amount by 10%% to %s.", + bid_id.hex(), + bid.debug_ind, + ci_to.format_amount(bid.amount_to), + ) + self.logBidEvent( + bid.bid_id, + EventLogTypes.DEBUG_TWEAK_APPLIED, + "ind {}".format(bid.debug_ind), + session, + ) if bid.debug_ind == DebugTypes.SEND_LOCKED_XMR: unlock_time = 10000 - self.log.debug('Adaptor-sig bid %s: Debug %d - Sending locked XMR.', bid_id.hex(), bid.debug_ind) - self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session) + self.log.debug( + "Adaptor-sig bid %s: Debug %d - Sending locked XMR.", + bid_id.hex(), + bid.debug_ind, + ) + self.logBidEvent( + bid.bid_id, + EventLogTypes.DEBUG_TWEAK_APPLIED, + "ind {}".format(bid.debug_ind), + session, + ) try: - b_lock_tx_id = ci_to.publishBLockTx(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, b_fee_rate, unlock_time=unlock_time) + b_lock_tx_id = ci_to.publishBLockTx( + xmr_swap.vkbv, + xmr_swap.pkbs, + bid.amount_to, + b_fee_rate, + unlock_time=unlock_time, + ) if bid.debug_ind == DebugTypes.B_LOCK_TX_MISSED_SEND: - self.log.debug('Adaptor-sig bid %s: Debug %d - Losing xmr lock tx %s.', bid_id.hex(), bid.debug_ind, b_lock_tx_id.hex()) - self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session) - raise TemporaryError('Fail for debug event') + self.log.debug( + "Adaptor-sig bid %s: Debug %d - Losing xmr lock tx %s.", + bid_id.hex(), + bid.debug_ind, + b_lock_tx_id.hex(), + ) + self.logBidEvent( + bid.bid_id, + EventLogTypes.DEBUG_TWEAK_APPLIED, + "ind {}".format(bid.debug_ind), + session, + ) + raise TemporaryError("Fail for debug event") except Exception as ex: if self.debug: self.log.error(traceback.format_exc()) - error_msg = 'publishBLockTx failed for bid {} with error {}'.format(bid_id.hex(), str(ex)) - num_retries = self.countBidEvents(bid, EventLogTypes.FAILED_TX_B_LOCK_PUBLISH, session) + error_msg = "publishBLockTx failed for bid {} with error {}".format( + bid_id.hex(), str(ex) + ) + num_retries = self.countBidEvents( + bid, EventLogTypes.FAILED_TX_B_LOCK_PUBLISH, session + ) if num_retries > 0: - error_msg += ', retry no. {}'.format(num_retries) + error_msg += ", retry no. {}".format(num_retries) self.log.error(error_msg) - if num_retries < 5 and (ci_to.is_transient_error(ex) or self.is_transient_error(ex)): + if num_retries < 5 and ( + ci_to.is_transient_error(ex) or self.is_transient_error(ex) + ): delay = self.get_delay_retry_seconds() - self.log.info('Retrying sending adaptor-sig swap chain B lock tx for bid %s in %d seconds', bid_id.hex(), delay) - self.createActionInSession(delay, ActionTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, session) + self.log.info( + "Retrying sending adaptor-sig swap chain B lock tx for bid %s in %d seconds", + bid_id.hex(), + delay, + ) + self.createActionInSession( + delay, ActionTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, session + ) else: - self.setBidError(bid_id, bid, 'publishBLockTx failed: ' + str(ex), save_bid=False) - self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) + self.setBidError( + bid_id, bid, "publishBLockTx failed: " + str(ex), save_bid=False + ) + self.saveBidInSession( + bid_id, bid, session, xmr_swap, save_in_progress=offer + ) - self.logBidEvent(bid.bid_id, EventLogTypes.FAILED_TX_B_LOCK_PUBLISH, str(ex), session) + self.logBidEvent( + bid.bid_id, EventLogTypes.FAILED_TX_B_LOCK_PUBLISH, str(ex), session + ) return - self.log.debug('Submitted lock txn %s to %s chain for bid %s', b_lock_tx_id.hex(), ci_to.coin_name(), bid_id.hex()) + self.log.debug( + "Submitted lock txn %s to %s chain for bid %s", + b_lock_tx_id.hex(), + ci_to.coin_name(), + bid_id.hex(), + ) bid.xmr_b_lock_tx = SwapTx( bid_id=bid_id, tx_type=TxTypes.XMR_SWAP_B_LOCK, @@ -6113,59 +8632,84 @@ class BasicSwap(BaseApp): ) xmr_swap.b_lock_tx_id = b_lock_tx_id bid.xmr_b_lock_tx.setState(TxStates.TX_SENT) - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_PUBLISHED, '', session) + self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_PUBLISHED, "", session) if bid.debug_ind == DebugTypes.BID_STOP_AFTER_COIN_B_LOCK: - self.log.debug('Adaptor-sig bid %s: Stalling bid for testing: %d.', bid_id.hex(), bid.debug_ind) + self.log.debug( + "Adaptor-sig bid %s: Stalling bid for testing: %d.", + bid_id.hex(), + bid.debug_ind, + ) bid.setState(BidStates.BID_STALLED_FOR_TEST) - self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session) + self.logBidEvent( + bid.bid_id, + EventLogTypes.DEBUG_TWEAK_APPLIED, + "ind {}".format(bid.debug_ind), + session, + ) self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) def sendXmrBidLockRelease(self, bid_id: bytes, session) -> None: # Leader sending lock tx a release secret (MSG5F) - self.log.debug('Sending bid secret for adaptor-sig bid %s', bid_id.hex()) + self.log.debug("Sending bid secret for adaptor-sig bid %s", bid_id.hex()) bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession(session, bid.offer_id, sent=False) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + offer, xmr_offer = self.getXmrOfferFromSession( + session, bid.offer_id, sent=False + ) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) - coin_from = Coins(offer.coin_to if reverse_bid else offer.coin_from) - coin_to = Coins(offer.coin_from if reverse_bid else offer.coin_to) msg_buf = XmrBidLockReleaseMessage( - bid_msg_id=bid_id, - al_lock_spend_tx_esig=xmr_swap.al_lock_spend_tx_esig) + bid_msg_id=bid_id, al_lock_spend_tx_esig=xmr_swap.al_lock_spend_tx_esig + ) msg_bytes = msg_buf.to_bytes() - payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_LOCK_RELEASE_LF) + msg_bytes.hex() + payload_hex = ( + str.format("{:02x}", MessageTypes.XMR_BID_LOCK_RELEASE_LF) + msg_bytes.hex() + ) addr_send_from: str = bid.bid_addr if reverse_bid else offer.addr_from addr_send_to: str = offer.addr_from if reverse_bid else bid.bid_addr msg_valid: int = self.getActiveBidMsgValidTime() - coin_a_lock_release_msg_id = self.sendSmsg(addr_send_from, addr_send_to, payload_hex, msg_valid) - self.addMessageLink(Concepts.BID, bid_id, MessageTypes.XMR_BID_LOCK_RELEASE_LF, coin_a_lock_release_msg_id, session=session) + coin_a_lock_release_msg_id = self.sendSmsg( + addr_send_from, addr_send_to, payload_hex, msg_valid + ) + self.addMessageLink( + Concepts.BID, + bid_id, + MessageTypes.XMR_BID_LOCK_RELEASE_LF, + coin_a_lock_release_msg_id, + session=session, + ) bid.setState(BidStates.XMR_SWAP_LOCK_RELEASED) self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) def redeemXmrBidCoinALockTx(self, bid_id: bytes, session) -> None: # Follower redeeming A lock tx - self.log.debug('Redeeming coin A lock tx for adaptor-sig bid %s', bid_id.hex()) + self.log.debug("Redeeming coin A lock tx for adaptor-sig bid %s", bid_id.hex()) bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession(session, bid.offer_id, sent=False) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + offer, xmr_offer = self.getXmrOfferFromSession( + session, bid.offer_id, sent=False + ) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns: - self.log.warning('Not redeeming coin A lock tx for bid {}: Chain A lock refund tx already exists.'.format(bid_id.hex())) + self.log.warning( + "Not redeeming coin A lock tx for bid {}: Chain A lock refund tx already exists.".format( + bid_id.hex() + ) + ) return reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) @@ -6175,47 +8719,91 @@ class BasicSwap(BaseApp): ci_to = self.ci(coin_to) for_ed25519: bool = True if ci_to.curve_type() == Curves.ed25519 else False - kbsf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBSF, for_ed25519) - kaf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KAF) + kbsf = self.getPathKey( + coin_from, + coin_to, + bid.created_at, + xmr_swap.contract_count, + KeyTypes.KBSF, + for_ed25519, + ) + kaf = self.getPathKey( + coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KAF + ) if not self.isBchXmrSwap(offer): # segwit coins sign the transaction - al_lock_spend_sig = ci_from.decryptOtVES(kbsf, xmr_swap.al_lock_spend_tx_esig) + al_lock_spend_sig = ci_from.decryptOtVES( + kbsf, xmr_swap.al_lock_spend_tx_esig + ) prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap) - v = ci_from.verifyTxSig(xmr_swap.a_lock_spend_tx, al_lock_spend_sig, xmr_swap.pkal, 0, xmr_swap.a_lock_tx_script, prevout_amount) - ensure(v, 'Invalid coin A lock tx spend tx leader sig') + v = ci_from.verifyTxSig( + xmr_swap.a_lock_spend_tx, + al_lock_spend_sig, + xmr_swap.pkal, + 0, + xmr_swap.a_lock_tx_script, + prevout_amount, + ) + ensure(v, "Invalid coin A lock tx spend tx leader sig") - af_lock_spend_sig = ci_from.signTx(kaf, xmr_swap.a_lock_spend_tx, 0, xmr_swap.a_lock_tx_script, prevout_amount) - v = ci_from.verifyTxSig(xmr_swap.a_lock_spend_tx, af_lock_spend_sig, xmr_swap.pkaf, 0, xmr_swap.a_lock_tx_script, prevout_amount) - ensure(v, 'Invalid coin A lock tx spend tx follower sig') + af_lock_spend_sig = ci_from.signTx( + kaf, + xmr_swap.a_lock_spend_tx, + 0, + xmr_swap.a_lock_tx_script, + prevout_amount, + ) + v = ci_from.verifyTxSig( + xmr_swap.a_lock_spend_tx, + af_lock_spend_sig, + xmr_swap.pkaf, + 0, + xmr_swap.a_lock_tx_script, + prevout_amount, + ) + ensure(v, "Invalid coin A lock tx spend tx follower sig") witness_stack = [] if coin_from not in (Coins.DCR,): - witness_stack += [b'',] + witness_stack += [ + b"", + ] witness_stack += [ al_lock_spend_sig, af_lock_spend_sig, xmr_swap.a_lock_tx_script, ] - xmr_swap.a_lock_spend_tx = ci_from.setTxSignature(xmr_swap.a_lock_spend_tx, witness_stack) + xmr_swap.a_lock_spend_tx = ci_from.setTxSignature( + xmr_swap.a_lock_spend_tx, witness_stack + ) else: # bch signs the output pkh tx = ci_from.loadTx(xmr_swap.a_lock_spend_tx) out1 = tx.vout[0].scriptPubKey out1_sig = ci_from.decryptOtVES(kbsf, xmr_swap.al_lock_spend_tx_esig) v = ci_from.verifyDataSig(out1, out1_sig, xmr_swap.pkal) - ensure(v, 'Invalid signature for lock spend txn') + ensure(v, "Invalid signature for lock spend txn") # update prevout after tx was signed tx.vin[0].prevout.hash = b2i(xmr_swap.a_lock_tx_id) - tx.vin[0].scriptSig = ci_from.getScriptScriptSig(xmr_swap.a_lock_tx_script, out1_sig) + tx.vin[0].scriptSig = ci_from.getScriptScriptSig( + xmr_swap.a_lock_tx_script, out1_sig + ) xmr_swap.a_lock_spend_tx = tx.serialize_without_witness() xmr_swap.a_lock_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_spend_tx) txid = bytes.fromhex(ci_from.publishTx(xmr_swap.a_lock_spend_tx)) - self.log.debug('Submitted lock spend txn %s to %s chain for bid %s', txid.hex(), ci_from.coin_name(), bid_id.hex()) - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_SPEND_TX_PUBLISHED, '', session) + self.log.debug( + "Submitted lock spend txn %s to %s chain for bid %s", + txid.hex(), + ci_from.coin_name(), + bid_id.hex(), + ) + self.logBidEvent( + bid.bid_id, EventLogTypes.LOCK_TX_A_SPEND_TX_PUBLISHED, "", session + ) if bid.xmr_a_lock_spend_tx is None: bid.xmr_a_lock_spend_tx = SwapTx( bid_id=bid_id, @@ -6224,21 +8812,27 @@ class BasicSwap(BaseApp): ) bid.xmr_a_lock_spend_tx.setState(TxStates.TX_NONE) else: - self.log.warning('Chain A lock TX %s already exists for bid %s', bid.xmr_a_lock_spend_tx.txid.hex(), bid_id.hex()) + self.log.warning( + "Chain A lock TX %s already exists for bid %s", + bid.xmr_a_lock_spend_tx.txid.hex(), + bid_id.hex(), + ) self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) def redeemXmrBidCoinBLockTx(self, bid_id: bytes, session) -> None: # Leader redeeming B lock tx - self.log.debug('Redeeming coin B lock tx for adaptor-sig bid %s', bid_id.hex()) + self.log.debug("Redeeming coin B lock tx for adaptor-sig bid %s", bid_id.hex()) bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession(session, bid.offer_id, sent=False) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + offer, xmr_offer = self.getXmrOfferFromSession( + session, bid.offer_id, sent=False + ) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) coin_from = Coins(offer.coin_to if reverse_bid else offer.coin_from) @@ -6251,26 +8845,47 @@ class BasicSwap(BaseApp): chain_height = ci_to.getChainHeight() lock_tx_depth = (chain_height - bid.xmr_b_lock_tx.chain_height) + 1 if lock_tx_depth < ci_to.depth_spendable(): - raise TemporaryError(f'Chain B lock tx depth {lock_tx_depth} < required for spending.') + raise TemporaryError( + f"Chain B lock tx depth {lock_tx_depth} < required for spending." + ) if TxTypes.BCH_MERCY in bid.txns: - self.log.info('Using keyshare from mercy tx.') + self.log.info("Using keyshare from mercy tx.") kbsf = bid.txns[TxTypes.BCH_MERCY].tx_data pkbsf = ci_to.getPubkey(kbsf) - ensure(pkbsf == xmr_swap.pkbsf, 'Keyshare from mercy tx does not match expected pubkey') + ensure( + pkbsf == xmr_swap.pkbsf, + "Keyshare from mercy tx does not match expected pubkey", + ) elif TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE in bid.txns: - self.log.info('Using keyshare from swipe tx.') + self.log.info("Using keyshare from swipe tx.") kbsf = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE].tx_data pkbsf = ci_to.getPubkey(kbsf) - ensure(pkbsf == xmr_swap.pkbsf, 'Keyshare from swipe tx does not match expected pubkey') + ensure( + pkbsf == xmr_swap.pkbsf, + "Keyshare from swipe tx does not match expected pubkey", + ) else: # Extract the leader's decrypted signature and use it to recover the follower's privatekey - xmr_swap.al_lock_spend_tx_sig = ci_from.extractLeaderSig(xmr_swap.a_lock_spend_tx) - kbsf = ci_from.recoverEncKey(xmr_swap.al_lock_spend_tx_esig, xmr_swap.al_lock_spend_tx_sig, xmr_swap.pkasf) - assert (kbsf is not None) + xmr_swap.al_lock_spend_tx_sig = ci_from.extractLeaderSig( + xmr_swap.a_lock_spend_tx + ) + kbsf = ci_from.recoverEncKey( + xmr_swap.al_lock_spend_tx_esig, + xmr_swap.al_lock_spend_tx_sig, + xmr_swap.pkasf, + ) + assert kbsf is not None for_ed25519: bool = True if ci_to.curve_type() == Curves.ed25519 else False - kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBSL, for_ed25519) + kbsl = self.getPathKey( + coin_from, + coin_to, + bid.created_at, + xmr_swap.contract_count, + KeyTypes.KBSL, + for_ed25519, + ) vkbs = ci_to.sumKeys(kbsl, kbsf) if coin_to == (Coins.XMR, Coins.WOW): @@ -6278,28 +8893,64 @@ class BasicSwap(BaseApp): elif coin_to in (Coins.PART_BLIND, Coins.PART_ANON): address_to = self.getCachedStealthAddressForCoin(coin_to, session) else: - address_to = self.getReceiveAddressFromPool(coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_SPEND, session) + address_to = self.getReceiveAddressFromPool( + coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_SPEND, session + ) lock_tx_vout = bid.getLockTXBVout() - txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, b_fee_rate, bid.chain_b_height_start, lock_tx_vout=lock_tx_vout) - self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex()) - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, '', session) + txid = ci_to.spendBLockTx( + xmr_swap.b_lock_tx_id, + address_to, + xmr_swap.vkbv, + vkbs, + bid.amount_to, + b_fee_rate, + bid.chain_b_height_start, + lock_tx_vout=lock_tx_vout, + ) + self.log.debug( + "Submitted lock B spend txn %s to %s chain for bid %s", + txid.hex(), + ci_to.coin_name(), + bid_id.hex(), + ) + self.logBidEvent( + bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, "", session + ) except Exception as ex: - error_msg = 'spendBLockTx failed for bid {} with error {}'.format(bid_id.hex(), str(ex)) - num_retries = self.countBidEvents(bid, EventLogTypes.FAILED_TX_B_SPEND, session) + error_msg = "spendBLockTx failed for bid {} with error {}".format( + bid_id.hex(), str(ex) + ) + num_retries = self.countBidEvents( + bid, EventLogTypes.FAILED_TX_B_SPEND, session + ) if num_retries > 0: - error_msg += ', retry no. {}'.format(num_retries) + error_msg += ", retry no. {}".format(num_retries) self.log.error(error_msg) - if num_retries < 100 and (ci_to.is_transient_error(ex) or self.is_transient_error(ex)): + if num_retries < 100 and ( + ci_to.is_transient_error(ex) or self.is_transient_error(ex) + ): delay = self.get_delay_retry_seconds() - self.log.info('Retrying sending adaptor-sig swap chain B spend tx for bid %s in %d seconds', bid_id.hex(), delay) - self.createActionInSession(delay, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, session) + self.log.info( + "Retrying sending adaptor-sig swap chain B spend tx for bid %s in %d seconds", + bid_id.hex(), + delay, + ) + self.createActionInSession( + delay, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, session + ) else: - self.setBidError(bid_id, bid, 'spendBLockTx failed: ' + str(ex), save_bid=False) - self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) + self.setBidError( + bid_id, bid, "spendBLockTx failed: " + str(ex), save_bid=False + ) + self.saveBidInSession( + bid_id, bid, session, xmr_swap, save_in_progress=offer + ) - self.logBidEvent(bid.bid_id, EventLogTypes.FAILED_TX_B_SPEND, str(ex), session) + self.logBidEvent( + bid.bid_id, EventLogTypes.FAILED_TX_B_SPEND, str(ex), session + ) return bid.xmr_b_lock_tx.spend_txid = txid @@ -6312,15 +8963,17 @@ class BasicSwap(BaseApp): def recoverXmrBidCoinBLockTx(self, bid_id: bytes, session) -> None: # Follower recovering B lock tx - self.log.debug('Recovering coin B lock tx for adaptor-sig bid %s', bid_id.hex()) + self.log.debug("Recovering coin B lock tx for adaptor-sig bid %s", bid_id.hex()) bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession(session, bid.offer_id, sent=False) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + offer, xmr_offer = self.getXmrOfferFromSession( + session, bid.offer_id, sent=False + ) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) coin_from = Coins(offer.coin_to if reverse_bid else offer.coin_from) @@ -6330,12 +8983,25 @@ class BasicSwap(BaseApp): b_fee_rate: int = xmr_offer.a_fee_rate if reverse_bid else xmr_offer.b_fee_rate # Extract the follower's decrypted signature and use it to recover the leader's privatekey - af_lock_refund_spend_tx_sig = ci_from.extractFollowerSig(xmr_swap.a_lock_refund_spend_tx) - kbsl = ci_from.recoverEncKey(xmr_swap.af_lock_refund_spend_tx_esig, af_lock_refund_spend_tx_sig, xmr_swap.pkasl) - assert (kbsl is not None) + af_lock_refund_spend_tx_sig = ci_from.extractFollowerSig( + xmr_swap.a_lock_refund_spend_tx + ) + kbsl = ci_from.recoverEncKey( + xmr_swap.af_lock_refund_spend_tx_esig, + af_lock_refund_spend_tx_sig, + xmr_swap.pkasl, + ) + assert kbsl is not None for_ed25519: bool = True if ci_to.curve_type() == Curves.ed25519 else False - kbsf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBSF, for_ed25519) + kbsf = self.getPathKey( + coin_from, + coin_to, + bid.created_at, + xmr_swap.contract_count, + KeyTypes.KBSF, + for_ed25519, + ) vkbs = ci_to.sumKeys(kbsl, kbsf) try: @@ -6344,30 +9010,69 @@ class BasicSwap(BaseApp): elif coin_to in (Coins.PART_BLIND, Coins.PART_ANON): address_to = self.getCachedStealthAddressForCoin(coin_to, session) else: - address_to = self.getReceiveAddressFromPool(coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_REFUND, session) + address_to = self.getReceiveAddressFromPool( + coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_REFUND, session + ) lock_tx_vout = bid.getLockTXBVout() - txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, b_fee_rate, bid.chain_b_height_start, lock_tx_vout=lock_tx_vout) - self.log.debug('Submitted lock B refund txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex()) - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED, '', session) + txid = ci_to.spendBLockTx( + xmr_swap.b_lock_tx_id, + address_to, + xmr_swap.vkbv, + vkbs, + bid.amount_to, + b_fee_rate, + bid.chain_b_height_start, + lock_tx_vout=lock_tx_vout, + ) + self.log.debug( + "Submitted lock B refund txn %s to %s chain for bid %s", + txid.hex(), + ci_to.coin_name(), + bid_id.hex(), + ) + self.logBidEvent( + bid.bid_id, EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED, "", session + ) except Exception as ex: # TODO: Make min-conf 10? - error_msg = 'spendBLockTx refund failed for bid {} with error {}'.format(bid_id.hex(), str(ex)) - num_retries = self.countBidEvents(bid, EventLogTypes.FAILED_TX_B_REFUND, session) + error_msg = "spendBLockTx refund failed for bid {} with error {}".format( + bid_id.hex(), str(ex) + ) + num_retries = self.countBidEvents( + bid, EventLogTypes.FAILED_TX_B_REFUND, session + ) if num_retries > 0: - error_msg += ', retry no. {}'.format(num_retries) + error_msg += ", retry no. {}".format(num_retries) self.log.error(error_msg) str_error = str(ex) - if num_retries < 100 and (ci_to.is_transient_error(ex) or self.is_transient_error(ex)): + if num_retries < 100 and ( + ci_to.is_transient_error(ex) or self.is_transient_error(ex) + ): delay = self.get_delay_retry_seconds() - self.log.info('Retrying sending adaptor-sig swap chain B refund tx for bid %s in %d seconds', bid_id.hex(), delay) - self.createActionInSession(delay, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B, bid_id, session) + self.log.info( + "Retrying sending adaptor-sig swap chain B refund tx for bid %s in %d seconds", + bid_id.hex(), + delay, + ) + self.createActionInSession( + delay, ActionTypes.RECOVER_XMR_SWAP_LOCK_TX_B, bid_id, session + ) else: - self.setBidError(bid_id, bid, 'spendBLockTx for refund failed: ' + str(ex), save_bid=False) - self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) + self.setBidError( + bid_id, + bid, + "spendBLockTx for refund failed: " + str(ex), + save_bid=False, + ) + self.saveBidInSession( + bid_id, bid, session, xmr_swap, save_in_progress=offer + ) - self.logBidEvent(bid.bid_id, EventLogTypes.FAILED_TX_B_REFUND, str_error, session) + self.logBidEvent( + bid.bid_id, EventLogTypes.FAILED_TX_B_REFUND, str_error, session + ) return bid.xmr_b_lock_tx.spend_txid = txid @@ -6379,53 +9084,62 @@ class BasicSwap(BaseApp): def sendXmrBidCoinALockSpendTxMsg(self, bid_id: bytes, session) -> None: # Send MSG4F L -> F - self.log.debug('Sending coin A lock spend tx msg for adaptor-sig bid %s', bid_id.hex()) + self.log.debug( + "Sending coin A lock spend tx msg for adaptor-sig bid %s", bid_id.hex() + ) bid, xmr_swap = self.getXmrBidFromSession(session, bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession(session, bid.offer_id, sent=False) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + offer, xmr_offer = self.getXmrOfferFromSession( + session, bid.offer_id, sent=False + ) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) - ci_from = self.ci(offer.coin_to if reverse_bid else offer.coin_from) addr_send_from: str = bid.bid_addr if reverse_bid else offer.addr_from addr_send_to: str = offer.addr_from if reverse_bid else bid.bid_addr msg_buf = XmrBidLockSpendTxMessage( bid_msg_id=bid_id, a_lock_spend_tx=xmr_swap.a_lock_spend_tx, - kal_sig=xmr_swap.kal_sig) + kal_sig=xmr_swap.kal_sig, + ) msg_bytes = msg_buf.to_bytes() - payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_LOCK_SPEND_TX_LF) + msg_bytes.hex() + payload_hex = ( + str.format("{:02x}", MessageTypes.XMR_BID_LOCK_SPEND_TX_LF) + + msg_bytes.hex() + ) msg_valid: int = self.getActiveBidMsgValidTime() - xmr_swap.coin_a_lock_refund_spend_tx_msg_id = self.sendSmsg(addr_send_from, addr_send_to, payload_hex, msg_valid) + xmr_swap.coin_a_lock_refund_spend_tx_msg_id = self.sendSmsg( + addr_send_from, addr_send_to, payload_hex, msg_valid + ) bid.setState(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX) self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) def processXmrBidCoinALockSigs(self, msg) -> None: # Leader processing MSG3L - self.log.debug('Processing xmr coin a follower lock sigs msg %s', msg['msgid']) - now: int = self.getTime() - msg_bytes = bytes.fromhex(msg['hex'][2:-2]) + self.log.debug("Processing xmr coin a follower lock sigs msg %s", msg["msgid"]) + + msg_bytes = bytes.fromhex(msg["hex"][2:-2]) msg_data = XmrBidLockTxSigsMessage(init_all=False) msg_data.from_bytes(msg_bytes) - ensure(len(msg_data.bid_msg_id) == 28, 'Bad bid_msg_id length') + ensure(len(msg_data.bid_msg_id) == 28, "Bad bid_msg_id length") bid_id = msg_data.bid_msg_id bid, xmr_swap = self.getXmrBid(bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) coin_from = Coins(offer.coin_to if reverse_bid else offer.coin_from) @@ -6435,59 +9149,108 @@ class BasicSwap(BaseApp): ci_from = self.ci(coin_from) ci_to = self.ci(coin_to) - ensure(msg['to'] == addr_sent_to, 'Received on incorrect address') - ensure(msg['from'] == addr_sent_from, 'Sent from incorrect address') + ensure(msg["to"] == addr_sent_to, "Received on incorrect address") + ensure(msg["from"] == addr_sent_from, "Sent from incorrect address") try: - allowed_states = [BidStates.BID_ACCEPTED, ] + allowed_states = [ + BidStates.BID_ACCEPTED, + ] if bid.was_sent and offer.was_sent: allowed_states.append(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS) - ensure(bid.state in allowed_states, 'Invalid state for bid {}'.format(bid.state)) - xmr_swap.af_lock_refund_spend_tx_esig = msg_data.af_lock_refund_spend_tx_esig + ensure( + bid.state in allowed_states, + "Invalid state for bid {}".format(bid.state), + ) + xmr_swap.af_lock_refund_spend_tx_esig = ( + msg_data.af_lock_refund_spend_tx_esig + ) xmr_swap.af_lock_refund_tx_sig = msg_data.af_lock_refund_tx_sig for_ed25519: bool = True if ci_to.curve_type() == Curves.ed25519 else False - kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBSL, for_ed25519) - kal = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KAL) + kbsl = self.getPathKey( + coin_from, + coin_to, + bid.created_at, + xmr_swap.contract_count, + KeyTypes.KBSL, + for_ed25519, + ) + kal = self.getPathKey( + coin_from, + coin_to, + bid.created_at, + xmr_swap.contract_count, + KeyTypes.KAL, + ) if not self.isBchXmrSwap(offer): # segwit coins sign the transaction - xmr_swap.af_lock_refund_spend_tx_sig = ci_from.decryptOtVES(kbsl, xmr_swap.af_lock_refund_spend_tx_esig) + xmr_swap.af_lock_refund_spend_tx_sig = ci_from.decryptOtVES( + kbsl, xmr_swap.af_lock_refund_spend_tx_esig + ) prevout_amount = ci_from.getLockRefundTxSwapOutputValue(bid, xmr_swap) - al_lock_refund_spend_tx_sig = ci_from.signTx(kal, xmr_swap.a_lock_refund_spend_tx, 0, xmr_swap.a_lock_refund_tx_script, prevout_amount) + al_lock_refund_spend_tx_sig = ci_from.signTx( + kal, + xmr_swap.a_lock_refund_spend_tx, + 0, + xmr_swap.a_lock_refund_tx_script, + prevout_amount, + ) - self.log.debug('Setting lock refund spend tx sigs') + self.log.debug("Setting lock refund spend tx sigs") witness_stack = [] - if coin_from not in (Coins.DCR, ): - witness_stack += [b'',] + if coin_from not in (Coins.DCR,): + witness_stack += [ + b"", + ] witness_stack += [ al_lock_refund_spend_tx_sig, xmr_swap.af_lock_refund_spend_tx_sig, bytes((1,)), xmr_swap.a_lock_refund_tx_script, ] - signed_tx = ci_from.setTxSignature(xmr_swap.a_lock_refund_spend_tx, witness_stack) - ensure(signed_tx, 'setTxSignature failed') + signed_tx = ci_from.setTxSignature( + xmr_swap.a_lock_refund_spend_tx, witness_stack + ) + ensure(signed_tx, "setTxSignature failed") xmr_swap.a_lock_refund_spend_tx = signed_tx - v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_spend_tx, xmr_swap.af_lock_refund_spend_tx_sig, xmr_swap.pkaf, 0, xmr_swap.a_lock_refund_tx_script, prevout_amount) - ensure(v, 'Invalid signature for lock refund spend txn') + v = ci_from.verifyTxSig( + xmr_swap.a_lock_refund_spend_tx, + xmr_swap.af_lock_refund_spend_tx_sig, + xmr_swap.pkaf, + 0, + xmr_swap.a_lock_refund_tx_script, + prevout_amount, + ) + ensure(v, "Invalid signature for lock refund spend txn") xmr_swap_1.addLockRefundSigs(self, xmr_swap, ci_from) else: # bch signs the output pkh tx = ci_from.loadTx(xmr_swap.a_lock_refund_spend_tx) out1 = tx.vout[0].scriptPubKey - out1_sig = ci_from.decryptOtVES(kbsl, xmr_swap.af_lock_refund_spend_tx_esig) + out1_sig = ci_from.decryptOtVES( + kbsl, xmr_swap.af_lock_refund_spend_tx_esig + ) v = ci_from.verifyDataSig(out1, out1_sig, xmr_swap.pkaf) - ensure(v, 'Invalid signature for lock refund spend txn') + ensure(v, "Invalid signature for lock refund spend txn") - tx.vin[0].scriptSig = ci_from.getScriptScriptSig(xmr_swap.a_lock_refund_tx_script, out1_sig) + tx.vin[0].scriptSig = ci_from.getScriptScriptSig( + xmr_swap.a_lock_refund_tx_script, out1_sig + ) tx.vin[0].prevout.hash = b2i(xmr_swap.a_lock_tx_id) xmr_swap.a_lock_refund_spend_tx = tx.serialize_without_witness() - xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_spend_tx) + xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxid( + xmr_swap.a_lock_refund_spend_tx + ) delay = self.get_delay_event_seconds() - self.log.info('Sending coin A lock tx for adaptor-sig bid %s in %d seconds', bid_id.hex(), delay) + self.log.info( + "Sending coin A lock tx for adaptor-sig bid %s in %d seconds", + bid_id.hex(), + delay, + ) self.createAction(delay, ActionTypes.SEND_XMR_SWAP_LOCK_TX_A, bid_id) bid.setState(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS) @@ -6499,32 +9262,31 @@ class BasicSwap(BaseApp): def processXmrBidLockSpendTx(self, msg) -> None: # Follower receiving MSG4F - self.log.debug('Processing adaptor-sig bid lock spend tx msg %s', msg['msgid']) - now: int = self.getTime() - msg_bytes = bytes.fromhex(msg['hex'][2:-2]) + self.log.debug("Processing adaptor-sig bid lock spend tx msg %s", msg["msgid"]) + + msg_bytes = bytes.fromhex(msg["hex"][2:-2]) msg_data = XmrBidLockSpendTxMessage(init_all=False) msg_data.from_bytes(msg_bytes) - ensure(len(msg_data.bid_msg_id) == 28, 'Bad bid_msg_id length') + ensure(len(msg_data.bid_msg_id) == 28, "Bad bid_msg_id length") bid_id = msg_data.bid_msg_id bid, xmr_swap = self.getXmrBid(bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) ci_from = self.ci(offer.coin_to if reverse_bid else offer.coin_from) - ci_to = self.ci(offer.coin_from if reverse_bid else offer.coin_to) addr_sent_from: str = bid.bid_addr if reverse_bid else offer.addr_from addr_sent_to: str = offer.addr_from if reverse_bid else bid.bid_addr a_fee_rate: int = xmr_offer.b_fee_rate if reverse_bid else xmr_offer.a_fee_rate - ensure(msg['to'] == addr_sent_to, 'Received on incorrect address') - ensure(msg['from'] == addr_sent_from, 'Sent from incorrect address') + ensure(msg["to"] == addr_sent_to, "Received on incorrect address") + ensure(msg["from"] == addr_sent_from, "Sent from incorrect address") try: xmr_swap.a_lock_spend_tx = msg_data.a_lock_spend_tx @@ -6535,16 +9297,26 @@ class BasicSwap(BaseApp): ci_from.verifySCLockSpendTx( xmr_swap.a_lock_spend_tx, - xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script, - xmr_swap.dest_af, a_fee_rate, xmr_swap.vkbv) + xmr_swap.a_lock_tx, + xmr_swap.a_lock_tx_script, + xmr_swap.dest_af, + a_fee_rate, + xmr_swap.vkbv, + ) - ci_from.verifyCompactSig(xmr_swap.pkal, 'proof key owned for swap', xmr_swap.kal_sig) + ci_from.verifyCompactSig( + xmr_swap.pkal, "proof key owned for swap", xmr_swap.kal_sig + ) if bid.state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS: bid.setState(BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX) bid.setState(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX) else: - self.log.warning('processXmrBidLockSpendTx bid {} unexpected state {}'.format(bid_id.hex(), bid.state)) + self.log.warning( + "processXmrBidLockSpendTx bid {} unexpected state {}".format( + bid_id.hex(), bid.state + ) + ) self.saveBid(bid_id, bid, xmr_swap=xmr_swap) except Exception as ex: if self.debug: @@ -6555,30 +9327,43 @@ class BasicSwap(BaseApp): self.swaps_in_progress[bid_id] = (bid, offer) def processXmrSplitMessage(self, msg) -> None: - self.log.debug('Processing xmr split msg %s', msg['msgid']) + self.log.debug("Processing xmr split msg %s", msg["msgid"]) now: int = self.getTime() - msg_bytes = bytes.fromhex(msg['hex'][2:-2]) + msg_bytes = bytes.fromhex(msg["hex"][2:-2]) msg_data = XmrSplitMessage(init_all=False) msg_data.from_bytes(msg_bytes) # Validate data - ensure(len(msg_data.msg_id) == 28, 'Bad msg_id length') - self.log.debug('for bid %s', msg_data.msg_id.hex()) + ensure(len(msg_data.msg_id) == 28, "Bad msg_id length") + self.log.debug("for bid %s", msg_data.msg_id.hex()) # TODO: Wait for bid msg to arrive first - if msg_data.msg_type == XmrSplitMsgTypes.BID or msg_data.msg_type == XmrSplitMsgTypes.BID_ACCEPT: + if ( + msg_data.msg_type == XmrSplitMsgTypes.BID + or msg_data.msg_type == XmrSplitMsgTypes.BID_ACCEPT + ): session = self.openSession() try: - q = session.execute(text('SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x\'{}\' AND msg_type = {} AND msg_sequence = {}'.format(msg_data.msg_id.hex(), msg_data.msg_type, msg_data.sequence))).first() + q = session.execute( + text( + "SELECT COUNT(*) FROM xmr_split_data WHERE bid_id = x'{}' AND msg_type = {} AND msg_sequence = {}".format( + msg_data.msg_id.hex(), msg_data.msg_type, msg_data.sequence + ) + ) + ).first() num_exists = q[0] if num_exists > 0: - self.log.warning('Ignoring duplicate xmr_split_data entry: ({}, {}, {})'.format(msg_data.msg_id.hex(), msg_data.msg_type, msg_data.sequence)) + self.log.warning( + "Ignoring duplicate xmr_split_data entry: ({}, {}, {})".format( + msg_data.msg_id.hex(), msg_data.msg_type, msg_data.sequence + ) + ) return dbr = XmrSplitData() - dbr.addr_from = msg['from'] - dbr.addr_to = msg['to'] + dbr.addr_from = msg["from"] + dbr.addr_to = msg["to"] dbr.bid_id = msg_data.msg_id dbr.msg_type = msg_data.msg_type dbr.msg_sequence = msg_data.sequence @@ -6589,43 +9374,49 @@ class BasicSwap(BaseApp): self.closeSession(session) def processXmrLockReleaseMessage(self, msg) -> None: - self.log.debug('Processing adaptor-sig swap lock release msg %s', msg['msgid']) - now: int = self.getTime() - msg_bytes = bytes.fromhex(msg['hex'][2:-2]) + self.log.debug("Processing adaptor-sig swap lock release msg %s", msg["msgid"]) + + msg_bytes = bytes.fromhex(msg["hex"][2:-2]) msg_data = XmrBidLockReleaseMessage(init_all=False) msg_data.from_bytes(msg_bytes) # Validate data - ensure(len(msg_data.bid_msg_id) == 28, 'Bad msg_id length') + ensure(len(msg_data.bid_msg_id) == 28, "Bad msg_id length") bid_id = msg_data.bid_msg_id bid, xmr_swap = self.getXmrBid(bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) - if BidStates(bid.state) in (BidStates.BID_STALLED_FOR_TEST, ): - self.log.debug('Bid stalled %s', bid_id.hex()) + if BidStates(bid.state) in (BidStates.BID_STALLED_FOR_TEST,): + self.log.debug("Bid stalled %s", bid_id.hex()) return offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) ci_from = self.ci(offer.coin_to if reverse_bid else offer.coin_from) addr_sent_from: str = bid.bid_addr if reverse_bid else offer.addr_from addr_sent_to: str = offer.addr_from if reverse_bid else bid.bid_addr - ensure(msg['to'] == addr_sent_to, 'Received on incorrect address') - ensure(msg['from'] == addr_sent_from, 'Sent from incorrect address') + ensure(msg["to"] == addr_sent_to, "Received on incorrect address") + ensure(msg["from"] == addr_sent_from, "Sent from incorrect address") xmr_swap.al_lock_spend_tx_esig = msg_data.al_lock_spend_tx_esig try: prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap) v = ci_from.verifyTxOtVES( - xmr_swap.a_lock_spend_tx, xmr_swap.al_lock_spend_tx_esig, - xmr_swap.pkal, xmr_swap.pkasf, 0, xmr_swap.a_lock_tx_script, prevout_amount) - ensure(v, 'verifyTxOtVES failed for chain a lock tx leader esig') + xmr_swap.a_lock_spend_tx, + xmr_swap.al_lock_spend_tx_esig, + xmr_swap.pkal, + xmr_swap.pkasf, + 0, + xmr_swap.a_lock_tx_script, + prevout_amount, + ) + ensure(v, "verifyTxOtVES failed for chain a lock tx leader esig") except Exception as ex: if self.debug: self.log.error(traceback.format_exc()) @@ -6634,10 +9425,19 @@ class BasicSwap(BaseApp): return if self.haveDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK): - self.logBidEvent(bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(DebugTypes.BID_DONT_SPEND_COIN_A_LOCK), None) + self.logBidEvent( + bid_id, + EventLogTypes.DEBUG_TWEAK_APPLIED, + "ind {}".format(DebugTypes.BID_DONT_SPEND_COIN_A_LOCK), + None, + ) else: delay = self.get_delay_event_seconds() - self.log.info('Redeeming coin A lock tx for adaptor-sig bid %s in %d seconds', bid_id.hex(), delay) + self.log.info( + "Redeeming coin A lock tx for adaptor-sig bid %s in %d seconds", + bid_id.hex(), + delay, + ) self.createAction(delay, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_A, bid_id) bid.setState(BidStates.XMR_SWAP_LOCK_RELEASED) @@ -6645,39 +9445,46 @@ class BasicSwap(BaseApp): self.swaps_in_progress[bid_id] = (bid, offer) def processADSBidReversed(self, msg) -> None: - self.log.debug('Processing adaptor-sig reverse bid msg %s', msg['msgid']) + self.log.debug("Processing adaptor-sig reverse bid msg %s", msg["msgid"]) now: int = self.getTime() - bid_bytes = bytes.fromhex(msg['hex'][2:-2]) + bid_bytes = bytes.fromhex(msg["hex"][2:-2]) bid_data = ADSBidIntentMessage(init_all=False) bid_data.from_bytes(bid_bytes) # Validate data - ensure(bid_data.protocol_version >= MINPROTO_VERSION_ADAPTOR_SIG, 'Invalid protocol version') - ensure(len(bid_data.offer_msg_id) == 28, 'Bad offer_id length') + ensure( + bid_data.protocol_version >= MINPROTO_VERSION_ADAPTOR_SIG, + "Invalid protocol version", + ) + ensure(len(bid_data.offer_msg_id) == 28, "Bad offer_id length") offer_id = bid_data.offer_msg_id offer, xmr_offer = self.getXmrOffer(offer_id, sent=True) - ensure(offer and offer.was_sent, 'Offer not found: {}.'.format(offer_id.hex())) - ensure(offer.swap_type == SwapTypes.XMR_SWAP, 'Bid/offer swap type mismatch') - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(offer_id.hex())) + ensure(offer and offer.was_sent, "Offer not found: {}.".format(offer_id.hex())) + ensure(offer.swap_type == SwapTypes.XMR_SWAP, "Bid/offer swap type mismatch") + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(offer_id.hex())) ci_from = self.ci(offer.coin_to) ci_to = self.ci(offer.coin_from) if not validOfferStateToReceiveBid(offer.state): - raise ValueError('Bad offer state') - ensure(msg['to'] == offer.addr_from, 'Received on incorrect address') - ensure(now <= offer.expire_at, 'Offer expired') - self.validateBidValidTime(offer.swap_type, offer.coin_from, offer.coin_to, bid_data.time_valid) - ensure(now <= msg['sent'] + bid_data.time_valid, 'Bid expired') + raise ValueError("Bad offer state") + ensure(msg["to"] == offer.addr_from, "Received on incorrect address") + ensure(now <= offer.expire_at, "Offer expired") + self.validateBidValidTime( + offer.swap_type, offer.coin_from, offer.coin_to, bid_data.time_valid + ) + ensure(now <= msg["sent"] + bid_data.time_valid, "Bid expired") # ci_from/to are reversed bid_rate: int = ci_to.make_int(bid_data.amount_to / bid_data.amount_from, r=1) - reversed_rate: int = ci_from.make_int(bid_data.amount_from / bid_data.amount_to, r=1) + reversed_rate: int = ci_from.make_int( + bid_data.amount_from / bid_data.amount_to, r=1 + ) self.validateBidAmount(offer, bid_data.amount_from, bid_rate) - bid_id = bytes.fromhex(msg['msgid']) + bid_id = bytes.fromhex(msg["msgid"]) bid, xmr_swap = self.getXmrBid(bid_id) if bid is None: @@ -6689,9 +9496,9 @@ class BasicSwap(BaseApp): amount=bid_data.amount_to, amount_to=bid_data.amount_from, rate=reversed_rate, - created_at=msg['sent'], - expire_at=msg['sent'] + bid_data.time_valid, - bid_addr=msg['from'], + created_at=msg["sent"], + expire_at=msg["sent"] + bid_data.time_valid, + bid_addr=msg["from"], was_sent=False, was_received=True, chain_a_height_start=ci_from.getChainHeight(), @@ -6704,59 +9511,89 @@ class BasicSwap(BaseApp): wallet_restore_height = self.getWalletRestoreHeight(ci_to) if bid.chain_b_height_start < wallet_restore_height: bid.chain_b_height_start = wallet_restore_height - self.log.warning('Adaptor-sig swap restore height clamped to {}'.format(wallet_restore_height)) + self.log.warning( + "Adaptor-sig swap restore height clamped to {}".format( + wallet_restore_height + ) + ) else: - ensure(bid.state == BidStates.BID_REQUEST_SENT, 'Wrong bid state: {}'.format(BidStates(bid.state).name)) + ensure( + bid.state == BidStates.BID_REQUEST_SENT, + "Wrong bid state: {}".format(BidStates(bid.state).name), + ) # Don't update bid.created_at, it's been used to derive kaf - bid.expire_at = msg['sent'] + bid_data.time_valid + bid.expire_at = msg["sent"] + bid_data.time_valid bid.was_received = True bid.setState(BidStates.BID_RECEIVED) # BID_REQUEST_RECEIVED - self.log.info('Received reverse adaptor-sig bid %s for offer %s', bid_id.hex(), bid_data.offer_msg_id.hex()) + self.log.info( + "Received reverse adaptor-sig bid %s for offer %s", + bid_id.hex(), + bid_data.offer_msg_id.hex(), + ) self.saveBid(bid_id, bid, xmr_swap=xmr_swap) try: session = self.openSession() - self.notify(NT.BID_RECEIVED, {'type': 'ads_reversed', 'bid_id': bid.bid_id.hex(), 'offer_id': bid.offer_id.hex()}, session) + self.notify( + NT.BID_RECEIVED, + { + "type": "ads_reversed", + "bid_id": bid.bid_id.hex(), + "offer_id": bid.offer_id.hex(), + }, + session, + ) - options = {'reverse_bid': True, 'bid_rate': bid_rate} + options = {"reverse_bid": True, "bid_rate": bid_rate} if self.shouldAutoAcceptBid(offer, bid, session, options=options): delay = self.get_delay_event_seconds() - self.log.info('Auto accepting reverse adaptor-sig bid %s in %d seconds', bid.bid_id.hex(), delay) - self.createActionInSession(delay, ActionTypes.ACCEPT_AS_REV_BID, bid.bid_id, session) + self.log.info( + "Auto accepting reverse adaptor-sig bid %s in %d seconds", + bid.bid_id.hex(), + delay, + ) + self.createActionInSession( + delay, ActionTypes.ACCEPT_AS_REV_BID, bid.bid_id, session + ) bid.setState(BidStates.SWAP_DELAYING) finally: self.closeSession(session) def processADSBidReversedAccept(self, msg) -> None: - self.log.debug('Processing adaptor-sig reverse bid accept msg %s', msg['msgid']) + self.log.debug("Processing adaptor-sig reverse bid accept msg %s", msg["msgid"]) - now: int = self.getTime() - msg_bytes = bytes.fromhex(msg['hex'][2:-2]) + msg_bytes = bytes.fromhex(msg["hex"][2:-2]) msg_data = ADSBidIntentAcceptMessage(init_all=False) msg_data.from_bytes(msg_bytes) bid_id = msg_data.bid_msg_id bid, xmr_swap = self.getXmrBid(bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) - ensure(msg['to'] == bid.bid_addr, 'Received on incorrect address') - ensure(msg['from'] == offer.addr_from, 'Sent from incorrect address') + ensure(msg["to"] == bid.bid_addr, "Received on incorrect address") + ensure(msg["from"] == offer.addr_from, "Sent from incorrect address") ci_from = self.ci(offer.coin_to) ci_to = self.ci(offer.coin_from) - ensure(ci_to.verifyKey(msg_data.kbvf), 'Invalid chain B follower view key') - ensure(ci_from.verifyPubkey(msg_data.pkaf), 'Invalid chain A follower public key') - ensure(ci_from.isValidAddressHash(msg_data.dest_af) or ci_from.isValidPubkey(msg_data.dest_af), 'Invalid destination address') + ensure(ci_to.verifyKey(msg_data.kbvf), "Invalid chain B follower view key") + ensure( + ci_from.verifyPubkey(msg_data.pkaf), "Invalid chain A follower public key" + ) + ensure( + ci_from.isValidAddressHash(msg_data.dest_af) + or ci_from.isValidPubkey(msg_data.dest_af), + "Invalid destination address", + ) if ci_to.curve_type() == Curves.ed25519: - ensure(len(msg_data.kbsf_dleag) == 16000, 'Invalid kbsf_dleag size') + ensure(len(msg_data.kbsf_dleag) == 16000, "Invalid kbsf_dleag size") xmr_swap.dest_af = msg_data.dest_af xmr_swap.pkaf = msg_data.pkaf @@ -6770,16 +9607,24 @@ class BasicSwap(BaseApp): wallet_restore_height: int = self.getWalletRestoreHeight(ci_to) if bid.chain_b_height_start < wallet_restore_height: bid.chain_b_height_start = wallet_restore_height - self.log.warning('Reverse adaptor-sig swap restore height clamped to {}'.format(wallet_restore_height)) + self.log.warning( + "Reverse adaptor-sig swap restore height clamped to {}".format( + wallet_restore_height + ) + ) bid.setState(BidStates.BID_RECEIVING) - self.log.info('Receiving reverse adaptor-sig bid %s for offer %s', bid_id.hex(), bid.offer_id.hex()) + self.log.info( + "Receiving reverse adaptor-sig bid %s for offer %s", + bid_id.hex(), + bid.offer_id.hex(), + ) self.saveBid(bid_id, bid, xmr_swap=xmr_swap) try: session = self.openSession() - self.notify(NT.BID_ACCEPTED, {'bid_id': bid_id.hex()}, session) + self.notify(NT.BID_ACCEPTED, {"bid_id": bid_id.hex()}, session) if ci_to.curve_type() != Curves.ed25519: self.receiveXmrBid(bid, session) finally: @@ -6787,9 +9632,8 @@ class BasicSwap(BaseApp): def processMsg(self, msg) -> None: try: - msg_type = int(msg['hex'][:2], 16) + msg_type = int(msg["hex"][:2], 16) - rv = None if msg_type == MessageTypes.OFFER: self.processOffer(msg) elif msg_type == MessageTypes.OFFER_REVOKE: @@ -6817,33 +9661,40 @@ class BasicSwap(BaseApp): self.processADSBidReversedAccept(msg) except InactiveCoin as ex: - self.log.debug('Ignoring message involving inactive coin {}, type {}'.format(Coins(ex.coinid).name, MessageTypes(msg_type).name)) + self.log.debug( + "Ignoring message involving inactive coin {}, type {}".format( + Coins(ex.coinid).name, MessageTypes(msg_type).name + ) + ) except Exception as ex: - self.log.error('processMsg %s', str(ex)) + self.log.error("processMsg %s", str(ex)) if self.debug: self.log.error(traceback.format_exc()) - self.logEvent(Concepts.NETWORK_MESSAGE, - bytes.fromhex(msg['msgid']), - EventLogTypes.ERROR, - str(ex), - None) + self.logEvent( + Concepts.NETWORK_MESSAGE, + bytes.fromhex(msg["msgid"]), + EventLogTypes.ERROR, + str(ex), + None, + ) def processZmqSmsg(self) -> None: message = self.zmqSubscriber.recv() - clear = self.zmqSubscriber.recv() + # Clear + _ = self.zmqSubscriber.recv() if message[0] == 3: # Paid smsg return # TODO: Switch to paid? msg_id = message[2:] - options = {'encoding': 'hex', 'setread': True} + options = {"encoding": "hex", "setread": True} num_tries = 5 for i in range(num_tries + 1): try: - msg = self.callrpc('smsg', [msg_id.hex(), options]) + msg = self.callrpc("smsg", [msg_id.hex(), options]) break except Exception as e: - if 'Unknown message id' in str(e) and i < num_tries: + if "Unknown message id" in str(e) and i < num_tries: self.delay_event.wait(1) else: raise e @@ -6864,11 +9715,18 @@ class BasicSwap(BaseApp): offers_to_expire.add(offer_id) self._expiring_offers.pop(i) - if now - self._last_checked_expiring_bids_offers >= self.check_expiring_bids_offers_seconds: + if ( + now - self._last_checked_expiring_bids_offers + >= self.check_expiring_bids_offers_seconds + ): check_records = True self._last_checked_expiring_bids = now - if len(bids_to_expire) == 0 and len(offers_to_expire) == 0 and check_records is False: + if ( + len(bids_to_expire) == 0 + and len(offers_to_expire) == 0 + and check_records is False + ): return bids_expired: int = 0 @@ -6877,15 +9735,20 @@ class BasicSwap(BaseApp): session = self.openSession() if check_records: - query = '''SELECT 1, bid_id, expire_at FROM bids WHERE active_ind = 1 AND state IN (:bid_received, :bid_sent) AND expire_at <= :check_time + query = """SELECT 1, bid_id, expire_at FROM bids WHERE active_ind = 1 AND state IN (:bid_received, :bid_sent) AND expire_at <= :check_time UNION ALL SELECT 2, offer_id, expire_at FROM offers WHERE active_ind = 1 AND state IN (:offer_received, :offer_sent) AND expire_at <= :check_time - ''' - q = session.execute(text(query), {'bid_received': int(BidStates.BID_RECEIVED), - 'offer_received': int(OfferStates.OFFER_RECEIVED), - 'bid_sent': int(BidStates.BID_SENT), - 'offer_sent': int(OfferStates.OFFER_SENT), - 'check_time': now + self.check_expiring_bids_offers_seconds}) + """ + q = session.execute( + text(query), + { + "bid_received": int(BidStates.BID_RECEIVED), + "offer_received": int(OfferStates.OFFER_RECEIVED), + "bid_sent": int(BidStates.BID_SENT), + "offer_sent": int(OfferStates.OFFER_SENT), + "check_time": now + self.check_expiring_bids_offers_seconds, + }, + ) for entry in q: record_id = entry[1] expire_at = entry[2] @@ -6901,54 +9764,82 @@ class BasicSwap(BaseApp): offers_to_expire.add(record_id) for bid_id in bids_to_expire: - query = text('SELECT expire_at, states FROM bids WHERE bid_id = :bid_id AND active_ind = 1 AND state IN (:bid_received, :bid_sent)') - rows = session.execute(query, {'bid_id': bid_id, - 'bid_received': int(BidStates.BID_RECEIVED), - 'bid_sent': int(BidStates.BID_SENT)}).fetchall() + query = text( + "SELECT expire_at, states FROM bids WHERE bid_id = :bid_id AND active_ind = 1 AND state IN (:bid_received, :bid_sent)" + ) + rows = session.execute( + query, + { + "bid_id": bid_id, + "bid_received": int(BidStates.BID_RECEIVED), + "bid_sent": int(BidStates.BID_SENT), + }, + ).fetchall() if len(rows) > 0: new_state: int = int(BidStates.BID_EXPIRED) - states = (bytes() if rows[0][1] is None else rows[0][1]) + pack_state(new_state, now) - query = 'UPDATE bids SET state = :new_state, states = :states WHERE bid_id = :bid_id' - session.execute(text(query), {'bid_id': bid_id, 'new_state': new_state, 'states': states}) + states = ( + bytes() if rows[0][1] is None else rows[0][1] + ) + pack_state(new_state, now) + query = "UPDATE bids SET state = :new_state, states = :states WHERE bid_id = :bid_id" + session.execute( + text(query), + {"bid_id": bid_id, "new_state": new_state, "states": states}, + ) bids_expired += 1 for offer_id in offers_to_expire: - query = 'SELECT expire_at, states FROM offers WHERE offer_id = :offer_id AND active_ind = 1 AND state IN (:offer_received, :offer_sent)' - rows = session.execute(text(query), {'offer_id': offer_id, - 'offer_received': int(OfferStates.OFFER_RECEIVED), - 'offer_sent': int(OfferStates.OFFER_SENT)}).fetchall() + query = "SELECT expire_at, states FROM offers WHERE offer_id = :offer_id AND active_ind = 1 AND state IN (:offer_received, :offer_sent)" + rows = session.execute( + text(query), + { + "offer_id": offer_id, + "offer_received": int(OfferStates.OFFER_RECEIVED), + "offer_sent": int(OfferStates.OFFER_SENT), + }, + ).fetchall() if len(rows) > 0: new_state: int = int(OfferStates.OFFER_EXPIRED) - states = (bytes() if rows[0][1] is None else rows[0][1]) + pack_state(new_state, now) - query = 'UPDATE offers SET state = :new_state, states = :states WHERE offer_id = :offer_id' - session.execute(text(query), {'offer_id': offer_id, 'new_state': new_state, 'states': states}) + states = ( + bytes() if rows[0][1] is None else rows[0][1] + ) + pack_state(new_state, now) + query = "UPDATE offers SET state = :new_state, states = :states WHERE offer_id = :offer_id" + session.execute( + text(query), + { + "offer_id": offer_id, + "new_state": new_state, + "states": states, + }, + ) offers_expired += 1 finally: self.closeSession(session) if bids_expired + offers_expired > 0: - mb = '' if bids_expired == 1 else 's' - mo = '' if offers_expired == 1 else 's' - self.log.debug(f'Expired {bids_expired} bid{mb} and {offers_expired} offer{mo}') + mb = "" if bids_expired == 1 else "s" + mo = "" if offers_expired == 1 else "s" + self.log.debug( + f"Expired {bids_expired} bid{mb} and {offers_expired} offer{mo}" + ) def update(self) -> None: if self._zmq_queue_enabled: try: if self._read_zmq_queue: message = self.zmqSubscriber.recv(flags=zmq.NOBLOCK) - if message == b'smsg': + if message == b"smsg": self.processZmqSmsg() - except zmq.Again as ex: + except zmq.Again as e: # noqa: F841 pass - except Exception as ex: - self.logException(f'smsg zmq {ex}') + except Exception as e: + self.logException(f"smsg zmq {e}") if self._poll_smsg: now: int = self.getTime() if now - self._last_checked_smsg >= self.check_smsg_seconds: self._last_checked_smsg = now - options = {'encoding': 'hex', 'setread': True} - msgs = self.callrpc('smsginbox', ['unread', '', options]) - for msg in msgs['messages']: + options = {"encoding": "hex", "setread": True} + msgs = self.callrpc("smsginbox", ["unread", "", options]) + for msg in msgs["messages"]: self.processMsg(msg) try: @@ -6964,12 +9855,19 @@ class BasicSwap(BaseApp): to_remove.append((bid_id, v[0], v[1])) except Exception as ex: if self.debug: - self.log.error('checkBidState %s', traceback.format_exc()) + self.log.error("checkBidState %s", traceback.format_exc()) if self.is_transient_error(ex): - self.log.warning('checkBidState %s %s', bid_id.hex(), str(ex)) - self.logBidEvent(bid_id, EventLogTypes.SYSTEM_WARNING, 'No connection to daemon', session=None) + self.log.warning( + "checkBidState %s %s", bid_id.hex(), str(ex) + ) + self.logBidEvent( + bid_id, + EventLogTypes.SYSTEM_WARNING, + "No connection to daemon", + session=None, + ) else: - self.log.error('checkBidState %s %s', bid_id.hex(), str(ex)) + self.log.error("checkBidState %s %s", bid_id.hex(), str(ex)) self.setBidError(bid_id, v[0], str(ex)) for bid_id, bid, offer in to_remove: @@ -6978,9 +9876,13 @@ class BasicSwap(BaseApp): if now - self._last_checked_watched >= self.check_watched_seconds: for k, c in self.coin_clients.items(): - if k == Coins.PART_ANON or k == Coins.PART_BLIND or k == Coins.LTC_MWEB: + if ( + k == Coins.PART_ANON + or k == Coins.PART_BLIND + or k == Coins.LTC_MWEB + ): continue - if len(c['watched_outputs']) > 0 or len(c['watched_scripts']): + if len(c["watched_outputs"]) > 0 or len(c["watched_scripts"]): self.checkForSpends(k, c) self._last_checked_watched = now @@ -6999,40 +9901,46 @@ class BasicSwap(BaseApp): self._last_checked_xmr_swaps = now except Exception as ex: - self.logException(f'update {ex}') + self.logException(f"update {ex}") def manualBidUpdate(self, bid_id: bytes, data) -> None: - self.log.info('Manually updating bid %s', bid_id.hex()) + self.log.info("Manually updating bid %s", bid_id.hex()) add_bid_action = -1 try: session = self.openSession() bid, offer = self.getBidAndOffer(bid_id, session) - ensure(bid, 'Bid not found {}'.format(bid_id.hex())) - ensure(offer, 'Offer not found {}'.format(bid.offer_id.hex())) + ensure(bid, "Bid not found {}".format(bid_id.hex())) + ensure(offer, "Offer not found {}".format(bid.offer_id.hex())) has_changed = False - if bid.state != data['bid_state']: - bid.setState(data['bid_state']) - self.log.warning('Set state to %s', strBidState(bid.state)) + if bid.state != data["bid_state"]: + bid.setState(data["bid_state"]) + self.log.warning("Set state to %s", strBidState(bid.state)) has_changed = True - if data.get('bid_action', -1) != -1: - self.log.warning('Adding action', ActionTypes(data['bid_action']).name) - add_bid_action = ActionTypes(data['bid_action']) + if data.get("bid_action", -1) != -1: + self.log.warning("Adding action", ActionTypes(data["bid_action"]).name) + add_bid_action = ActionTypes(data["bid_action"]) has_changed = True - if 'debug_ind' in data: - if bid.debug_ind != data['debug_ind']: - if bid.debug_ind is None and data['debug_ind'] == -1: + if "debug_ind" in data: + if bid.debug_ind != data["debug_ind"]: + if bid.debug_ind is None and data["debug_ind"] == -1: pass # Already unset else: - self.log.debug('Bid %s Setting debug flag: %s', bid_id.hex(), data['debug_ind']) - bid.debug_ind = data['debug_ind'] + self.log.debug( + "Bid %s Setting debug flag: %s", + bid_id.hex(), + data["debug_ind"], + ) + bid.debug_ind = data["debug_ind"] has_changed = True - if data.get('kbs_other', None) is not None: - return xmr_swap_1.recoverNoScriptTxnWithKey(self, bid_id, data['kbs_other'], session) + if data.get("kbs_other", None) is not None: + return xmr_swap_1.recoverNoScriptTxnWithKey( + self, bid_id, data["kbs_other"], session + ) if has_changed: activate_bid = False @@ -7051,131 +9959,147 @@ class BasicSwap(BaseApp): self.saveBidInSession(bid_id, bid, session) session.commit() else: - raise ValueError('No changes') + raise ValueError("No changes") finally: self.closeSession(session, commit=False) def editGeneralSettings(self, data): - self.log.info('Updating general settings') + self.log.info("Updating general settings") settings_changed = False suggest_reboot = False settings_copy = copy.deepcopy(self.settings) with self.mxDB: - if 'debug' in data: - new_value = data['debug'] - ensure(isinstance(new_value, bool), 'New debug value not boolean') - if settings_copy.get('debug', False) != new_value: + if "debug" in data: + new_value = data["debug"] + ensure(isinstance(new_value, bool), "New debug value not boolean") + if settings_copy.get("debug", False) != new_value: self.debug = new_value - settings_copy['debug'] = new_value + settings_copy["debug"] = new_value settings_changed = True - if 'debug_ui' in data: - new_value = data['debug_ui'] - ensure(isinstance(new_value, bool), 'New debug_ui value not boolean') - if settings_copy.get('debug_ui', False) != new_value: + if "debug_ui" in data: + new_value = data["debug_ui"] + ensure(isinstance(new_value, bool), "New debug_ui value not boolean") + if settings_copy.get("debug_ui", False) != new_value: self.debug_ui = new_value - settings_copy['debug_ui'] = new_value + settings_copy["debug_ui"] = new_value settings_changed = True - if 'expire_db_records' in data: - new_value = data['expire_db_records'] - ensure(isinstance(new_value, bool), 'New expire_db_records value not boolean') - if settings_copy.get('expire_db_records', False) != new_value: + if "expire_db_records" in data: + new_value = data["expire_db_records"] + ensure( + isinstance(new_value, bool), + "New expire_db_records value not boolean", + ) + if settings_copy.get("expire_db_records", False) != new_value: self._expire_db_records = new_value - settings_copy['expire_db_records'] = new_value + settings_copy["expire_db_records"] = new_value settings_changed = True - if 'show_chart' in data: - new_value = data['show_chart'] - ensure(isinstance(new_value, bool), 'New show_chart value not boolean') - if settings_copy.get('show_chart', True) != new_value: - settings_copy['show_chart'] = new_value + if "show_chart" in data: + new_value = data["show_chart"] + ensure(isinstance(new_value, bool), "New show_chart value not boolean") + if settings_copy.get("show_chart", True) != new_value: + settings_copy["show_chart"] = new_value settings_changed = True - if 'chart_api_key' in data: - new_value = data['chart_api_key'] - ensure(isinstance(new_value, str), 'New chart_api_key value not a string') - ensure(len(new_value) <= 128, 'New chart_api_key value too long') + if "chart_api_key" in data: + new_value = data["chart_api_key"] + ensure( + isinstance(new_value, str), "New chart_api_key value not a string" + ) + ensure(len(new_value) <= 128, "New chart_api_key value too long") if all(c in string.hexdigits for c in new_value): - if settings_copy.get('chart_api_key', '') != new_value: - settings_copy['chart_api_key'] = new_value - if 'chart_api_key_enc' in settings_copy: - settings_copy.pop('chart_api_key_enc') + if settings_copy.get("chart_api_key", "") != new_value: + settings_copy["chart_api_key"] = new_value + if "chart_api_key_enc" in settings_copy: + settings_copy.pop("chart_api_key_enc") settings_changed = True else: # Encode value as hex to avoid escaping - new_value = new_value.encode('utf-8').hex() - if settings_copy.get('chart_api_key_enc', '') != new_value: - settings_copy['chart_api_key_enc'] = new_value - if 'chart_api_key' in settings_copy: - settings_copy.pop('chart_api_key') + new_value = new_value.encode("utf-8").hex() + if settings_copy.get("chart_api_key_enc", "") != new_value: + settings_copy["chart_api_key_enc"] = new_value + if "chart_api_key" in settings_copy: + settings_copy.pop("chart_api_key") settings_changed = True - if 'coingecko_api_key' in data: - new_value = data['coingecko_api_key'] - ensure(isinstance(new_value, str), 'New coingecko_api_key value not a string') - ensure(len(new_value) <= 128, 'New coingecko_api_keyvalue too long') + if "coingecko_api_key" in data: + new_value = data["coingecko_api_key"] + ensure( + isinstance(new_value, str), + "New coingecko_api_key value not a string", + ) + ensure(len(new_value) <= 128, "New coingecko_api_keyvalue too long") if all(c in string.hexdigits for c in new_value): - if settings_copy.get('coingecko_api_key', '') != new_value: - settings_copy['coingecko_api_key'] = new_value - if 'coingecko_api_key_enc' in settings_copy: - settings_copy.pop('coingecko_api_key_enc') + if settings_copy.get("coingecko_api_key", "") != new_value: + settings_copy["coingecko_api_key"] = new_value + if "coingecko_api_key_enc" in settings_copy: + settings_copy.pop("coingecko_api_key_enc") settings_changed = True else: # Encode value as hex to avoid escaping - new_value = new_value.encode('utf-8').hex() - if settings_copy.get('coingecko_api_key_enc', '') != new_value: - settings_copy['coingecko_api_key_enc'] = new_value - if 'coingecko_api_key' in settings_copy: - settings_copy.pop('coingecko_api_key') + new_value = new_value.encode("utf-8").hex() + if settings_copy.get("coingecko_api_key_enc", "") != new_value: + settings_copy["coingecko_api_key_enc"] = new_value + if "coingecko_api_key" in settings_copy: + settings_copy.pop("coingecko_api_key") settings_changed = True - if 'enabled_chart_coins' in data: - new_value = data['enabled_chart_coins'].strip() - ensure(isinstance(new_value, str), 'New enabled_chart_coins value not a string') - if new_value.lower() == 'all' or new_value == '': + if "enabled_chart_coins" in data: + new_value = data["enabled_chart_coins"].strip() + ensure( + isinstance(new_value, str), + "New enabled_chart_coins value not a string", + ) + if new_value.lower() == "all" or new_value == "": pass else: - tickers = new_value.split(',') + tickers = new_value.split(",") seen_tickers = [] for ticker in tickers: upcased_ticker = ticker.strip().upper() if upcased_ticker not in known_chart_coins: - raise ValueError(f'Unknown coin: {ticker}') + raise ValueError(f"Unknown coin: {ticker}") if upcased_ticker in seen_tickers: - raise ValueError(f'Duplicate coin: {ticker}') + raise ValueError(f"Duplicate coin: {ticker}") seen_tickers.append(upcased_ticker) - if settings_copy.get('enabled_chart_coins', '') != new_value: - settings_copy['enabled_chart_coins'] = new_value + if settings_copy.get("enabled_chart_coins", "") != new_value: + settings_copy["enabled_chart_coins"] = new_value settings_changed = True if settings_changed: settings_path = os.path.join(self.data_dir, cfg.CONFIG_FILENAME) - settings_path_new = settings_path + '.new' - shutil.copyfile(settings_path, settings_path + '.last') - with open(settings_path_new, 'w') as fp: + settings_path_new = settings_path + ".new" + shutil.copyfile(settings_path, settings_path + ".last") + with open(settings_path_new, "w") as fp: json.dump(settings_copy, fp, indent=4) shutil.move(settings_path_new, settings_path) self.settings = settings_copy return settings_changed, suggest_reboot def editSettings(self, coin_name: str, data): - self.log.info(f'Updating settings {coin_name}') + self.log.info(f"Updating settings {coin_name}") settings_changed = False suggest_reboot = False settings_copy = copy.deepcopy(self.settings) with self.mxDB: - settings_cc = settings_copy['chainclients'][coin_name] - if 'lookups' in data: - if settings_cc.get('chain_lookups', 'local') != data['lookups']: + settings_cc = settings_copy["chainclients"][coin_name] + if "lookups" in data: + if settings_cc.get("chain_lookups", "local") != data["lookups"]: settings_changed = True - settings_cc['chain_lookups'] = data['lookups'] + settings_cc["chain_lookups"] = data["lookups"] for coin, cc in self.coin_clients.items(): - if cc['name'] == coin_name: - cc['chain_lookups'] = data['lookups'] + if cc["name"] == coin_name: + cc["chain_lookups"] = data["lookups"] break - for setting in ('manage_daemon', 'rpchost', 'rpcport', 'automatically_select_daemon'): + for setting in ( + "manage_daemon", + "rpchost", + "rpcport", + "automatically_select_daemon", + ): if setting not in data: continue if settings_cc.get(setting) != data[setting]: @@ -7183,130 +10107,144 @@ class BasicSwap(BaseApp): suggest_reboot = True settings_cc[setting] = data[setting] - if 'remotedaemonurls' in data: - remotedaemonurls_in = data['remotedaemonurls'].split('\n') + if "remotedaemonurls" in data: + remotedaemonurls_in = data["remotedaemonurls"].split("\n") remotedaemonurls = set() for url in remotedaemonurls_in: - if url.count(':') > 0: + if url.count(":") > 0: remotedaemonurls.add(url.strip()) - if set(settings_cc.get('remote_daemon_urls', [])) != remotedaemonurls: - settings_cc['remote_daemon_urls'] = list(remotedaemonurls) + if set(settings_cc.get("remote_daemon_urls", [])) != remotedaemonurls: + settings_cc["remote_daemon_urls"] = list(remotedaemonurls) settings_changed = True suggest_reboot = True # Ensure remote_daemon_urls appears in settings if automatically_select_daemon is present - if 'automatically_select_daemon' in settings_cc and 'remote_daemon_urls' not in settings_cc: - settings_cc['remote_daemon_urls'] = [] + if ( + "automatically_select_daemon" in settings_cc + and "remote_daemon_urls" not in settings_cc + ): + settings_cc["remote_daemon_urls"] = [] settings_changed = True - if 'fee_priority' in data: - new_fee_priority = data['fee_priority'] - ensure(new_fee_priority >= 0 and new_fee_priority < 4, 'Invalid priority') + if "fee_priority" in data: + new_fee_priority = data["fee_priority"] + ensure( + new_fee_priority >= 0 and new_fee_priority < 4, "Invalid priority" + ) - if settings_cc.get('fee_priority', 0) != new_fee_priority: + if settings_cc.get("fee_priority", 0) != new_fee_priority: settings_changed = True - settings_cc['fee_priority'] = new_fee_priority + settings_cc["fee_priority"] = new_fee_priority for coin, cc in self.coin_clients.items(): - if cc['name'] == coin_name: - cc['fee_priority'] = new_fee_priority + if cc["name"] == coin_name: + cc["fee_priority"] = new_fee_priority if self.isCoinActive(coin): self.ci(coin).setFeePriority(new_fee_priority) break - if 'conf_target' in data: - new_conf_target = data['conf_target'] - ensure(new_conf_target >= 1 and new_conf_target < 33, 'Invalid conf_target') + if "conf_target" in data: + new_conf_target = data["conf_target"] + ensure( + new_conf_target >= 1 and new_conf_target < 33, "Invalid conf_target" + ) - if settings_cc.get('conf_target', 2) != new_conf_target: + if settings_cc.get("conf_target", 2) != new_conf_target: settings_changed = True - settings_cc['conf_target'] = new_conf_target + settings_cc["conf_target"] = new_conf_target for coin, cc in self.coin_clients.items(): - if cc['name'] == coin_name: - cc['conf_target'] = new_conf_target + if cc["name"] == coin_name: + cc["conf_target"] = new_conf_target if self.isCoinActive(coin): self.ci(coin).setConfTarget(new_conf_target) break - if 'anon_tx_ring_size' in data: - new_anon_tx_ring_size = data['anon_tx_ring_size'] - ensure(new_anon_tx_ring_size >= 3 and new_anon_tx_ring_size < 33, 'Invalid anon_tx_ring_size') + if "anon_tx_ring_size" in data: + new_anon_tx_ring_size = data["anon_tx_ring_size"] + ensure( + new_anon_tx_ring_size >= 3 and new_anon_tx_ring_size < 33, + "Invalid anon_tx_ring_size", + ) - if settings_cc.get('anon_tx_ring_size', 12) != new_anon_tx_ring_size: + if settings_cc.get("anon_tx_ring_size", 12) != new_anon_tx_ring_size: settings_changed = True - settings_cc['anon_tx_ring_size'] = new_anon_tx_ring_size + settings_cc["anon_tx_ring_size"] = new_anon_tx_ring_size for coin, cc in self.coin_clients.items(): - if cc['name'] == coin_name: - cc['anon_tx_ring_size'] = new_anon_tx_ring_size + if cc["name"] == coin_name: + cc["anon_tx_ring_size"] = new_anon_tx_ring_size if self.isCoinActive(coin): self.ci(coin).setAnonTxRingSize(new_anon_tx_ring_size) break - if 'wallet_pwd' in data: - new_wallet_pwd = data['wallet_pwd'] - if settings_cc.get('wallet_pwd', '') != new_wallet_pwd: + if "wallet_pwd" in data: + new_wallet_pwd = data["wallet_pwd"] + if settings_cc.get("wallet_pwd", "") != new_wallet_pwd: settings_changed = True - settings_cc['wallet_pwd'] = new_wallet_pwd + settings_cc["wallet_pwd"] = new_wallet_pwd if settings_changed: settings_path = os.path.join(self.data_dir, cfg.CONFIG_FILENAME) - settings_path_new = settings_path + '.new' - shutil.copyfile(settings_path, settings_path + '.last') - with open(settings_path_new, 'w') as fp: + settings_path_new = settings_path + ".new" + shutil.copyfile(settings_path, settings_path + ".last") + with open(settings_path_new, "w") as fp: json.dump(settings_copy, fp, indent=4) shutil.move(settings_path_new, settings_path) self.settings = settings_copy return settings_changed, suggest_reboot def enableCoin(self, coin_name: str) -> None: - self.log.info('Enabling coin %s', coin_name) + self.log.info("Enabling coin %s", coin_name) coin_id = self.getCoinIdFromName(coin_name) if coin_id in (Coins.PART, Coins.PART_BLIND, Coins.PART_ANON): - raise ValueError('Invalid coin') + raise ValueError("Invalid coin") - settings_cc = self.settings['chainclients'][coin_name] - if 'connection_type_prev' not in settings_cc: - raise ValueError('Can\'t find previous value.') - settings_cc['connection_type'] = settings_cc['connection_type_prev'] - del settings_cc['connection_type_prev'] - if 'manage_daemon_prev' in settings_cc: - settings_cc['manage_daemon'] = settings_cc['manage_daemon_prev'] - del settings_cc['manage_daemon_prev'] - if 'manage_wallet_daemon_prev' in settings_cc: - settings_cc['manage_wallet_daemon'] = settings_cc['manage_wallet_daemon_prev'] - del settings_cc['manage_wallet_daemon_prev'] + settings_cc = self.settings["chainclients"][coin_name] + if "connection_type_prev" not in settings_cc: + raise ValueError("Can't find previous value.") + settings_cc["connection_type"] = settings_cc["connection_type_prev"] + del settings_cc["connection_type_prev"] + if "manage_daemon_prev" in settings_cc: + settings_cc["manage_daemon"] = settings_cc["manage_daemon_prev"] + del settings_cc["manage_daemon_prev"] + if "manage_wallet_daemon_prev" in settings_cc: + settings_cc["manage_wallet_daemon"] = settings_cc[ + "manage_wallet_daemon_prev" + ] + del settings_cc["manage_wallet_daemon_prev"] settings_path = os.path.join(self.data_dir, cfg.CONFIG_FILENAME) - shutil.copyfile(settings_path, settings_path + '.last') - with open(settings_path, 'w') as fp: + shutil.copyfile(settings_path, settings_path + ".last") + with open(settings_path, "w") as fp: json.dump(self.settings, fp, indent=4) # Client must be restarted def disableCoin(self, coin_name: str) -> None: - self.log.info('Disabling coin %s', coin_name) + self.log.info("Disabling coin %s", coin_name) coin_id = self.getCoinIdFromName(coin_name) if coin_id in (Coins.PART, Coins.PART_BLIND, Coins.PART_ANON): - raise ValueError('Invalid coin') + raise ValueError("Invalid coin") - settings_cc = self.settings['chainclients'][coin_name] + settings_cc = self.settings["chainclients"][coin_name] - if settings_cc['connection_type'] != 'rpc': - raise ValueError('Already disabled.') + if settings_cc["connection_type"] != "rpc": + raise ValueError("Already disabled.") - settings_cc['manage_daemon_prev'] = settings_cc['manage_daemon'] - settings_cc['manage_daemon'] = False - settings_cc['connection_type_prev'] = settings_cc['connection_type'] - settings_cc['connection_type'] = 'none' + settings_cc["manage_daemon_prev"] = settings_cc["manage_daemon"] + settings_cc["manage_daemon"] = False + settings_cc["connection_type_prev"] = settings_cc["connection_type"] + settings_cc["connection_type"] = "none" - if 'manage_wallet_daemon' in settings_cc: - settings_cc['manage_wallet_daemon_prev'] = settings_cc['manage_wallet_daemon'] - settings_cc['manage_wallet_daemon'] = False + if "manage_wallet_daemon" in settings_cc: + settings_cc["manage_wallet_daemon_prev"] = settings_cc[ + "manage_wallet_daemon" + ] + settings_cc["manage_wallet_daemon"] = False settings_path = os.path.join(self.data_dir, cfg.CONFIG_FILENAME) - shutil.copyfile(settings_path, settings_path + '.last') - with open(settings_path, 'w') as fp: + shutil.copyfile(settings_path, settings_path + ".last") + with open(settings_path, "w") as fp: json.dump(self.settings, fp, indent=4) # Client must be restarted @@ -7315,10 +10253,10 @@ class BasicSwap(BaseApp): for c, v in self.coin_clients.items(): if c in (Coins.PART_ANON, Coins.PART_BLIND): continue - num_watched_outputs += len(v['watched_outputs']) + num_watched_outputs += len(v["watched_outputs"]) now: int = self.getTime() - q_bids_str = '''SELECT + q_bids_str = """SELECT COUNT(CASE WHEN b.was_sent THEN 1 ELSE NULL END) AS count_sent, COUNT(CASE WHEN b.was_sent AND (s.in_progress OR (s.swap_ended = 0 AND b.expire_at > {} AND o.expire_at > {})) THEN 1 ELSE NULL END) AS count_sent_active, COUNT(CASE WHEN b.was_received THEN 1 ELSE NULL END) AS count_received, @@ -7327,13 +10265,17 @@ class BasicSwap(BaseApp): FROM bids b JOIN offers o ON b.offer_id = o.offer_id JOIN bidstates s ON b.state = s.state_id - WHERE b.active_ind = 1'''.format(now, now, BidStates.BID_RECEIVED, now, now, now, now) + WHERE b.active_ind = 1""".format( + now, now, BidStates.BID_RECEIVED, now, now, now, now + ) - q_offers_str = '''SELECT + q_offers_str = """SELECT COUNT(CASE WHEN expire_at > {} THEN 1 ELSE NULL END) AS count_active, COUNT(CASE WHEN was_sent THEN 1 ELSE NULL END) AS count_sent, COUNT(CASE WHEN was_sent AND expire_at > {} THEN 1 ELSE NULL END) AS count_sent_active - FROM offers WHERE active_ind = 1'''.format(now, now) + FROM offers WHERE active_ind = 1""".format( + now, now + ) with self.engine.connect() as conn: q = conn.execute(text(q_bids_str)).first() @@ -7349,17 +10291,17 @@ class BasicSwap(BaseApp): num_sent_active_offers = q[2] rv = { - 'network': self.chain, - 'num_swapping': len(self.swaps_in_progress), - 'num_network_offers': num_offers, - 'num_sent_offers': num_sent_offers, - 'num_sent_active_offers': num_sent_active_offers, - 'num_recv_bids': bids_received, - 'num_sent_bids': bids_sent, - 'num_sent_active_bids': bids_sent_active, - 'num_recv_active_bids': bids_recv_active, - 'num_available_bids': bids_available, - 'num_watched_outputs': num_watched_outputs, + "network": self.chain, + "num_swapping": len(self.swaps_in_progress), + "num_network_offers": num_offers, + "num_sent_offers": num_sent_offers, + "num_sent_active_offers": num_sent_active_offers, + "num_recv_bids": bids_received, + "num_sent_bids": bids_sent, + "num_sent_active_bids": bids_sent_active, + "num_recv_active_bids": bids_recv_active, + "num_available_bids": bids_available, + "num_watched_outputs": num_watched_outputs, } return rv @@ -7370,20 +10312,22 @@ class BasicSwap(BaseApp): blockchaininfo = ci.getBlockchainInfo() rv = { - 'version': self.coin_clients[coin]['core_version'], - 'name': ci.coin_name(), - 'blocks': blockchaininfo['blocks'], - 'synced': '{:.2f}'.format(round(100 * blockchaininfo['verificationprogress'], 2)), + "version": self.coin_clients[coin]["core_version"], + "name": ci.coin_name(), + "blocks": blockchaininfo["blocks"], + "synced": "{:.2f}".format( + round(100 * blockchaininfo["verificationprogress"], 2) + ), } - if 'known_block_count' in blockchaininfo: - rv['known_block_count'] = blockchaininfo['known_block_count'] - if 'bootstrapping' in blockchaininfo: - rv['bootstrapping'] = blockchaininfo['bootstrapping'] + if "known_block_count" in blockchaininfo: + rv["known_block_count"] = blockchaininfo["known_block_count"] + if "bootstrapping" in blockchaininfo: + rv["bootstrapping"] = blockchaininfo["bootstrapping"] return rv except Exception as e: - self.log.warning('getWalletInfo failed with: %s', str(e)) + self.log.warning("getWalletInfo failed with: %s", str(e)) def getWalletInfo(self, coin): ci = self.ci(coin) @@ -7391,50 +10335,67 @@ class BasicSwap(BaseApp): try: walletinfo = ci.getWalletInfo() rv = { - 'deposit_address': self.getCachedAddressForCoin(coin), - 'balance': ci.format_amount(walletinfo['balance'], conv_int=True), - 'unconfirmed': ci.format_amount(walletinfo['unconfirmed_balance'], conv_int=True), - 'expected_seed': ci.knownWalletSeed(), - 'encrypted': walletinfo['encrypted'], - 'locked': walletinfo['locked'], + "deposit_address": self.getCachedAddressForCoin(coin), + "balance": ci.format_amount(walletinfo["balance"], conv_int=True), + "unconfirmed": ci.format_amount( + walletinfo["unconfirmed_balance"], conv_int=True + ), + "expected_seed": ci.knownWalletSeed(), + "encrypted": walletinfo["encrypted"], + "locked": walletinfo["locked"], } - if 'immature_balance' in walletinfo: - rv['immature'] = ci.format_amount(walletinfo['immature_balance'], conv_int=True) + if "immature_balance" in walletinfo: + rv["immature"] = ci.format_amount( + walletinfo["immature_balance"], conv_int=True + ) - if 'locked_utxos' in walletinfo: - rv['locked_utxos'] = walletinfo['locked_utxos'] + if "locked_utxos" in walletinfo: + rv["locked_utxos"] = walletinfo["locked_utxos"] if coin == Coins.PART: - rv['stealth_address'] = self.getCachedStealthAddressForCoin(Coins.PART) - rv['anon_balance'] = walletinfo['anon_balance'] - rv['anon_pending'] = walletinfo['unconfirmed_anon'] + walletinfo['immature_anon_balance'] - rv['blind_balance'] = walletinfo['blind_balance'] - rv['blind_unconfirmed'] = walletinfo['unconfirmed_blind'] + rv["stealth_address"] = self.getCachedStealthAddressForCoin(Coins.PART) + rv["anon_balance"] = walletinfo["anon_balance"] + rv["anon_pending"] = ( + walletinfo["unconfirmed_anon"] + walletinfo["immature_anon_balance"] + ) + rv["blind_balance"] = walletinfo["blind_balance"] + rv["blind_unconfirmed"] = walletinfo["unconfirmed_blind"] elif coin in (Coins.XMR, Coins.WOW): - rv['main_address'] = self.getCachedMainWalletAddress(ci) + rv["main_address"] = self.getCachedMainWalletAddress(ci) elif coin == Coins.NAV: - rv['immature'] = walletinfo['immature_balance'] + rv["immature"] = walletinfo["immature_balance"] elif coin == Coins.LTC: - rv['mweb_address'] = self.getCachedStealthAddressForCoin(Coins.LTC_MWEB) - rv['mweb_balance'] = walletinfo['mweb_balance'] - rv['mweb_pending'] = walletinfo['mweb_unconfirmed'] + walletinfo['mweb_immature'] + rv["mweb_address"] = self.getCachedStealthAddressForCoin(Coins.LTC_MWEB) + rv["mweb_balance"] = walletinfo["mweb_balance"] + rv["mweb_pending"] = ( + walletinfo["mweb_unconfirmed"] + walletinfo["mweb_immature"] + ) return rv except Exception as e: - self.log.warning('getWalletInfo for %s failed with: %s', ci.coin_name(), str(e)) + self.log.warning( + "getWalletInfo for %s failed with: %s", ci.coin_name(), str(e) + ) def addWalletInfoRecord(self, coin, info_type, wi) -> None: coin_id = int(coin) session = self.openSession() try: now: int = self.getTime() - session.add(Wallets(coin_id=coin, balance_type=info_type, wallet_data=json.dumps(wi), created_at=now)) - query_str = f'DELETE FROM wallets WHERE (coin_id = {coin_id} AND balance_type = {info_type}) AND record_id NOT IN (SELECT record_id FROM wallets WHERE coin_id = {coin_id} AND balance_type = {info_type} ORDER BY created_at DESC LIMIT 3 )' + session.add( + Wallets( + coin_id=coin, + balance_type=info_type, + wallet_data=json.dumps(wi), + created_at=now, + ) + ) + query_str = f"DELETE FROM wallets WHERE (coin_id = {coin_id} AND balance_type = {info_type}) AND record_id NOT IN (SELECT record_id FROM wallets WHERE coin_id = {coin_id} AND balance_type = {info_type} ORDER BY created_at DESC LIMIT 3 )" session.execute(text(query_str)) session.commit() except Exception as e: - self.log.error(f'addWalletInfoRecord {e}') + self.log.error(f"addWalletInfoRecord {e}") finally: self.closeSession(session, commit=False) @@ -7450,11 +10411,16 @@ class BasicSwap(BaseApp): if wi: self.addWalletInfoRecord(coin, 1, wi) except Exception as e: - self.log.error(f'updateWalletInfo {e}') + self.log.error(f"updateWalletInfo {e}") finally: self._updating_wallets_info[int(coin)] = False - def updateWalletsInfo(self, force_update: bool = False, only_coin: bool = None, wait_for_complete: bool = False) -> None: + def updateWalletsInfo( + self, + force_update: bool = False, + only_coin: bool = None, + wait_for_complete: bool = False, + ) -> None: now: int = self.getTime() if not force_update and now - self._last_updated_wallets_info < 30: return @@ -7464,63 +10430,76 @@ class BasicSwap(BaseApp): if c not in chainparams: continue cc = self.coin_clients[c] - if cc['connection_type'] == 'rpc': - if not force_update and now - cc.get('last_updated_wallet_info', 0) < 30: + if cc["connection_type"] == "rpc": + if ( + not force_update + and now - cc.get("last_updated_wallet_info", 0) < 30 + ): return - cc['last_updated_wallet_info'] = self.getTime() + cc["last_updated_wallet_info"] = self.getTime() self._updating_wallets_info[int(c)] = True handle = self.thread_pool.submit(self.updateWalletInfo, c) if wait_for_complete: try: handle.result(timeout=self._wallet_update_timeout) except Exception as e: - self.log.error(f'updateWalletInfo {e}') + self.log.error(f"updateWalletInfo {e}") def getWalletsInfo(self, opts=None): rv = {} for c in self.activeCoins(): - key = chainparams[c]['ticker'] if opts.get('ticker_key', False) else c + key = chainparams[c]["ticker"] if opts.get("ticker_key", False) else c try: rv[key] = self.getWalletInfo(c) rv[key].update(self.getBlockchainInfo(c)) except Exception as ex: - rv[key] = {'name': getCoinName(c), 'error': str(ex)} + rv[key] = {"name": getCoinName(c), "error": str(ex)} return rv def getCachedWalletsInfo(self, opts=None): rv = {} try: session = self.openSession() - where_str = '' - if opts is not None and 'coin_id' in opts: - where_str = 'WHERE coin_id = {}'.format(opts['coin_id']) - inner_str = f'SELECT coin_id, balance_type, MAX(created_at) as max_created_at FROM wallets {where_str} GROUP BY coin_id, balance_type' - query_str = 'SELECT a.coin_id, a.balance_type, wallet_data, created_at FROM wallets a, ({}) b WHERE a.coin_id = b.coin_id AND a.balance_type = b.balance_type AND a.created_at = b.max_created_at'.format(inner_str) + where_str = "" + if opts is not None and "coin_id" in opts: + where_str = "WHERE coin_id = {}".format(opts["coin_id"]) + inner_str = f"SELECT coin_id, balance_type, MAX(created_at) as max_created_at FROM wallets {where_str} GROUP BY coin_id, balance_type" + query_str = "SELECT a.coin_id, a.balance_type, wallet_data, created_at FROM wallets a, ({}) b WHERE a.coin_id = b.coin_id AND a.balance_type = b.balance_type AND a.created_at = b.max_created_at".format( + inner_str + ) q = session.execute(text(query_str)) for row in q: coin_id = row[0] - if self.coin_clients[coin_id]['connection_type'] != 'rpc': + if self.coin_clients[coin_id]["connection_type"] != "rpc": # Skip cached info if coin was disabled continue wallet_data = json.loads(row[2]) if row[1] == 1: - wallet_data['lastupdated'] = row[3] - wallet_data['updating'] = self._updating_wallets_info.get(coin_id, False) + wallet_data["lastupdated"] = row[3] + wallet_data["updating"] = self._updating_wallets_info.get( + coin_id, False + ) # Ensure the latest addresses are displayed - q = session.execute(text('SELECT key, value FROM kv_string WHERE key = "receive_addr_{0}" OR key = "stealth_addr_{0}"'.format(chainparams[coin_id]['name']))) + q = session.execute( + text( + 'SELECT key, value FROM kv_string WHERE key = "receive_addr_{0}" OR key = "stealth_addr_{0}"'.format( + chainparams[coin_id]["name"] + ) + ) + ) for row in q: - if row[0].startswith('stealth'): + if row[0].startswith("stealth"): if coin_id == Coins.LTC: - wallet_data['mweb_address'] = row[1] + wallet_data["mweb_address"] = row[1] else: - wallet_data['stealth_address'] = row[1] + wallet_data["stealth_address"] = row[1] else: - wallet_data['deposit_address'] = row[1] + wallet_data["deposit_address"] = row[1] if coin_id in rv: rv[coin_id].update(wallet_data) @@ -7529,16 +10508,16 @@ class BasicSwap(BaseApp): finally: self.closeSession(session) - if opts is not None and 'coin_id' in opts: + if opts is not None and "coin_id" in opts: return rv for c in self.activeCoins(): coin_id = int(c) if coin_id not in rv: rv[coin_id] = { - 'name': getCoinName(c), - 'no_data': True, - 'updating': self._updating_wallets_info.get(coin_id, False), + "name": getCoinName(c), + "no_data": True, + "updating": self._updating_wallets_info.get(coin_id, False), } return rv @@ -7547,9 +10526,21 @@ class BasicSwap(BaseApp): session = self.openSession() try: if offer_id: - q = session.execute(text('SELECT COUNT(*) FROM bids WHERE state >= {} AND offer_id = x\'{}\''.format(BidStates.BID_ACCEPTED, offer_id.hex()))).first() + q = session.execute( + text( + "SELECT COUNT(*) FROM bids WHERE state >= {} AND offer_id = x'{}'".format( + BidStates.BID_ACCEPTED, offer_id.hex() + ) + ) + ).first() else: - q = session.execute(text('SELECT COUNT(*) FROM bids WHERE state >= {}'.format(BidStates.BID_ACCEPTED))).first() + q = session.execute( + text( + "SELECT COUNT(*) FROM bids WHERE state >= {}".format( + BidStates.BID_ACCEPTED + ) + ) + ).first() return q[0] finally: self.closeSession(session, commit=False) @@ -7561,7 +10552,17 @@ class BasicSwap(BaseApp): now: int = self.getTime() if with_bid_info: - subquery = session.query(sa.func.sum(Bid.amount).label('completed_bid_amount')).filter(sa.and_(Bid.offer_id == Offer.offer_id, Bid.state == BidStates.SWAP_COMPLETED)).correlate(Offer).scalar_subquery() + subquery = ( + session.query(sa.func.sum(Bid.amount).label("completed_bid_amount")) + .filter( + sa.and_( + Bid.offer_id == Offer.offer_id, + Bid.state == BidStates.SWAP_COMPLETED, + ) + ) + .correlate(Offer) + .scalar_subquery() + ) q = session.query(Offer, subquery) else: q = session.query(Offer) @@ -7569,51 +10570,57 @@ class BasicSwap(BaseApp): if sent: q = q.filter(Offer.was_sent == True) # noqa: E712 - active_state = filters.get('active', 'any') - if active_state == 'active': + active_state = filters.get("active", "any") + if active_state == "active": q = q.filter(Offer.expire_at > now, Offer.active_ind == 1) - elif active_state == 'expired': + elif active_state == "expired": q = q.filter(Offer.expire_at <= now) - elif active_state == 'revoked': + elif active_state == "revoked": q = q.filter(Offer.active_ind != 1) else: q = q.filter(sa.and_(Offer.expire_at > now, Offer.active_ind == 1)) - filter_offer_id = filters.get('offer_id', None) + filter_offer_id = filters.get("offer_id", None) if filter_offer_id is not None: q = q.filter(Offer.offer_id == filter_offer_id) - filter_coin_from = filters.get('coin_from', None) + filter_coin_from = filters.get("coin_from", None) if filter_coin_from and filter_coin_from > -1: q = q.filter(Offer.coin_from == int(filter_coin_from)) - filter_coin_to = filters.get('coin_to', None) + filter_coin_to = filters.get("coin_to", None) if filter_coin_to and filter_coin_to > -1: q = q.filter(Offer.coin_to == int(filter_coin_to)) - filter_include_sent = filters.get('include_sent', None) + filter_include_sent = filters.get("include_sent", None) if filter_include_sent is not None and filter_include_sent is not True: q = q.filter(Offer.was_sent == False) # noqa: E712 - order_dir = filters.get('sort_dir', 'desc') - order_by = filters.get('sort_by', 'created_at') + order_dir = filters.get("sort_dir", "desc") + order_by = filters.get("sort_by", "created_at") - if order_by == 'created_at': - q = q.order_by(Offer.created_at.desc() if order_dir == 'desc' else Offer.created_at.asc()) - elif order_by == 'rate': - q = q.order_by(Offer.rate.desc() if order_dir == 'desc' else Offer.rate.asc()) + if order_by == "created_at": + q = q.order_by( + Offer.created_at.desc() + if order_dir == "desc" + else Offer.created_at.asc() + ) + elif order_by == "rate": + q = q.order_by( + Offer.rate.desc() if order_dir == "desc" else Offer.rate.asc() + ) - limit = filters.get('limit', None) + limit = filters.get("limit", None) if limit is not None: q = q.limit(limit) - offset = filters.get('offset', None) + offset = filters.get("offset", None) if offset is not None: q = q.offset(offset) for row in q: offer = row[0] if with_bid_info else row # Show offers for enabled coins only try: - ci_from = self.ci(offer.coin_from) - ci_to = self.ci(offer.coin_to) - except Exception as e: + _ = self.ci(offer.coin_from) + _ = self.ci(offer.coin_to) + except Exception as e: # noqa: F841 continue if with_bid_info: rv.append((offer, 0 if row[1] is None else row[1])) @@ -7623,61 +10630,86 @@ class BasicSwap(BaseApp): finally: self.closeSession(session, commit=False) - def activeBidsQueryStr(self, now: int, offer_table: str = 'offers', bids_table: str = 'bids') -> str: - offers_inset = f' AND {offer_table}.expire_at > {now}' if offer_table != '' else '' + def activeBidsQueryStr( + self, now: int, offer_table: str = "offers", bids_table: str = "bids" + ) -> str: + offers_inset = ( + f" AND {offer_table}.expire_at > {now}" if offer_table != "" else "" + ) - inactive_states_str = ', '.join([str(int(s)) for s in inactive_states]) - return f' ({bids_table}.state NOT IN ({inactive_states_str}) AND ({bids_table}.state > {BidStates.BID_RECEIVED} OR ({bids_table}.expire_at > {now}{offers_inset}))) ' + inactive_states_str = ", ".join([str(int(s)) for s in inactive_states]) + return f" ({bids_table}.state NOT IN ({inactive_states_str}) AND ({bids_table}.state > {BidStates.BID_RECEIVED} OR ({bids_table}.expire_at > {now}{offers_inset}))) " - def listBids(self, sent: bool = False, offer_id: bytes = None, for_html: bool = False, filters={}): + def listBids( + self, + sent: bool = False, + offer_id: bytes = None, + for_html: bool = False, + filters={}, + ): session = self.openSession() try: rv = [] now: int = self.getTime() - query_str = 'SELECT ' + \ - 'bids.created_at, bids.expire_at, bids.bid_id, bids.offer_id, bids.amount, bids.state, bids.was_received, ' + \ - 'tx1.state, tx2.state, offers.coin_from, bids.rate, bids.bid_addr, offers.bid_reversed, bids.amount_to, offers.coin_to ' + \ - 'FROM bids ' + \ - 'LEFT JOIN offers ON offers.offer_id = bids.offer_id ' + \ - 'LEFT JOIN transactions AS tx1 ON tx1.bid_id = bids.bid_id AND tx1.tx_type = CASE WHEN offers.swap_type = :ads_swap THEN :al_type ELSE :itx_type END ' + \ - 'LEFT JOIN transactions AS tx2 ON tx2.bid_id = bids.bid_id AND tx2.tx_type = CASE WHEN offers.swap_type = :ads_swap THEN :bl_type ELSE :ptx_type END ' + query_str = ( + "SELECT " + + "bids.created_at, bids.expire_at, bids.bid_id, bids.offer_id, bids.amount, bids.state, bids.was_received, " + + "tx1.state, tx2.state, offers.coin_from, bids.rate, bids.bid_addr, offers.bid_reversed, bids.amount_to, offers.coin_to " + + "FROM bids " + + "LEFT JOIN offers ON offers.offer_id = bids.offer_id " + + "LEFT JOIN transactions AS tx1 ON tx1.bid_id = bids.bid_id AND tx1.tx_type = CASE WHEN offers.swap_type = :ads_swap THEN :al_type ELSE :itx_type END " + + "LEFT JOIN transactions AS tx2 ON tx2.bid_id = bids.bid_id AND tx2.tx_type = CASE WHEN offers.swap_type = :ads_swap THEN :bl_type ELSE :ptx_type END " + ) - query_str += 'WHERE bids.active_ind = 1 ' - filter_bid_id = filters.get('bid_id', None) + query_str += "WHERE bids.active_ind = 1 " + filter_bid_id = filters.get("bid_id", None) if filter_bid_id is not None: - query_str += 'AND bids.bid_id = x\'{}\' '.format(filter_bid_id.hex()) + query_str += "AND bids.bid_id = x'{}' ".format(filter_bid_id.hex()) if offer_id is not None: - query_str += 'AND bids.offer_id = x\'{}\' '.format(offer_id.hex()) + query_str += "AND bids.offer_id = x'{}' ".format(offer_id.hex()) elif sent: - query_str += 'AND bids.was_sent = 1 ' + query_str += "AND bids.was_sent = 1 " else: - query_str += 'AND bids.was_received = 1 ' + query_str += "AND bids.was_received = 1 " - bid_state_ind = filters.get('bid_state_ind', -1) + bid_state_ind = filters.get("bid_state_ind", -1) if bid_state_ind != -1: - query_str += 'AND bids.state = {} '.format(bid_state_ind) + query_str += "AND bids.state = {} ".format(bid_state_ind) - with_available_or_active = filters.get('with_available_or_active', False) - with_expired = filters.get('with_expired', True) + with_available_or_active = filters.get("with_available_or_active", False) + with_expired = filters.get("with_expired", True) if with_available_or_active: - query_str += ' AND ' + self.activeBidsQueryStr(now) + query_str += " AND " + self.activeBidsQueryStr(now) else: if with_expired is not True: - query_str += 'AND bids.expire_at > {} AND offers.expire_at > {} '.format(now, now) + query_str += ( + "AND bids.expire_at > {} AND offers.expire_at > {} ".format( + now, now + ) + ) - sort_dir = filters.get('sort_dir', 'DESC').upper() - sort_by = filters.get('sort_by', 'created_at') - query_str += f' ORDER BY bids.{sort_by} {sort_dir}' + sort_dir = filters.get("sort_dir", "DESC").upper() + sort_by = filters.get("sort_by", "created_at") + query_str += f" ORDER BY bids.{sort_by} {sort_dir}" - limit = filters.get('limit', None) + limit = filters.get("limit", None) if limit is not None: - query_str += f' LIMIT {limit}' - offset = filters.get('offset', None) + query_str += f" LIMIT {limit}" + offset = filters.get("offset", None) if offset is not None: - query_str += f' OFFSET {offset}' + query_str += f" OFFSET {offset}" - q = session.execute(text(query_str), {'ads_swap': SwapTypes.XMR_SWAP, 'itx_type': TxTypes.ITX, 'ptx_type': TxTypes.PTX, 'al_type': TxTypes.XMR_SWAP_A_LOCK, 'bl_type': TxTypes.XMR_SWAP_B_LOCK}) + q = session.execute( + text(query_str), + { + "ads_swap": SwapTypes.XMR_SWAP, + "itx_type": TxTypes.ITX, + "ptx_type": TxTypes.PTX, + "al_type": TxTypes.XMR_SWAP_A_LOCK, + "bl_type": TxTypes.XMR_SWAP_B_LOCK, + }, + ) for row in q: result = [x for x in row] coin_from = result[9] @@ -7685,8 +10717,8 @@ class BasicSwap(BaseApp): # Show bids for enabled coins only try: ci_from = self.ci(coin_from) - ci_to = self.ci(coin_to) - except Exception as e: + _ = self.ci(coin_to) + except Exception as e: # noqa: F841 continue if result[12]: # Reversed amount_from = result[13] @@ -7729,75 +10761,83 @@ class BasicSwap(BaseApp): for c, v in self.coin_clients.items(): if c in (Coins.PART_ANON, Coins.PART_BLIND): # exclude duplicates continue - if self.coin_clients[c]['connection_type'] == 'rpc': - rv_heights.append((c, v['last_height_checked'])) - for o in v['watched_outputs']: + if self.coin_clients[c]["connection_type"] == "rpc": + rv_heights.append((c, v["last_height_checked"])) + for o in v["watched_outputs"]: rv.append((c, o.bid_id, o.txid_hex, o.vout, o.tx_type)) return (rv, rv_heights) finally: self.mxDB.release() def listAllSMSGAddresses(self, filters={}, session=None): - query_str = 'SELECT addr_id, addr, use_type, active_ind, created_at, note, pubkey FROM smsgaddresses' - query_str += ' WHERE 1 = 1 ' + query_str = "SELECT addr_id, addr, use_type, active_ind, created_at, note, pubkey FROM smsgaddresses" + query_str += " WHERE 1 = 1 " query_data = {} - if filters.get('exclude_inactive', True) is True: - query_str += ' AND active_ind = :active_ind ' - query_data['active_ind'] = 1 - if 'addr_id' in filters: - query_str += ' AND addr_id = :addr_id ' - query_data['addr_id'] = filters['addr_id'] - if 'addressnote' in filters: - query_str += ' AND note LIKE :note ' - query_data['note'] = '%' + filters['addressnote'] + '%' - if 'addr_type' in filters and filters['addr_type'] > -1: - query_str += ' AND use_type = :addr_type ' - query_data['addr_type'] = filters['addr_type'] + if filters.get("exclude_inactive", True) is True: + query_str += " AND active_ind = :active_ind " + query_data["active_ind"] = 1 + if "addr_id" in filters: + query_str += " AND addr_id = :addr_id " + query_data["addr_id"] = filters["addr_id"] + if "addressnote" in filters: + query_str += " AND note LIKE :note " + query_data["note"] = "%" + filters["addressnote"] + "%" + if "addr_type" in filters and filters["addr_type"] > -1: + query_str += " AND use_type = :addr_type " + query_data["addr_type"] = filters["addr_type"] - sort_dir = filters.get('sort_dir', 'DESC').upper() - sort_by = filters.get('sort_by', 'created_at') - query_str += f' ORDER BY {sort_by} {sort_dir}' - limit = filters.get('limit', None) + sort_dir = filters.get("sort_dir", "DESC").upper() + sort_by = filters.get("sort_by", "created_at") + query_str += f" ORDER BY {sort_by} {sort_dir}" + limit = filters.get("limit", None) if limit is not None: - query_str += f' LIMIT {limit}' - offset = filters.get('offset', None) + query_str += f" LIMIT {limit}" + offset = filters.get("offset", None) if offset is not None: - query_str += f' OFFSET {offset}' + query_str += f" OFFSET {offset}" try: use_session = self.openSession(session) rv = [] q = use_session.execute(text(query_str), query_data) for row in q: - rv.append({ - 'id': row[0], - 'addr': row[1], - 'type': row[2], - 'active_ind': row[3], - 'created_at': row[4], - 'note': row[5], - 'pubkey': row[6], - }) + rv.append( + { + "id": row[0], + "addr": row[1], + "type": row[2], + "active_ind": row[3], + "created_at": row[4], + "note": row[5], + "pubkey": row[6], + } + ) return rv finally: if session is None: self.closeSession(use_session, commit=False) def listSMSGAddresses(self, use_type_str: str): - if use_type_str == 'offer_send_from': + if use_type_str == "offer_send_from": use_type = AddressTypes.OFFER - elif use_type_str == 'offer_send_to': + elif use_type_str == "offer_send_to": use_type = AddressTypes.SEND_OFFER - elif use_type_str == 'bid': + elif use_type_str == "bid": use_type = AddressTypes.BID else: - raise ValueError('Unknown address type') + raise ValueError("Unknown address type") try: session = self.openSession() rv = [] - q = session.execute(text('SELECT sa.addr, ki.label FROM smsgaddresses AS sa LEFT JOIN knownidentities AS ki ON sa.addr = ki.address WHERE sa.use_type = {} AND sa.active_ind = 1 ORDER BY sa.addr_id DESC'.format(use_type))) + q = session.execute( + text( + "SELECT sa.addr, ki.label FROM smsgaddresses AS sa LEFT JOIN knownidentities AS ki ON sa.addr = ki.address WHERE sa.use_type = {} AND sa.active_ind = 1 ORDER BY sa.addr_id DESC".format( + use_type + ) + ) + ) for row in q: rv.append((row[0], row[1])) return rv @@ -7809,23 +10849,23 @@ class BasicSwap(BaseApp): session = self.openSession() rv = [] - query_str = 'SELECT strats.record_id, strats.label, strats.type_ind FROM automationstrategies AS strats' - query_str += ' WHERE strats.active_ind = 1 ' + query_str = "SELECT strats.record_id, strats.label, strats.type_ind FROM automationstrategies AS strats" + query_str += " WHERE strats.active_ind = 1 " - type_ind = filters.get('type_ind', None) + type_ind = filters.get("type_ind", None) if type_ind is not None: - query_str += f' AND strats.type_ind = {type_ind} ' + query_str += f" AND strats.type_ind = {type_ind} " - sort_dir = filters.get('sort_dir', 'DESC').upper() - sort_by = filters.get('sort_by', 'created_at') - query_str += f' ORDER BY strats.{sort_by} {sort_dir}' + sort_dir = filters.get("sort_dir", "DESC").upper() + sort_by = filters.get("sort_by", "created_at") + query_str += f" ORDER BY strats.{sort_by} {sort_dir}" - limit = filters.get('limit', None) + limit = filters.get("limit", None) if limit is not None: - query_str += f' LIMIT {limit}' - offset = filters.get('offset', None) + query_str += f" LIMIT {limit}" + offset = filters.get("offset", None) if offset is not None: - query_str += f' OFFSET {offset}' + query_str += f" OFFSET {offset}" q = session.execute(text(query_str)) for row in q: @@ -7837,15 +10877,23 @@ class BasicSwap(BaseApp): def getAutomationStrategy(self, strategy_id: int): try: session = self.openSession() - return session.query(AutomationStrategy).filter_by(record_id=strategy_id).first() + return ( + session.query(AutomationStrategy) + .filter_by(record_id=strategy_id) + .first() + ) finally: self.closeSession(session, commit=False) def updateAutomationStrategy(self, strategy_id: int, data, note: str) -> None: try: session = self.openSession() - strategy = session.query(AutomationStrategy).filter_by(record_id=strategy_id).first() - strategy.data = json.dumps(data).encode('utf-8') + strategy = ( + session.query(AutomationStrategy) + .filter_by(record_id=strategy_id) + .first() + ) + strategy.data = json.dumps(data).encode("utf-8") strategy.note = note session.add(strategy) finally: @@ -7854,60 +10902,78 @@ class BasicSwap(BaseApp): def getLinkedStrategy(self, linked_type: int, linked_id): try: session = self.openSession() - query_str = 'SELECT links.strategy_id, strats.label FROM automationlinks links' + \ - ' LEFT JOIN automationstrategies strats ON strats.record_id = links.strategy_id' + \ - ' WHERE links.linked_type = {} AND links.linked_id = x\'{}\' AND links.active_ind = 1'.format(int(linked_type), linked_id.hex()) + query_str = ( + "SELECT links.strategy_id, strats.label FROM automationlinks links" + + " LEFT JOIN automationstrategies strats ON strats.record_id = links.strategy_id" + + " WHERE links.linked_type = {} AND links.linked_id = x'{}' AND links.active_ind = 1".format( + int(linked_type), linked_id.hex() + ) + ) q = session.execute(text(query_str)).first() return q finally: self.closeSession(session, commit=False) - def newSMSGAddress(self, use_type=AddressTypes.RECV_OFFER, addressnote=None, session=None): + def newSMSGAddress( + self, use_type=AddressTypes.RECV_OFFER, addressnote=None, session=None + ): now: int = self.getTime() try: use_session = self.openSession(session) - v = use_session.query(DBKVString).filter_by(key='smsg_chain_id').first() + v = use_session.query(DBKVString).filter_by(key="smsg_chain_id").first() if not v: - smsg_account = self.callrpc('extkey', ['deriveAccount', 'smsg keys', '78900']) - smsg_account_id = smsg_account['account'] - self.log.info(f'Creating smsg keys account {smsg_account_id}') - extkey = self.callrpc('extkey') + smsg_account = self.callrpc( + "extkey", ["deriveAccount", "smsg keys", "78900"] + ) + smsg_account_id = smsg_account["account"] + self.log.info(f"Creating smsg keys account {smsg_account_id}") + extkey = self.callrpc("extkey") # Disable receiving on all chains smsg_chain_id = None - extkey = self.callrpc('extkey', ['account', smsg_account_id]) - for c in extkey['chains']: - rv = self.callrpc('extkey', ['options', c['id'], 'receive_on', 'false']) - if c['function'] == 'active_external': - smsg_chain_id = c['id'] + extkey = self.callrpc("extkey", ["account", smsg_account_id]) + for c in extkey["chains"]: + self.callrpc("extkey", ["options", c["id"], "receive_on", "false"]) + if c["function"] == "active_external": + smsg_chain_id = c["id"] if not smsg_chain_id: - raise ValueError('External chain not found.') + raise ValueError("External chain not found.") - use_session.add(DBKVString( - key='smsg_chain_id', - value=smsg_chain_id)) + use_session.add(DBKVString(key="smsg_chain_id", value=smsg_chain_id)) else: smsg_chain_id = v.value - smsg_chain = self.callrpc('extkey', ['key', smsg_chain_id]) - num_derives = int(smsg_chain['num_derives']) + smsg_chain = self.callrpc("extkey", ["key", smsg_chain_id]) + num_derives = int(smsg_chain["num_derives"]) - new_addr = self.callrpc('deriverangekeys', [num_derives, num_derives, smsg_chain_id, False, True])[0] + new_addr = self.callrpc( + "deriverangekeys", + [num_derives, num_derives, smsg_chain_id, False, True], + )[0] num_derives += 1 - rv = self.callrpc('extkey', ['options', smsg_chain_id, 'num_derives', str(num_derives)]) + # Update num_derives + self.callrpc( + "extkey", ["options", smsg_chain_id, "num_derives", str(num_derives)] + ) - addr_info = self.callrpc('getaddressinfo', [new_addr]) - self.callrpc('smsgaddlocaladdress', [new_addr]) # Enable receiving smsgs - self.callrpc('smsglocalkeys', ['anon', '-', new_addr]) + addr_info = self.callrpc("getaddressinfo", [new_addr]) + self.callrpc("smsgaddlocaladdress", [new_addr]) # Enable receiving smsgs + self.callrpc("smsglocalkeys", ["anon", "-", new_addr]) - addr_obj = SmsgAddress(addr=new_addr, use_type=use_type, active_ind=1, created_at=now, pubkey=addr_info['pubkey']) + addr_obj = SmsgAddress( + addr=new_addr, + use_type=use_type, + active_ind=1, + created_at=now, + pubkey=addr_info["pubkey"], + ) if addressnote is not None: addr_obj.note = addressnote use_session.add(addr_obj) - return new_addr, addr_info['pubkey'] + return new_addr, addr_info["pubkey"] finally: if session is None: self.closeSession(use_session) @@ -7918,32 +10984,55 @@ class BasicSwap(BaseApp): now: int = self.getTime() ci = self.ci(Coins.PART) add_addr = ci.pubkey_to_address(bytes.fromhex(pubkey_hex)) - self.callrpc('smsgaddaddress', [add_addr, pubkey_hex]) - self.callrpc('smsglocalkeys', ['anon', '-', add_addr]) + self.callrpc("smsgaddaddress", [add_addr, pubkey_hex]) + self.callrpc("smsglocalkeys", ["anon", "-", add_addr]) - session.add(SmsgAddress(addr=add_addr, use_type=AddressTypes.SEND_OFFER, active_ind=1, created_at=now, note=addressnote, pubkey=pubkey_hex)) + session.add( + SmsgAddress( + addr=add_addr, + use_type=AddressTypes.SEND_OFFER, + active_ind=1, + created_at=now, + note=addressnote, + pubkey=pubkey_hex, + ) + ) return add_addr finally: self.closeSession(session) - def editSMSGAddress(self, address: str, active_ind: int, addressnote: str = None, use_type=None, session=None) -> None: + def editSMSGAddress( + self, + address: str, + active_ind: int, + addressnote: str = None, + use_type=None, + session=None, + ) -> None: use_session = self.openSession(session) try: - mode = '-' if active_ind == 0 else '+' - rv = self.callrpc('smsglocalkeys', ['recv', mode, address]) - if 'not found' in rv['result']: - self.callrpc('smsgaddlocaladdress', [address,]) - self.callrpc('smsglocalkeys', ['anon', '-', address]) - values = {'active_ind': active_ind, 'addr': address, 'use_type': use_type} - query_str: str = 'UPDATE smsgaddresses SET active_ind = :active_ind' + mode = "-" if active_ind == 0 else "+" + rv = self.callrpc("smsglocalkeys", ["recv", mode, address]) + if "not found" in rv["result"]: + self.callrpc( + "smsgaddlocaladdress", + [ + address, + ], + ) + self.callrpc("smsglocalkeys", ["anon", "-", address]) + values = {"active_ind": active_ind, "addr": address, "use_type": use_type} + query_str: str = "UPDATE smsgaddresses SET active_ind = :active_ind" if addressnote is not None: - values['note'] = addressnote - query_str += ', note = :note' - query_str += ' WHERE addr = :addr' + values["note"] = addressnote + query_str += ", note = :note" + query_str += " WHERE addr = :addr" rv = use_session.execute(text(query_str), values) if rv.rowcount < 1: - query_str: str = 'INSERT INTO smsgaddresses (addr, active_ind, use_type) VALUES (:addr, :active_ind, :use_type)' + query_str: str = ( + "INSERT INTO smsgaddresses (addr, active_ind, use_type) VALUES (:addr, :active_ind, :use_type)" + ) use_session.execute(text(query_str), values) finally: if session is None: @@ -7951,7 +11040,7 @@ class BasicSwap(BaseApp): def disableAllSMSGAddresses(self): filters = { - 'exclude_inactive': True, + "exclude_inactive": True, } session = self.openSession() rv = {} @@ -7959,40 +11048,48 @@ class BasicSwap(BaseApp): try: active_addresses = self.listAllSMSGAddresses(filters, session=session) for active_address in active_addresses: - if active_address['addr'] == self.network_addr: + if active_address["addr"] == self.network_addr: continue - self.editSMSGAddress(active_address['addr'], active_ind=0, session=session) + self.editSMSGAddress( + active_address["addr"], active_ind=0, session=session + ) num_disabled += 1 finally: self.closeSession(session) - rv['num_disabled'] = num_disabled + rv["num_disabled"] = num_disabled num_core_disabled = 0 # Check localkeys - smsg_localkeys = self.callrpc('smsglocalkeys') - all_keys = smsg_localkeys['wallet_keys'] + smsg_localkeys['smsg_keys'] + smsg_localkeys = self.callrpc("smsglocalkeys") + all_keys = smsg_localkeys["wallet_keys"] + smsg_localkeys["smsg_keys"] for smsg_addr in all_keys: - if smsg_addr['address'] == self.network_addr: + if smsg_addr["address"] == self.network_addr: continue - if smsg_addr['receive'] != 0: - self.log.warning('Disabling smsg key found in core and not bsx: {}'.format(smsg_addr['address'])) - self.callrpc('smsglocalkeys', ['recv', '-', smsg_addr['address']]) + if smsg_addr["receive"] != 0: + self.log.warning( + "Disabling smsg key found in core and not bsx: {}".format( + smsg_addr["address"] + ) + ) + self.callrpc("smsglocalkeys", ["recv", "-", smsg_addr["address"]]) num_core_disabled += 1 if num_core_disabled > 0: - rv['num_core_disabled'] = num_core_disabled + rv["num_core_disabled"] = num_core_disabled return rv def prepareSMSGAddress(self, addr_send_from, use_type, session): if addr_send_from is None: return self.newSMSGAddress(use_type=use_type, session=session)[0] use_addr = addr_send_from - self.editSMSGAddress(use_addr, 1, use_type=use_type, session=session) # Ensure receive is active + self.editSMSGAddress( + use_addr, 1, use_type=use_type, session=session + ) # Ensure receive is active return use_addr def createCoinALockRefundSwipeTx(self, ci, bid, offer, xmr_swap, xmr_offer): - self.log.debug('Creating %s lock refund swipe tx', ci.coin_name()) + self.log.debug("Creating %s lock refund swipe tx", ci.coin_name()) reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from, offer.coin_to) a_fee_rate: int = xmr_offer.b_fee_rate if reverse_bid else xmr_offer.a_fee_rate @@ -8005,29 +11102,46 @@ class BasicSwap(BaseApp): pass # BCH sends a separate mercy tx else: - for_ed25519: bool = True if self.ci(coin_to).curve_type() == Curves.ed25519 else False - kbsf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KBSF, for_ed25519) + for_ed25519: bool = ( + True if self.ci(coin_to).curve_type() == Curves.ed25519 else False + ) + kbsf = self.getPathKey( + coin_from, + coin_to, + bid.created_at, + xmr_swap.contract_count, + KeyTypes.KBSF, + for_ed25519, + ) pkh_dest = ci.decodeAddress(self.getReceiveAddressForCoin(ci.coin_type())) spend_tx = ci.createSCLockRefundSpendToFTx( - xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script, + xmr_swap.a_lock_refund_tx, + xmr_swap.a_lock_refund_tx_script, pkh_dest, - a_fee_rate, xmr_swap.vkbv, kbsf) + a_fee_rate, + xmr_swap.vkbv, + kbsf, + ) - vkaf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KAF) + vkaf = self.getPathKey( + coin_from, coin_to, bid.created_at, xmr_swap.contract_count, KeyTypes.KAF + ) prevout_amount = ci.getLockRefundTxSwapOutputValue(bid, xmr_swap) - sig = ci.signTx(vkaf, spend_tx, 0, xmr_swap.a_lock_refund_tx_script, prevout_amount) + sig = ci.signTx( + vkaf, spend_tx, 0, xmr_swap.a_lock_refund_tx_script, prevout_amount + ) witness_stack = [ sig, - b'', + b"", xmr_swap.a_lock_refund_tx_script, ] xmr_swap.a_lock_refund_swipe_tx = ci.setTxSignature(spend_tx, witness_stack) def setBidDebugInd(self, bid_id: bytes, debug_ind, add_to_bid: bool = True) -> None: - self.log.debug('Bid %s Setting debug flag: %s', bid_id.hex(), debug_ind) + self.log.debug("Bid %s Setting debug flag: %s", bid_id.hex(), debug_ind) self._debug_cases.append((bid_id, debug_ind)) if add_to_bid is False: @@ -8035,7 +11149,7 @@ class BasicSwap(BaseApp): bid = self.getBid(bid_id) if bid is None: - raise ValueError('Bid not found.') + raise ValueError("Bid not found.") bid.debug_ind = debug_ind @@ -8053,7 +11167,7 @@ class BasicSwap(BaseApp): return False def storeOfferRevoke(self, offer_id: bytes, sig) -> bool: - self.log.debug('Storing revoke request for offer: %s', offer_id.hex()) + self.log.debug("Storing revoke request for offer: %s", offer_id.hex()) for pair in self._possibly_revoked_offers: if offer_id == pair[0]: return False @@ -8063,9 +11177,15 @@ class BasicSwap(BaseApp): def isOfferRevoked(self, offer_id: bytes, offer_addr_from) -> bool: for pair in self._possibly_revoked_offers: if offer_id == pair[0]: - signature_enc = base64.b64encode(pair[1]).decode('utf-8') - passed = self.callcoinrpc(Coins.PART, 'verifymessage', [offer_addr_from, signature_enc, offer_id.hex() + '_revoke']) - return True if passed is True else False # _possibly_revoked_offers should not contain duplicates + signature_enc = base64.b64encode(pair[1]).decode("utf-8") + passed = self.callcoinrpc( + Coins.PART, + "verifymessage", + [offer_addr_from, signature_enc, offer_id.hex() + "_revoke"], + ) + return ( + True if passed is True else False + ) # _possibly_revoked_offers should not contain duplicates return False def updateBidInProgress(self, bid): @@ -8080,52 +11200,62 @@ class BasicSwap(BaseApp): rv = [] for a in addresses: v = session.query(KnownIdentity).filter_by(address=a).first() - rv.append('' if (not v or not v.label) else v.label) + rv.append("" if (not v or not v.label) else v.label) return rv finally: self.closeSession(session, commit=False) def add_connection(self, host, port, peer_pubkey): - self.log.info('add_connection %s %d %s', host, port, peer_pubkey.hex()) + self.log.info("add_connection %s %d %s", host, port, peer_pubkey.hex()) self._network.add_connection(host, port, peer_pubkey) def get_network_info(self): if not self._network: - return {'Error': 'Not Initialised'} + return {"Error": "Not Initialised"} return self._network.get_info() def getLockedState(self): if self._is_encrypted is None or self._is_locked is None: - self._is_encrypted, self._is_locked = self.ci(Coins.PART).isWalletEncryptedLocked() + self._is_encrypted, self._is_locked = self.ci( + Coins.PART + ).isWalletEncryptedLocked() return self._is_encrypted, self._is_locked def lookupRates(self, coin_from, coin_to, output_array=False): - self.log.debug('lookupRates {}, {}'.format(Coins(int(coin_from)).name, Coins(int(coin_to)).name)) + self.log.debug( + "lookupRates {}, {}".format( + Coins(int(coin_from)).name, Coins(int(coin_to)).name + ) + ) - rate_sources = self.settings.get('rate_sources', {}) + rate_sources = self.settings.get("rate_sources", {}) ci_from = self.ci(int(coin_from)) ci_to = self.ci(int(coin_to)) - name_from = ci_from.chainparams()['name'] - name_to = ci_to.chainparams()['name'] - exchange_name_from = ci_from.getExchangeName('coingecko.com') - exchange_name_to = ci_to.getExchangeName('coingecko.com') - ticker_from = ci_from.chainparams()['ticker'] - ticker_to = ci_to.chainparams()['ticker'] - headers = {'User-Agent': 'Mozilla/5.0', 'Connection': 'close'} + name_from = ci_from.chainparams()["name"] + name_to = ci_to.chainparams()["name"] + exchange_name_from = ci_from.getExchangeName("coingecko.com") + exchange_name_to = ci_to.getExchangeName("coingecko.com") + ticker_from = ci_from.chainparams()["ticker"] + ticker_to = ci_to.chainparams()["ticker"] + headers = {"User-Agent": "Mozilla/5.0", "Connection": "close"} rv = {} - if rate_sources.get('coingecko.com', True): + if rate_sources.get("coingecko.com", True): try: - url = 'https://api.coingecko.com/api/v3/simple/price?ids={},{}&vs_currencies=usd,btc'.format(exchange_name_from, exchange_name_to) - self.log.debug(f'lookupRates: {url}') + url = "https://api.coingecko.com/api/v3/simple/price?ids={},{}&vs_currencies=usd,btc".format( + exchange_name_from, exchange_name_to + ) + self.log.debug(f"lookupRates: {url}") start = time.time() js = json.loads(self.readURL(url, timeout=10, headers=headers)) - js['time_taken'] = time.time() - start - rate = float(js[exchange_name_from]['usd']) / float(js[exchange_name_to]['usd']) - js['rate_inferred'] = ci_to.format_amount(rate, conv_int=True, r=1) - rv['coingecko'] = js + js["time_taken"] = time.time() - start + rate = float(js[exchange_name_from]["usd"]) / float( + js[exchange_name_to]["usd"] + ) + js["rate_inferred"] = ci_to.format_amount(rate, conv_int=True, r=1) + rv["coingecko"] = js except Exception as e: - rv['coingecko_error'] = str(e) + rv["coingecko_error"] = str(e) if self.debug: self.log.error(traceback.format_exc()) @@ -8139,23 +11269,25 @@ class BasicSwap(BaseApp): if output_array: def format_float(f): - return '{:.12f}'.format(f).rstrip('0').rstrip('.') + return "{:.12f}".format(f).rstrip("0").rstrip(".") rv_array = [] - if 'coingecko_error' in rv: - rv_array.append(('coingecko.com', 'error', rv['coingecko_error'])) - if 'coingecko' in rv: - js = rv['coingecko'] - rv_array.append(( - 'coingecko.com', - ticker_from, - ticker_to, - format_float(float(js[name_from]['usd'])), - format_float(float(js[name_to]['usd'])), - format_float(float(js[name_from]['btc'])), - format_float(float(js[name_to]['btc'])), - format_float(float(js['rate_inferred'])), - )) + if "coingecko_error" in rv: + rv_array.append(("coingecko.com", "error", rv["coingecko_error"])) + if "coingecko" in rv: + js = rv["coingecko"] + rv_array.append( + ( + "coingecko.com", + ticker_from, + ticker_to, + format_float(float(js[name_from]["usd"])), + format_float(float(js[name_to]["usd"])), + format_float(float(js[name_from]["btc"])), + format_float(float(js[name_to]["btc"])), + format_float(float(js["rate_inferred"])), + ) + ) return rv_array return rv @@ -8163,7 +11295,7 @@ class BasicSwap(BaseApp): def setFilters(self, prefix, filters): try: session = self.openSession() - key_str = 'saved_filters_' + prefix + key_str = "saved_filters_" + prefix value_str = json.dumps(filters) self.setStringKV(key_str, value_str, session) finally: @@ -8172,7 +11304,7 @@ class BasicSwap(BaseApp): def getFilters(self, prefix): try: session = self.openSession() - key_str = 'saved_filters_' + prefix + key_str = "saved_filters_" + prefix value_str = self.getStringKV(key_str, session) return None if not value_str else json.loads(value_str) finally: @@ -8181,8 +11313,8 @@ class BasicSwap(BaseApp): def clearFilters(self, prefix) -> None: try: session = self.openSession() - key_str = 'saved_filters_' + prefix - query_str = 'DELETE FROM kv_string WHERE key = :key_str' - session.execute(text(query_str), {'key_str': key_str}) + key_str = "saved_filters_" + prefix + query_str = "DELETE FROM kv_string WHERE key = :key_str" + session.execute(text(query_str), {"key_str": key_str}) finally: self.closeSession(session) diff --git a/basicswap/basicswap_util.py b/basicswap/basicswap_util.py index 53231f8..1b82b11 100644 --- a/basicswap/basicswap_util.py +++ b/basicswap/basicswap_util.py @@ -76,13 +76,13 @@ class OfferStates(IntEnum): class BidStates(IntEnum): BID_SENT = 1 - BID_RECEIVING = 2 # Partially received + BID_RECEIVING = 2 # Partially received BID_RECEIVED = 3 - BID_RECEIVING_ACC = 4 # Partially received accept message - BID_ACCEPTED = 5 # BidAcceptMessage received/sent - SWAP_INITIATED = 6 # Initiate txn validated - SWAP_PARTICIPATING = 7 # Participate txn validated - SWAP_COMPLETED = 8 # All swap txns spent + BID_RECEIVING_ACC = 4 # Partially received accept message + BID_ACCEPTED = 5 # BidAcceptMessage received/sent + SWAP_INITIATED = 6 # Initiate txn validated + SWAP_PARTICIPATING = 7 # Participate txn validated + SWAP_COMPLETED = 8 # All swap txns spent XMR_SWAP_SCRIPT_COIN_LOCKED = 9 XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX = 10 XMR_SWAP_NOSCRIPT_COIN_LOCKED = 11 @@ -96,13 +96,13 @@ class BidStates(IntEnum): XMR_SWAP_FAILED = 19 SWAP_DELAYING = 20 SWAP_TIMEDOUT = 21 - BID_ABANDONED = 22 # Bid will no longer be processed - BID_ERROR = 23 # An error occurred + BID_ABANDONED = 22 # Bid will no longer be processed + BID_ERROR = 23 # An error occurred BID_STALLED_FOR_TEST = 24 BID_REJECTED = 25 BID_STATE_UNKNOWN = 26 - XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS = 27 # XmrBidLockTxSigsMessage - XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX = 28 # XmrBidLockSpendTxMessage + XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS = 27 # XmrBidLockTxSigsMessage + XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX = 28 # XmrBidLockSpendTxMessage BID_REQUEST_SENT = 29 BID_REQUEST_ACCEPTED = 30 BID_EXPIRED = 31 @@ -231,12 +231,12 @@ class AutomationOverrideOptions(IntEnum): def strAutomationOverrideOption(option): if option == AutomationOverrideOptions.DEFAULT: - return 'Default' + return "Default" if option == AutomationOverrideOptions.ALWAYS_ACCEPT: - return 'Always Accept' + return "Always Accept" if option == AutomationOverrideOptions.NEVER_ACCEPT: - return 'Never Accept' - return 'Unknown' + return "Never Accept" + return "Unknown" class VisibilityOverrideOptions(IntEnum): @@ -247,250 +247,253 @@ class VisibilityOverrideOptions(IntEnum): def strVisibilityOverrideOption(option): if option == VisibilityOverrideOptions.DEFAULT: - return 'Default' + return "Default" if option == VisibilityOverrideOptions.HIDE: - return 'Hide' + return "Hide" if option == VisibilityOverrideOptions.BLOCK: - return 'Block' - return 'Unknown' + return "Block" + return "Unknown" def strOfferState(state): if state == OfferStates.OFFER_SENT: - return 'Sent' + return "Sent" if state == OfferStates.OFFER_RECEIVED: - return 'Received' + return "Received" if state == OfferStates.OFFER_ABANDONED: - return 'Abandoned' + return "Abandoned" if state == OfferStates.OFFER_EXPIRED: - return 'Expired' - return 'Unknown' + return "Expired" + return "Unknown" def strBidState(state): if state == BidStates.BID_SENT: - return 'Sent' + return "Sent" if state == BidStates.BID_RECEIVING: - return 'Receiving' + return "Receiving" if state == BidStates.BID_RECEIVING_ACC: - return 'Receiving accept' + return "Receiving accept" if state == BidStates.BID_RECEIVED: - return 'Received' + return "Received" if state == BidStates.BID_ACCEPTED: - return 'Accepted' + return "Accepted" if state == BidStates.SWAP_INITIATED: - return 'Initiated' + return "Initiated" if state == BidStates.SWAP_PARTICIPATING: - return 'Participating' + return "Participating" if state == BidStates.SWAP_COMPLETED: - return 'Completed' + return "Completed" if state == BidStates.SWAP_TIMEDOUT: - return 'Timed-out' + return "Timed-out" if state == BidStates.BID_ABANDONED: - return 'Abandoned' + return "Abandoned" if state == BidStates.BID_STALLED_FOR_TEST: - return 'Stalled (debug)' + return "Stalled (debug)" if state == BidStates.BID_ERROR: - return 'Error' + return "Error" if state == BidStates.BID_REJECTED: - return 'Rejected' + return "Rejected" if state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED: - return 'Script coin locked' + return "Script coin locked" if state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX: - return 'Script coin spend tx valid' + return "Script coin spend tx valid" if state == BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED: - return 'Scriptless coin locked' + return "Scriptless coin locked" if state == BidStates.XMR_SWAP_LOCK_RELEASED: - return 'Script coin lock released' + return "Script coin lock released" if state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED: - return 'Script tx redeemed' + return "Script tx redeemed" if state == BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND: - return 'Script pre-refund tx in chain' + return "Script pre-refund tx in chain" if state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED: - return 'Scriptless tx redeemed' + return "Scriptless tx redeemed" if state == BidStates.XMR_SWAP_NOSCRIPT_TX_RECOVERED: - return 'Scriptless tx recovered' + return "Scriptless tx recovered" if state == BidStates.XMR_SWAP_FAILED_REFUNDED: - return 'Failed, refunded' + return "Failed, refunded" if state == BidStates.XMR_SWAP_FAILED_SWIPED: - return 'Failed, swiped' + return "Failed, swiped" if state == BidStates.XMR_SWAP_FAILED: - return 'Failed' + return "Failed" if state == BidStates.SWAP_DELAYING: - return 'Delaying' + return "Delaying" if state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS: - return 'Exchanged script lock tx sigs msg' + return "Exchanged script lock tx sigs msg" if state == BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX: - return 'Exchanged script lock spend tx msg' + return "Exchanged script lock spend tx msg" if state == BidStates.BID_REQUEST_SENT: - return 'Request sent' + return "Request sent" if state == BidStates.BID_REQUEST_ACCEPTED: - return 'Request accepted' + return "Request accepted" if state == BidStates.BID_STATE_UNKNOWN: - return 'Unknown bid state' + return "Unknown bid state" if state == BidStates.BID_EXPIRED: - return 'Expired' - return 'Unknown' + ' ' + str(state) + return "Expired" + return "Unknown" + " " + str(state) def strTxState(state): if state == TxStates.TX_NONE: - return 'None' + return "None" if state == TxStates.TX_SENT: - return 'Sent' + return "Sent" if state == TxStates.TX_CONFIRMED: - return 'Confirmed' + return "Confirmed" if state == TxStates.TX_REDEEMED: - return 'Redeemed' + return "Redeemed" if state == TxStates.TX_REFUNDED: - return 'Refunded' + return "Refunded" if state == TxStates.TX_IN_MEMPOOL: - return 'In Mempool' + return "In Mempool" if state == TxStates.TX_IN_CHAIN: - return 'In Chain' - return 'Unknown' + return "In Chain" + return "Unknown" def strTxType(tx_type): if tx_type == TxTypes.XMR_SWAP_A_LOCK: - return 'Chain A Lock Tx' + return "Chain A Lock Tx" if tx_type == TxTypes.XMR_SWAP_A_LOCK_SPEND: - return 'Chain A Lock Spend Tx' + return "Chain A Lock Spend Tx" if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND: - return 'Chain A Lock Refund Tx' + return "Chain A Lock Refund Tx" if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND: - return 'Chain A Lock Refund Spend Tx' + return "Chain A Lock Refund Spend Tx" if tx_type == TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE: - return 'Chain A Lock Refund Swipe Tx' + return "Chain A Lock Refund Swipe Tx" if tx_type == TxTypes.XMR_SWAP_B_LOCK: - return 'Chain B Lock Tx' + return "Chain B Lock Tx" if tx_type == TxTypes.ITX_PRE_FUNDED: - return 'Funded mock initiate Tx' + return "Funded mock initiate Tx" if tx_type == TxTypes.BCH_MERCY: - return 'BCH Mercy Tx' - return 'Unknown' + return "BCH Mercy Tx" + return "Unknown" def strAddressType(addr_type): if addr_type == AddressTypes.OFFER: - return 'Offer' + return "Offer" if addr_type == AddressTypes.BID: - return 'Bid' + return "Bid" if addr_type == AddressTypes.RECV_OFFER: - return 'Offer recv' + return "Offer recv" if addr_type == AddressTypes.SEND_OFFER: - return 'Offer send' - return 'Unknown' + return "Offer send" + return "Unknown" def getLockName(lock_type): if lock_type == TxLockTypes.SEQUENCE_LOCK_BLOCKS: - return 'Sequence lock, blocks' + return "Sequence lock, blocks" if lock_type == TxLockTypes.SEQUENCE_LOCK_TIME: - return 'Sequence lock, time' + return "Sequence lock, time" if lock_type == TxLockTypes.ABS_LOCK_BLOCKS: - return 'blocks' + return "blocks" if lock_type == TxLockTypes.ABS_LOCK_TIME: - return 'time' + return "time" def describeEventEntry(event_type, event_msg): if event_type == EventLogTypes.FAILED_TX_B_LOCK_PUBLISH: - return 'Failed to publish lock tx B' + return "Failed to publish lock tx B" if event_type == EventLogTypes.LOCK_TX_A_PUBLISHED: - return 'Lock tx A published' + return "Lock tx A published" if event_type == EventLogTypes.LOCK_TX_B_PUBLISHED: - return 'Lock tx B published' + return "Lock tx B published" if event_type == EventLogTypes.FAILED_TX_B_SPEND: - return 'Failed to publish lock tx B spend: ' + event_msg + return "Failed to publish lock tx B spend: " + event_msg if event_type == EventLogTypes.LOCK_TX_A_SEEN: - return 'Lock tx A seen in chain' + return "Lock tx A seen in chain" if event_type == EventLogTypes.LOCK_TX_A_CONFIRMED: - return 'Lock tx A confirmed in chain' + return "Lock tx A confirmed in chain" if event_type == EventLogTypes.LOCK_TX_B_SEEN: - return 'Lock tx B seen in chain' + return "Lock tx B seen in chain" if event_type == EventLogTypes.LOCK_TX_B_CONFIRMED: - return 'Lock tx B confirmed in chain' + return "Lock tx B confirmed in chain" if event_type == EventLogTypes.LOCK_TX_B_IN_MEMPOOL: - return 'Lock tx B seen in mempool' + return "Lock tx B seen in mempool" if event_type == EventLogTypes.DEBUG_TWEAK_APPLIED: - return 'Debug tweak applied ' + event_msg + return "Debug tweak applied " + event_msg if event_type == EventLogTypes.FAILED_TX_B_REFUND: - return 'Failed to publish lock tx B refund' + return "Failed to publish lock tx B refund" if event_type == EventLogTypes.LOCK_TX_B_INVALID: - return 'Detected invalid lock Tx B' + return "Detected invalid lock Tx B" if event_type == EventLogTypes.LOCK_TX_A_REFUND_TX_PUBLISHED: - return 'Lock tx A refund tx published' + return "Lock tx A refund tx published" if event_type == EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED: - return 'Lock tx A refund spend tx published' + return "Lock tx A refund spend tx published" if event_type == EventLogTypes.LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED: - return 'Lock tx A refund swipe tx published' + return "Lock tx A refund swipe tx published" if event_type == EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED: - return 'Lock tx B refund tx published' + return "Lock tx B refund tx published" if event_type == EventLogTypes.LOCK_TX_A_SPEND_TX_PUBLISHED: - return 'Lock tx A spend tx published' + return "Lock tx A spend tx published" if event_type == EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED: - return 'Lock tx B spend tx published' + return "Lock tx B spend tx published" if event_type == EventLogTypes.LOCK_TX_A_REFUND_TX_SEEN: - return 'Lock tx A refund tx seen in chain' + return "Lock tx A refund tx seen in chain" if event_type == EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_SEEN: - return 'Lock tx A refund spend tx seen in chain' + return "Lock tx A refund spend tx seen in chain" if event_type == EventLogTypes.SYSTEM_WARNING: - return 'Warning: ' + event_msg + return "Warning: " + event_msg if event_type == EventLogTypes.ERROR: - return 'Error: ' + event_msg + return "Error: " + event_msg if event_type == EventLogTypes.AUTOMATION_CONSTRAINT: - return 'Failed auto accepting' + return "Failed auto accepting" if event_type == EventLogTypes.AUTOMATION_ACCEPTING_BID: - return 'Auto accepting' + return "Auto accepting" if event_type == EventLogTypes.ITX_PUBLISHED: - return 'Initiate tx published' + return "Initiate tx published" if event_type == EventLogTypes.ITX_REDEEM_PUBLISHED: - return 'Initiate tx redeem tx published' + return "Initiate tx redeem tx published" if event_type == EventLogTypes.ITX_REFUND_PUBLISHED: - return 'Initiate tx refund tx published' + return "Initiate tx refund tx published" if event_type == EventLogTypes.PTX_PUBLISHED: - return 'Participate tx published' + return "Participate tx published" if event_type == EventLogTypes.PTX_REDEEM_PUBLISHED: - return 'Participate tx redeem tx published' + return "Participate tx redeem tx published" if event_type == EventLogTypes.PTX_REFUND_PUBLISHED: - return 'Participate tx refund tx published' + return "Participate tx refund tx published" if event_type == EventLogTypes.BCH_MERCY_TX_FOUND: - return 'BCH mercy tx found' + return "BCH mercy tx found" if event_type == EventLogTypes.BCH_MERCY_TX_PUBLISHED: - return 'Lock tx B mercy tx published' + return "Lock tx B mercy tx published" def getVoutByAddress(txjs, p2sh): - for o in txjs['vout']: + for o in txjs["vout"]: try: - if 'address' in o['scriptPubKey'] and o['scriptPubKey']['address'] == p2sh: - return o['n'] - if p2sh in o['scriptPubKey']['addresses']: - return o['n'] + if "address" in o["scriptPubKey"] and o["scriptPubKey"]["address"] == p2sh: + return o["n"] + if p2sh in o["scriptPubKey"]["addresses"]: + return o["n"] except Exception: pass - raise ValueError('Address output not found in txn') + raise ValueError("Address output not found in txn") def getVoutByScriptPubKey(txjs, scriptPubKey_hex: str) -> int: - for o in txjs['vout']: + for o in txjs["vout"]: try: - if scriptPubKey_hex == o['scriptPubKey']['hex']: - return o['n'] + if scriptPubKey_hex == o["scriptPubKey"]["hex"]: + return o["n"] except Exception: pass - raise ValueError('scriptPubKey output not found in txn') + raise ValueError("scriptPubKey output not found in txn") -def replaceAddrPrefix(addr, coin_type, chain_name, addr_type='pubkey_address'): - return encodeAddress(bytes((chainparams[coin_type][chain_name][addr_type],)) + decodeAddress(addr)[1:]) +def replaceAddrPrefix(addr, coin_type, chain_name, addr_type="pubkey_address"): + return encodeAddress( + bytes((chainparams[coin_type][chain_name][addr_type],)) + + decodeAddress(addr)[1:] + ) def getOfferProofOfFundsHash(offer_msg, offer_addr): # TODO: Hash must not include proof_of_funds sig if it exists in offer_msg h = hashlib.sha256() - h.update(offer_addr.encode('utf-8')) + h.update(offer_addr.encode("utf-8")) offer_bytes = offer_msg.to_bytes() h.update(offer_bytes) return h.digest() @@ -500,33 +503,40 @@ def getLastBidState(packed_states): num_states = len(packed_states) // 12 if num_states < 2: return BidStates.BID_STATE_UNKNOWN - return struct.unpack_from(' bool: - ''' + """ If the user sets a COIN _RPC_HOST or PORT variable, set manage_daemon for COIN to false. The COIN _MANAGE_DAEMON variables can override this and set manage_daemon directly. If BSX_DOCKER_MODE is active COIN _MANAGE_DAEMON will default to false. - ''' - default_mode: str = 'false' if BSX_DOCKER_MODE else 'true' if BSX_TEST_MODE else 'auto' - manage_daemon: str = os.getenv(prefix + '_MANAGE_DAEMON', default_mode) + """ + default_mode: str = ( + "false" if BSX_DOCKER_MODE else "true" if BSX_TEST_MODE else "auto" + ) + manage_daemon: str = os.getenv(prefix + "_MANAGE_DAEMON", default_mode) - if manage_daemon == 'auto': - host_was_set: bool = prefix + '_RPC_HOST' in os.environ - port_was_set: bool = prefix + '_RPC_PORT' in os.environ + if manage_daemon == "auto": + host_was_set: bool = prefix + "_RPC_HOST" in os.environ + port_was_set: bool = prefix + "_RPC_PORT" in os.environ if host_was_set or port_was_set: return False @@ -305,19 +327,23 @@ def shouldManageDaemon(prefix: str) -> bool: def exitWithError(error_msg: str): - sys.stderr.write('Error: {}, exiting.\n'.format(error_msg)) + sys.stderr.write("Error: {}, exiting.\n".format(error_msg)) sys.exit(1) def setConnectionParameters(timeout: int = 5, allow_set_tor: bool = True): opener = urllib.request.build_opener() - opener.addheaders = [('User-agent', 'Mozilla/5.0')] + 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) + 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 + socket.getaddrinfo = ( + getaddrinfo_tor # Without this accessing .onion links would fail + ) # Set low timeout for urlretrieve connections socket.setdefaulttimeout(timeout) @@ -337,7 +363,7 @@ def getRemoteFileLength(url: str) -> (int, bool): setConnectionParameters() with contextlib.closing(urlopen(url)) as fp: # NOTE: The test here is case insensitive, 'Accept-Ranges' will match - can_resume = 'accept-ranges' in fp.headers + can_resume = "accept-ranges" in fp.headers return fp.length, can_resume finally: popConnectionParameters() @@ -345,22 +371,24 @@ def getRemoteFileLength(url: str) -> (int, bool): def downloadRelease(url: str, path: str, extra_opts, timeout: int = 10) -> None: """If file exists at path compare it's size to the content length at the url - and attempt to resume download if file size is below expected. + and attempt to resume download if file size is below expected. """ resume_from: int = 0 if os.path.exists(path): - if extra_opts.get('redownload_releases', False): - logging.warning(f'Overwriting: {path}') - elif extra_opts.get('verify_release_file_size', True): + if extra_opts.get("redownload_releases", False): + logging.warning(f"Overwriting: {path}") + elif extra_opts.get("verify_release_file_size", True): file_size = os.stat(path).st_size remote_file_length, can_resume = getRemoteFileLength(url) if file_size < remote_file_length: - logger.warning(f'{path} is an unexpected size, {file_size} < {remote_file_length}. Attempting to resume download.') + logger.warning( + f"{path} is an unexpected size, {file_size} < {remote_file_length}. Attempting to resume download." + ) if can_resume: resume_from = file_size else: - logger.warning('Download can not be resumed, restarting.') + logger.warning("Download can not be resumed, restarting.") else: return else: @@ -371,11 +399,13 @@ def downloadRelease(url: str, path: str, extra_opts, timeout: int = 10) -> None: def downloadFile(url: str, path: str, timeout: int = 5, resume_from: int = 0) -> None: - logger.info(f'Downloading file {url}') - logger.info(f'To {path}') + logger.info(f"Downloading file {url}") + logger.info(f"To {path}") try: setConnectionParameters(timeout=timeout) - urlretrieve(url, path, make_reporthook(resume_from, logger), resume_from=resume_from) + urlretrieve( + url, path, make_reporthook(resume_from, logger), resume_from=resume_from + ) finally: popConnectionParameters() @@ -392,35 +422,37 @@ def downloadBytes(url) -> None: def importPubkeyFromUrls(gpg, pubkeyurls): for url in pubkeyurls: try: - logger.info('Importing public key from url: ' + url) + 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') + gpg.trust_keys(key, "TRUST_FULLY") break except Exception as e: - logging.warning('Import from url failed: %s', str(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_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.') + 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.') + 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: + if key["keyid"] == key_id: return True return False @@ -429,14 +461,14 @@ 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}') + logger.info(f"Skipping PIVX params download, path exists: {output_dir}") return os.makedirs(output_dir) - source_url = 'https://download.z.cash/downloads/' + source_url = "https://download.z.cash/downloads/" files = { - 'sapling-spend.params': '8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13', - 'sapling-output.params': '2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4', + "sapling-spend.params": "8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13", + "sapling-output.params": "2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4", } try: @@ -446,58 +478,67 @@ def downloadPIVXParams(output_dir): path = os.path.join(output_dir, k) downloadFile(url, path) hasher = hashlib.sha256() - with open(path, 'rb') as fp: + with open(path, "rb") as fp: hasher.update(fp.read()) file_hash = hasher.hexdigest() - logger.info('%s hash: %s', k, file_hash) + 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'): + 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.') + 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) + 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) + 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', 'wownero'): - if coin in ('monero', 'wownero'): - bins = [coin + 'd', coin + '-wallet-rpc'] - elif coin == 'firo': - bins = [coin + 'd', coin + '-cli', coin + '-tx'] + if coin in ("monero", "firo", "wownero"): + if coin in ("monero", "wownero"): + bins = [coin + "d", coin + "-wallet-rpc"] + elif coin == "firo": + bins = [coin + "d", coin + "-cli", coin + "-tx"] else: - raise ValueError('Unknown coin') + raise ValueError("Unknown coin") - if 'win32' in BIN_ARCH or 'win64' in BIN_ARCH: + 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' + 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: + 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) + 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) + logging.warning( + "Unable to set file permissions: %s, for %s", + str(e), + out_path, + ) break return @@ -507,7 +548,7 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts= 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.') + logger.info("Skipping extract, files exist.") return with tarfile.open(release_path) as ft: @@ -519,273 +560,398 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts= 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: + 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) + logging.warning( + "Unable to set file permissions: %s, for %s", + str(e), + out_path, + ) return - dir_name = 'dashcore' if coin == 'dash' else coin - dir_name = 'bitcoin-cash-node' if coin == 'bitcoincash' else coin - if coin == 'decred': - bins = ['dcrd', 'dcrwallet'] - elif coin == 'bitcoincash': - bins = ['bitcoind', 'bitcoin-cli', 'bitcoin-tx'] - versions = version.split('.') + dir_name = "dashcore" if coin == "dash" else coin + dir_name = "bitcoin-cash-node" if coin == "bitcoincash" else coin + if coin == "decred": + bins = ["dcrd", "dcrwallet"] + elif coin == "bitcoincash": + bins = ["bitcoind", "bitcoin-cli", "bitcoin-tx"] + versions = version.split(".") if int(versions[0]) >= 22 or int(versions[1]) >= 19: - bins.append('bitcoin-wallet') + bins.append("bitcoin-wallet") else: - bins = [coin + 'd', coin + '-cli', coin + '-tx'] - versions = version.split('.') + bins = [coin + "d", coin + "-cli", coin + "-tx"] + versions = version.split(".") if int(versions[0]) >= 22 or int(versions[1]) >= 19: - bins.append(coin + '-wallet') + 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) + 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) + return "{}-{}/bin/{}".format(dir_name, version + version_tag, b) - if 'win32' in BIN_ARCH or 'win64' in BIN_ARCH: + if "win32" in BIN_ARCH or "win64" in BIN_ARCH: with zipfile.ZipFile(release_path) as fz: for b in bins: - b += '.exe' + 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: + 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) + 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: + 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) + 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) + logger.info("prepareCore %s v%s%s", coin, version, version_tag) - bin_dir = os.path.expanduser(settings['chainclients'][coin]['bindir']) + 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' + 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': + 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' + 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' + 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_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) downloadRelease(release_url, release_path, extra_opts) - assert_filename = 'monero-{}-hashes.txt'.format(version) + 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_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 == 'wownero': - 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' + elif coin == "wownero": + 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' - release_url = 'https://git.wownero.com/attachments/280753b0-3af0-4a78-a248-8b925e8f4593' - if 'aarch64' in BIN_ARCH: - architecture = 'armv8' - release_url = 'https://git.wownero.com/attachments/0869ffe3-eeff-4240-a185-168ca80fa1e3' - elif 'arm' in BIN_ARCH: - architecture = 'armv7' # 32bit doesn't work - release_url = 'https://git.wownero.com/attachments/ff0c4886-3865-4670-9bc6-63dd60ded0e3' - elif 'osx64' in BIN_ARCH: - release_url = 'https://git.wownero.com/attachments/7e3fd17c-1bcd-442c-b82d-92a00cccffb8' - elif 'win64' in BIN_ARCH: - release_url = 'https://git.wownero.com/attachments/a1cf8611-1727-4b49-a8e5-1c66fe4f72a3' - elif 'win32' in BIN_ARCH: - release_url = 'https://git.wownero.com/attachments/007d606d-56e0-4c8a-92c1-d0974a781e80' + architecture = "x64" + release_url = ( + "https://git.wownero.com/attachments/280753b0-3af0-4a78-a248-8b925e8f4593" + ) + if "aarch64" in BIN_ARCH: + architecture = "armv8" + release_url = "https://git.wownero.com/attachments/0869ffe3-eeff-4240-a185-168ca80fa1e3" + elif "arm" in BIN_ARCH: + architecture = "armv7" # 32bit doesn't work + release_url = "https://git.wownero.com/attachments/ff0c4886-3865-4670-9bc6-63dd60ded0e3" + elif "osx64" in BIN_ARCH: + release_url = "https://git.wownero.com/attachments/7e3fd17c-1bcd-442c-b82d-92a00cccffb8" + elif "win64" in BIN_ARCH: + release_url = "https://git.wownero.com/attachments/a1cf8611-1727-4b49-a8e5-1c66fe4f72a3" + elif "win32" in BIN_ARCH: + release_url = "https://git.wownero.com/attachments/007d606d-56e0-4c8a-92c1-d0974a781e80" release_path = os.path.join(bin_dir, release_filename) downloadRelease(release_url, release_path, extra_opts) - assert_filename = 'wownero-{}-hashes.txt'.format(version) - assert_url = 'https://git.wownero.com/wownero/wownero.org-website/raw/commit/{}/hashes.txt'.format(WOW_SITE_COMMIT) + assert_filename = "wownero-{}-hashes.txt".format(version) + assert_url = "https://git.wownero.com/wownero/wownero.org-website/raw/commit/{}/hashes.txt".format( + WOW_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': + elif coin == "decred": arch_name = BIN_ARCH - if USE_PLATFORM == 'Darwin': - arch_name = 'darwin-amd64' - elif USE_PLATFORM == 'Windows': - arch_name = 'windows-amd64' + 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' + if "arm" in machine: + arch_name = "linux-arm" else: - arch_name = 'linux-amd64' + 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) + 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) downloadRelease(release_url, release_path, extra_opts) - assert_filename = 'decred-v{}-manifest.txt'.format(version) - assert_url = release_page_url + '/' + assert_filename + 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_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]) + major_version = int(version.split(".")[0]) - use_guix: bool = coin in ('dash', ) or major_version >= 22 + 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' + 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 != '': + 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) + 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) + 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' + 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])) + 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' + 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 == 'bitcoincash': - release_filename = 'bitcoin-cash-node-{}-{}.{}'.format(version, BIN_ARCH, FILE_EXT) - release_url = 'https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v{}/{}'.format(version, release_filename) - assert_filename = 'SHA256SUMS.{}.asc.Calin_Culianu'.format(version) - assert_url = 'https://gitlab.com/bitcoin-cash-node/announcements/-/raw/master/release-sigs/%s/%s' % (version, 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) - sums_name = 'all' if major_version >= 21 else 'codesigned' - assert_url = f'https://raw.githubusercontent.com/dashpay/guix.sigs/master/{version}/{signing_key_name}/{sums_name}.SHA256SUMS' - elif coin == 'firo': + 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 == "bitcoincash": + release_filename = "bitcoin-cash-node-{}-{}.{}".format( + version, BIN_ARCH, FILE_EXT + ) + release_url = "https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v{}/{}".format( + version, release_filename + ) + assert_filename = "SHA256SUMS.{}.asc.Calin_Culianu".format(version) + assert_url = ( + "https://gitlab.com/bitcoin-cash-node/announcements/-/raw/master/release-sigs/%s/%s" + % (version, 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 + ) + sums_name = "all" if major_version >= 21 else "codesigned" + assert_url = f"https://raw.githubusercontent.com/dashpay/guix.sigs/master/{version}/{signing_key_name}/{sums_name}.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) + 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') + raise ValueError("Unknown coin") release_path = os.path.join(bin_dir, release_filename) downloadRelease(release_url, release_path, extra_opts) # Rename assert files with full version - assert_filename = '{}-{}-{}-build-{}.assert'.format(coin, os_name, version, signing_key_name) + 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', 'bitcoincash',): - 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) + if coin not in ( + "firo", + "bitcoincash", + ): + 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: + 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())) + 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.') + 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.') + 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 @@ -796,72 +962,86 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}): """ gpg = gnupg.GPG() - keysdirpath = extra_opts.get('keysdirpath', None) + keysdirpath = extra_opts.get("keysdirpath", None) if keysdirpath is not None: - logger.info(f'Loading PGP keys from: {keysdirpath}.') + logger.info(f"Loading PGP keys from: {keysdirpath}.") for path in os.scandir(keysdirpath): if path.is_file(): - with open(path, 'rb') as fp: + 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') + 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) + 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) + pubkey_filename = "{}_{}.pgp".format(coin, signing_key_name) pubkeyurls = [ - 'https://raw.githubusercontent.com/basicswap/basicswap/master/pgp/keys/' + pubkey_filename, - 'https://gitlab.com/particl/basicswap/-/raw/master/pgp/keys/' + pubkey_filename, + "https://raw.githubusercontent.com/basicswap/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 == 'wownero': - pubkeyurls.append('https://git.wownero.com/wownero/wownero/raw/branch/master/utils/gpg_keys/wowario.asc') - if coin == 'firo': - pubkeyurls.append('https://firo.org/reuben.asc') - if coin == 'bitcoincash': - pubkeyurls.append('https://gitlab.com/bitcoin-cash-node/bitcoin-cash-node/-/raw/master/contrib/gitian-signing/pubkeys.txt') + 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 == "wownero": + pubkeyurls.append( + "https://git.wownero.com/wownero/wownero/raw/branch/master/utils/gpg_keys/wowario.asc" + ) + if coin == "firo": + pubkeyurls.append("https://firo.org/reuben.asc") + if coin == "bitcoincash": + pubkeyurls.append( + "https://gitlab.com/bitcoin-cash-node/bitcoin-cash-node/-/raw/master/contrib/gitian-signing/pubkeys.txt" + ) - if ADD_PUBKEY_URL != '': - pubkeyurls.append(ADD_PUBKEY_URL + '/' + pubkey_filename) + if ADD_PUBKEY_URL != "": + pubkeyurls.append(ADD_PUBKEY_URL + "/" + pubkey_filename) - if coin in ('monero', 'wownero', 'firo', 'bitcoincash',): - with open(assert_path, 'rb') as fp: + if coin in ( + "monero", + "wownero", + "firo", + "bitcoincash", + ): + 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.') + logger.warning("Signature made by unknown key.") importPubkeyFromUrls(gpg, pubkeyurls) - with open(assert_path, 'rb') as fp: + with open(assert_path, "rb") as fp: verified = gpg.verify_file(fp) - elif coin in ('navcoin'): - with open(assert_sig_path, 'rb') as 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.') + logger.warning("Signature made by unknown key.") importPubkeyFromUrls(gpg, pubkeyurls) - with open(assert_sig_path, 'rb') as fp: + 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: + 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)) + assert release_hash.hex() in str(decrypted) else: - with open(assert_sig_path, 'rb') as fp: + 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.') + logger.warning("Signature made by unknown key.") importPubkeyFromUrls(gpg, pubkeyurls) - with open(assert_sig_path, 'rb') as fp: + with open(assert_sig_path, "rb") as fp: verified = gpg.verify_file(fp, assert_path) ensureValidSignatureBy(verified, signing_key_name) @@ -869,246 +1049,323 @@ def prepareCore(coin, version_data, settings, data_dir, 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',): + """ + 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') + 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') + 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') + 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) + core_settings = settings["chainclients"][coin] + 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 in ('wownero', 'monero'): - conf_filename: str = core_settings.get('config_filename', coin + 'd.conf') + if coin in ("wownero", "monero"): + conf_filename: str = core_settings.get("config_filename", coin + "d.conf") core_conf_path = os.path.join(data_dir, conf_filename) 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') + 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') + 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 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 coin == 'monero': - if XMR_RPC_USER != '': - fp.write(f'rpc-login={XMR_RPC_USER}:{XMR_RPC_PWD}\n') + if coin == "monero": + if XMR_RPC_USER != "": + fp.write(f"rpc-login={XMR_RPC_USER}:{XMR_RPC_PWD}\n") if tor_control_password is not None: for opt_line in monerod_proxy_config: - fp.write(opt_line + '\n') + fp.write(opt_line + "\n") - if coin == 'wownero': - if WOW_RPC_USER != '': - fp.write(f'rpc-login={WOW_RPC_USER}:{WOW_RPC_PWD}\n') + if coin == "wownero": + if WOW_RPC_USER != "": + fp.write(f"rpc-login={WOW_RPC_USER}:{WOW_RPC_PWD}\n") if tor_control_password is not None: for opt_line in wownerod_proxy_config: - fp.write(opt_line + '\n') + fp.write(opt_line + "\n") - wallets_dir = core_settings.get('walletsdir', data_dir) + wallets_dir = core_settings.get("walletsdir", data_dir) if not os.path.exists(wallets_dir): os.makedirs(wallets_dir) - wallet_conf_filename: str = core_settings.get('wallet_config_filename', 'monero_wallet.conf' if coin == 'monero' else (coin + '-wallet-rpc.conf')) + wallet_conf_filename: str = core_settings.get( + "wallet_config_filename", + "monero_wallet.conf" if coin == "monero" else (coin + "-wallet-rpc.conf"), + ) wallet_conf_path = os.path.join(wallets_dir, wallet_conf_filename) 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' + 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('rpc-login={}:{}\n'.format(core_settings['walletrpcuser'], core_settings['walletrpcpassword'])) - if coin == 'monero': - fp.write('shared-ringdb-dir={}\n'.format(os.path.join(config_datadir, 'shared-ringdb'))) - elif coin == 'wownero': - fp.write('wow-shared-ringdb-dir={}\n'.format(os.path.join(config_datadir, 'shared-ringdb'))) - if chain == 'regtest': - fp.write('allow-mismatched-daemon-version=1\n') + 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( + "rpc-login={}:{}\n".format( + core_settings["walletrpcuser"], core_settings["walletrpcpassword"] + ) + ) + if coin == "monero": + fp.write( + "shared-ringdb-dir={}\n".format( + os.path.join(config_datadir, "shared-ringdb") + ) + ) + elif coin == "wownero": + fp.write( + "wow-shared-ringdb-dir={}\n".format( + os.path.join(config_datadir, "shared-ringdb") + ) + ) + if chain == "regtest": + fp.write("allow-mismatched-daemon-version=1\n") if tor_control_password is not None: - if not core_settings['manage_daemon']: + if not core_settings["manage_daemon"]: for opt_line in monero_wallet_rpc_proxy_config: - fp.write(opt_line + '\n') + fp.write(opt_line + "\n") return - if coin == 'decred': - chainname = 'simnet' if chain == 'regtest' else chain - conf_filename: str = core_settings.get('config_filename', 'dcrd.conf') + if coin == "decred": + chainname = "simnet" if chain == "regtest" else chain + conf_filename: str = core_settings.get("config_filename", "dcrd.conf") core_conf_path = os.path.join(data_dir, conf_filename) 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=info\n') - fp.write('notls=1\n') + 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=info\n") + fp.write("notls=1\n") - fp.write('rpclisten={}:{}\n'.format(core_settings['rpchost'], core_settings['rpcport'])) + 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'])) + 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_filename: str = core_settings.get('wallet_config_filename', 'dcrwallet.conf') + wallet_conf_filename: str = core_settings.get( + "wallet_config_filename", "dcrwallet.conf" + ) wallet_conf_path = os.path.join(data_dir, wallet_conf_filename) 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=info\n') - fp.write('noservertls=1\n') - fp.write('noclienttls=1\n') + 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=info\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( + "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'])) + fp.write("username={}\n".format(core_settings["rpcuser"])) + fp.write("password={}\n".format(core_settings["rpcpassword"])) return - core_conf_name: str = core_settings.get('config_filename', coin + '.conf') + core_conf_name: str = core_settings.get("config_filename", coin + ".conf") core_conf_path = os.path.join(data_dir, core_conf_name) 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') + 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') + 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) + logger.warning("Unknown chain %s", chain) - if COINS_RPCBIND_IP != '127.0.0.1': - fp.write('rpcallowip=127.0.0.1\n') + if COINS_RPCBIND_IP != "127.0.0.1": + fp.write("rpcallowip=127.0.0.1\n") if BSX_DOCKER_MODE: - 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( + "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') + 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 == 'bitcoincash': - fp.write('prune=4000\n') - fp.write('pid=bitcoincashd.pid\n') - if BCH_RPC_USER != '': - fp.write('rpcauth={}:{}${}\n'.format(BCH_RPC_USER, salt, password_to_hmac(salt, BCH_RPC_PWD))) - elif coin == 'namecoin': - fp.write('prune=2000\n') - elif coin == 'pivx': - params_dir = os.path.join(data_dir, 'pivx-params') + 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 == "bitcoincash": + fp.write("prune=4000\n") + fp.write("pid=bitcoincashd.pid\n") + if BCH_RPC_USER != "": + fp.write( + "rpcauth={}:{}${}\n".format( + BCH_RPC_USER, salt, password_to_hmac(salt, BCH_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))) + 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) + 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'] + 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'): + for dirname in ("blocks", "chainstate"): if os.path.exists(os.path.join(data_dir, dirname)): - raise ValueError(f'{dirname} directory already exists, not overwriting.') + 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}') + raise ValueError(f"BTC fastsync file not found: {sync_file_path}") # Double check - if extra_opts.get('check_btc_fastsync', True): + 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: @@ -1116,123 +1373,134 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}): def write_torrc(data_dir, tor_control_password): - tor_dir = os.path.join(data_dir, 'tor') + 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') + 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') + 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 + 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'] +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 in ('monero', 'wownero'): - core_conf_name: str = coin_settings.get('config_filename', coin + 'd.conf') + if coin in ("monero", "wownero"): + core_conf_name: str = coin_settings.get("config_filename", coin + "d.conf") core_conf_path = os.path.join(data_dir, core_conf_name) if not os.path.exists(core_conf_path): - exitWithError('{} does not exist'.format(core_conf_path)) + exitWithError("{} does not exist".format(core_conf_path)) - wallets_dir = coin_settings.get('walletsdir', data_dir) - wallet_conf_filename: str = coin_settings.get('wallet_config_filename', 'monero_wallet.conf' if coin == 'monero' else (coin + '-wallet-rpc.conf')) + wallets_dir = coin_settings.get("walletsdir", data_dir) + wallet_conf_filename: str = coin_settings.get( + "wallet_config_filename", + "monero_wallet.conf" if coin == "monero" else (coin + "-wallet-rpc.conf"), + ) wallet_conf_path = os.path.join(wallets_dir, wallet_conf_filename) if not os.path.exists(wallet_conf_path): - exitWithError('{} does not exist'.format(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') + 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: + 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 - if coin == 'monero': + if coin == "monero": for opt_line in monerod_proxy_config: - setting: str = opt_line[0: opt_line.find('=') + 1] + setting: str = opt_line[0 : opt_line.find("=") + 1] if line.startswith(setting): skip_line = True break - if coin == 'wownero': + if coin == "wownero": for opt_line in wownerod_proxy_config: - setting: str = opt_line[0: opt_line.find('=') + 1] + 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 coin == 'monero': + if coin == "monero": for opt_line in monerod_proxy_config: - fp.write(opt_line + '\n') - if coin == 'wownero': + fp.write(opt_line + "\n") + if coin == "wownero": for opt_line in wownerod_proxy_config: - fp.write(opt_line + '\n') + fp.write(opt_line + "\n") - with open(wallet_conf_path, 'w') as fp: - with open(wallet_conf_path + '.last') as fp_in: + 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] + 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']: + if not coin_settings["manage_daemon"]: for opt_line in monero_wallet_rpc_proxy_config: - fp.write(opt_line + '\n') + fp.write(opt_line + "\n") - coin_settings['trusted_daemon'] = extra_opts.get('trust_remote_node', 'auto') + coin_settings["trusted_daemon"] = extra_opts.get( + "trust_remote_node", "auto" + ) return - core_conf_name: str = coin_settings.get('config_filename', 'dcrd.conf' if coin == 'decred' else (coin + '.conf')) + core_conf_name: str = coin_settings.get( + "config_filename", "dcrd.conf" if coin == "decred" else (coin + ".conf") + ) config_path = os.path.join(data_dir, core_conf_name) if not os.path.exists(config_path): - exitWithError('{} does not exist'.format(config_path)) + exitWithError("{} does not exist".format(config_path)) - if 'onionport' not in coin_settings: + if "onionport" not in coin_settings: default_onionport = 0 - if coin == 'bitcoin': + if coin == "bitcoin": default_onionport = BTC_ONION_PORT - if coin == 'bitcoincash': + if coin == "bitcoincash": default_onionport = BCH_ONION_PORT - elif coin == 'particl': + elif coin == "particl": default_onionport = PART_ONION_PORT - elif coin == 'litecoin': + elif coin == "litecoin": default_onionport = LTC_ONION_PORT - elif coin in ('decred',): + elif coin in ("decred",): pass else: - exitWithError('Unknown default onion listening port for {}'.format(coin)) + exitWithError("Unknown default onion listening port for {}".format(coin)) if default_onionport > 0: - coin_settings['onionport'] = default_onionport + coin_settings["onionport"] = default_onionport # Backup - shutil.copyfile(config_path, config_path + '.last') + 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: + 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 @@ -1247,67 +1515,103 @@ def modify_tor_config(settings, coin, tor_control_password=None, enable=False, e def printVersion(): - logger.info(f'Basicswap version: {__version__}') + logger.info(f"Basicswap version: {__version__}") - logger.info('Core versions:') + 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) + 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('--wowrestoreheight=n Block height to restore Wownero wallet from, default:{}.'.format(DEFAULT_WOW_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.') - print('--noreleasesizecheck If unset the size of existing core release files will be compared to their size at their download url.') - print('--redownloadreleases If set core release files will be redownloaded.') - print('--dashv20compatible Generate the same DASH wallet seed as for DASH v20 - Use only when importing an existing seed.') + 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( + "--wowrestoreheight=n Block height to restore Wownero wallet from, default:{}.".format( + DEFAULT_WOW_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." + ) + print( + "--noreleasesizecheck If unset the size of existing core release files will be compared to their size at their download url." + ) + print("--redownloadreleases If set core release files will be redownloaded.") + print( + "--dashv20compatible Generate the same DASH wallet seed as for DASH v20 - Use only when importing an existing seed." + ) 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))) + print("\n" + "Known coins: {}".format(", ".join(active_coins))) def finalise_daemon(d): - logging.info('Interrupting {}'.format(d.handle.pid)) + 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.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}') + 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() @@ -1316,30 +1620,42 @@ def finalise_daemon(d): 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: + 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) + 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') + daemon_args.append("-nolisten") c = Coins.PART - coin_name = 'particl' - coin_settings = settings['chainclients'][coin_name] + coin_name = "particl" + coin_settings = settings["chainclients"][coin_name] daemon_args += getCoreBinArgs(c, coin_settings) - extra_config = {'stdout_to_file': True} - if coin_settings['manage_daemon']: - filename: str = getCoreBinName(c, coin_settings, coin_name + 'd') - daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args, extra_config=extra_config)) + extra_config = {"stdout_to_file": True} + if coin_settings["manage_daemon"]: + filename: str = getCoreBinName(c, coin_settings, coin_name + "d") + daemons.append( + startDaemon( + coin_settings["datadir"], + coin_settings["bindir"], + filename, + daemon_args, + extra_config=extra_config, + ) + ) 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') + 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: @@ -1351,120 +1667,199 @@ def test_particl_encryption(data_dir, settings, chain, use_tor_proxy): def encrypt_wallet(swap_client, coin_type) -> None: ci = swap_client.ci(coin_type) - ci.changeWalletPassword('', WALLET_ENCRYPTION_PWD) + 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): +def initialise_wallets( + particl_wallet_mnemonic, with_coins, data_dir, settings, chain, use_tor_proxy +): swap_client = None daemons = [] - daemon_args = ['-noconnect', '-nodnsseed'] + daemon_args = ["-noconnect", "-nodnsseed"] generated_mnemonic: bool = False coins_failed_to_initialise = [] - with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp: + with open(os.path.join(data_dir, "basicswap.log"), "a") as fp: try: - swap_client = BasicSwap(fp, data_dir, settings, chain, transient_instance=True) + 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.DCR, Coins.DASH) + daemon_args.append("-nolisten") + coins_to_create_wallets_for = ( + Coins.PART, + Coins.BTC, + Coins.LTC, + Coins.DCR, + 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'] + start_daemons = [ + "particl", + ] + [c for c in with_coins if c != "particl"] for coin_name in start_daemons: - coin_settings = settings['chainclients'][coin_name] + coin_settings = settings["chainclients"][coin_name] c = swap_client.getCoinIdFromName(coin_name) if c == Coins.XMR: - if coin_settings['manage_wallet_daemon']: - filename = coin_name + '-wallet-rpc' + ('.exe' if os.name == 'nt' else '') - filename: str = getWalletBinName(c, coin_settings, coin_name + '-wallet-rpc') - daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], filename)) + if coin_settings["manage_wallet_daemon"]: + filename = ( + coin_name + + "-wallet-rpc" + + (".exe" if os.name == "nt" else "") + ) + filename: str = getWalletBinName( + c, coin_settings, coin_name + "-wallet-rpc" + ) + daemons.append( + startXmrWalletDaemon( + coin_settings["datadir"], + coin_settings["bindir"], + filename, + ) + ) elif c == Coins.WOW: - if coin_settings['manage_wallet_daemon']: - filename: str = getWalletBinName(c, coin_settings, coin_name + '-wallet-rpc') - daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], filename)) + if coin_settings["manage_wallet_daemon"]: + filename: str = getWalletBinName( + c, coin_settings, coin_name + "-wallet-rpc" + ) + daemons.append( + startXmrWalletDaemon( + coin_settings["datadir"], + coin_settings["bindir"], + filename, + ) + ) elif c == Coins.DCR: pass else: - if coin_settings['manage_daemon']: - filename: str = getCoreBinName(c, coin_settings, coin_name + 'd') - coin_args = ['-nofindpeers', '-nostaking'] if c == Coins.PART else [] + if coin_settings["manage_daemon"]: + filename: str = getCoreBinName( + c, coin_settings, coin_name + "d" + ) + coin_args = ( + ["-nofindpeers", "-nostaking"] if c == Coins.PART else [] + ) coin_args += getCoreBinArgs(c, coin_settings) if c == Coins.FIRO: - coin_args += ['-hdseed={}'.format(swap_client.getWalletKey(Coins.FIRO, 1).hex())] + coin_args += [ + "-hdseed={}".format( + swap_client.getWalletKey(Coins.FIRO, 1).hex() + ) + ] - extra_config = {'stdout_to_file': True} - daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args, extra_config=extra_config)) + extra_config = {"stdout_to_file": True} + daemons.append( + startDaemon( + coin_settings["datadir"], + coin_settings["bindir"], + filename, + daemon_args + coin_args, + extra_config=extra_config, + ) + ) swap_client.setDaemonPID(c, daemons[-1].handle.pid) swap_client.setCoinRunParams(c) swap_client.createCoinInterface(c) if c in coins_to_create_wallets_for: if c == Coins.DCR: - if coin_settings['manage_wallet_daemon'] is False: + if coin_settings["manage_wallet_daemon"] is False: continue 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), - ] + 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: str = getWalletBinName(c, coin_settings, 'dcrwallet') - args = [os.path.join(coin_settings['bindir'], filename), '--create'] + extra_opts + filename: str = getWalletBinName(c, coin_settings, "dcrwallet") + 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()) continue swap_client.waitForDaemonRPC(c, with_wallet=False) # Create wallet if it doesn't exist yet - wallets = swap_client.callcoinrpc(c, 'listwallets') + wallets = swap_client.callcoinrpc(c, "listwallets") if len(wallets) < 1: - logger.info('Creating wallet.dat for {}.'.format(getCoinName(c))) + logger.info( + "Creating wallet.dat for {}.".format(getCoinName(c)) + ) if c in (Coins.BTC, Coins.LTC, Coins.DASH): # 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.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 != '': + 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 + 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" 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'] + particl_wallet_mnemonic = swap_client.callcoinrpc( + Coins.PART, "mnemonic", ["new"] + )["mnemonic"] generated_mnemonic = True - swap_client.callcoinrpc(Coins.PART, 'extkeyimportmaster', [particl_wallet_mnemonic]) + swap_client.callcoinrpc( + Coins.PART, "extkeyimportmaster", [particl_wallet_mnemonic] + ) # Particl wallet must be unlocked to call getWalletKey - if WALLET_ENCRYPTION_PWD != '': + 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, ): + if c in (Coins.PART,): continue - if c not in (Coins.DCR, ): + 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: + 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}.') + swap_client.ci(c).changeWalletPassword( + "", WALLET_ENCRYPTION_PWD + ) + except Exception as e: # noqa: F841 + logger.warning(f"changeWalletPassword failed for {coin_name}.") finally: if swap_client: @@ -1473,69 +1868,77 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, for d in daemons: finalise_daemon(d) - print('') + 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.') + 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}') + 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 "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)) + 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)) + 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)) + logger.info("Signal %d detected" % (sig)) def check_btc_fastsync_data(base_dir, sync_file_path): - github_pgp_url = 'https://raw.githubusercontent.com/basicswap/basicswap/master/pgp' - gitlab_pgp_url = 'https://gitlab.com/particl/basicswap/-/raw/master/pgp' - asc_filename = BITCOIN_FASTSYNC_FILE + '.asc' + github_pgp_url = "https://raw.githubusercontent.com/basicswap/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, + 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)) + logging.warning("Download failed: %s", str(e)) gpg = gnupg.GPG() - pubkey_filename = '{}_{}.pgp'.format('particl', 'tecnovert') + pubkey_filename = "{}_{}.pgp".format("particl", "tecnovert") pubkeyurls = [ - github_pgp_url + '/keys/' + pubkey_filename, - gitlab_pgp_url + '/keys/' + pubkey_filename, + github_pgp_url + "/keys/" + pubkey_filename, + gitlab_pgp_url + "/keys/" + pubkey_filename, ] - if not havePubkey(gpg, expected_key_ids['tecnovert'][0]): + if not havePubkey(gpg, expected_key_ids["tecnovert"][0]): importPubkeyFromUrls(gpg, pubkeyurls) - with open(asc_file_path, 'rb') as fp: + with open(asc_file_path, "rb") as fp: verified = gpg.verify_file(fp, sync_file_path) - ensureValidSignatureBy(verified, 'tecnovert') + 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()}') + 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') + exitWithError(f"{coin.capitalize()} is disabled") def main(): @@ -1543,13 +1946,15 @@ def main(): data_dir = None bin_dir = None port_offset = None - chain = 'mainnet' + chain = "mainnet" particl_wallet_mnemonic = None - with_coins = {'particl', } - add_coin = '' - disable_coin = '' + with_coins = { + "particl", + } + add_coin = "" + disable_coin = "" coins_changed = False - htmlhost = '127.0.0.1' + htmlhost = "127.0.0.1" xmr_restore_height = DEFAULT_XMR_RESTORE_HEIGHT wow_restore_height = DEFAULT_WOW_RESTORE_HEIGHT prepare_bin_only = False @@ -1560,169 +1965,180 @@ def main(): 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.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': + 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)) + if len(v) < 2 or v[0] != "-": + exitWithError("Unknown argument {}".format(v)) - s = v.split('=') + s = v.split("=") name = s[0].strip() for i in range(2): - if name[0] == '-': + if name[0] == "-": name = name[1:] - if name == 'v' or name == 'version': + if name == "v" or name == "version": printVersion() return 0 - if name == 'h' or name == 'help': + if name == "h" or name == "help": printHelp() return 0 - if name in ('mainnet', 'testnet', 'regtest'): + if name in ("mainnet", "testnet", "regtest"): chain = name continue - if name == 'preparebinonly': + if name == "preparebinonly": prepare_bin_only = True continue - if name == 'nocores': + if name == "nocores": no_cores = True continue - if name == 'usecontainers': - extra_opts['use_containers'] = True + if name == "usecontainers": + extra_opts["use_containers"] = True continue - if name == 'noextractover': - extra_opts['extract_core_overwrite'] = False + if name == "noextractover": + extra_opts["extract_core_overwrite"] = False continue - if name == 'usetorproxy': + if name == "usetorproxy": use_tor_proxy = True continue - if name == 'notorproxy': - extra_opts['no_tor_proxy'] = True + if name == "notorproxy": + extra_opts["no_tor_proxy"] = True continue - if name == 'enabletor': + if name == "enabletor": enable_tor = True continue - if name == 'disabletor': + if name == "disabletor": disable_tor = True continue - if name == 'usebtcfastsync': - extra_opts['use_btc_fastsync'] = True + if name == "usebtcfastsync": + extra_opts["use_btc_fastsync"] = True continue - if name == 'skipbtcfastsyncchecks': - extra_opts['check_btc_fastsync'] = False + if name == "skipbtcfastsyncchecks": + extra_opts["check_btc_fastsync"] = False continue - if name == 'trustremotenode': - extra_opts['trust_remote_node'] = True + if name == "trustremotenode": + extra_opts["trust_remote_node"] = True continue - if name == 'noreleasesizecheck': - extra_opts['verify_release_file_size'] = False + if name == "noreleasesizecheck": + extra_opts["verify_release_file_size"] = False continue - if name == 'redownloadreleases': - extra_opts['redownload_releases'] = True + if name == "redownloadreleases": + extra_opts["redownload_releases"] = True continue - if name == 'initwalletsonly': + if name == "initwalletsonly": initwalletsonly = True continue - if name == 'dashv20compatible': - extra_opts['dash_v20_compatible'] = True + if name == "dashv20compatible": + extra_opts["dash_v20_compatible"] = True continue if len(s) == 2: - if name == 'datadir': + if name == "datadir": data_dir = os.path.expanduser(s[1].strip('"')) continue - if name == 'bindir': + if name == "bindir": bin_dir = os.path.expanduser(s[1].strip('"')) continue - if name == 'portoffset': + if name == "portoffset": port_offset = int(s[1]) continue - if name == 'particl_mnemonic': + if name == "particl_mnemonic": particl_wallet_mnemonic = s[1].strip('"') continue - if name in ('withcoin', 'withcoins'): - for coin in [s.strip().lower() for s in s[1].split(',')]: + if name in ("withcoin", "withcoins"): + for coin in [s.strip().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.strip().lower() for s in s[1].split(',')]: + if name in ("withoutcoin", "withoutcoins"): + for coin in [s.strip().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': + if name == "addcoin": add_coin = s[1].strip().lower() ensure_coin_valid(add_coin) - with_coins = {add_coin, } + with_coins = { + add_coin, + } continue - if name == 'disablecoin': + if name == "disablecoin": disable_coin = s[1].strip().lower() ensure_coin_valid(disable_coin, test_disabled=False) continue - if name == 'htmlhost': + if name == "htmlhost": htmlhost = s[1].strip('"') continue - if name == 'wshost': - extra_opts['wshost'] = s[1].strip('"') + if name == "wshost": + extra_opts["wshost"] = s[1].strip('"') continue - if name == 'xmrrestoreheight': + if name == "xmrrestoreheight": xmr_restore_height = int(s[1]) continue - if name == 'wowrestoreheight': + if name == "wowrestoreheight": wow_restore_height = int(s[1]) continue - if name == 'keysdirpath': - extra_opts['keysdirpath'] = os.path.expanduser(s[1].strip('"')) + if name == "keysdirpath": + extra_opts["keysdirpath"] = os.path.expanduser(s[1].strip('"')) continue - if name == 'trustremotenode': - extra_opts['trust_remote_node'] = toBool(s[1]) + if name == "trustremotenode": + extra_opts["trust_remote_node"] = toBool(s[1]) continue - exitWithError('Unknown argument {}'.format(v)) + 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') + 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 '')) + 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 + 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') + 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): + 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) + 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): + 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') + logger.warning( + "Not automatically setting --usetorproxy as --notorproxy is set" + ) else: use_tor_proxy = True - logger.info('Automatically setting --usetorproxy') + logger.info("Automatically setting --usetorproxy") setConnectionParameters(allow_set_tor=False) @@ -1733,18 +2149,18 @@ def main(): 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': + 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.') + logger.warning("Ignoring usebtcfastsync option without Bitcoin selected.") if should_download_btc_fastsync: - logger.info(f'Preparing BTC Fastsync file {BITCOIN_FASTSYNC_FILE}') + 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_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) @@ -1753,405 +2169,451 @@ def main(): file_size = os.stat(sync_file_path).st_size remote_file_length, can_resume = getRemoteFileLength(sync_file_url) if file_size < remote_file_length: - logger.warning(f'{BITCOIN_FASTSYNC_FILE} is an unexpected size, {file_size} < {remote_file_length}') + logger.warning( + f"{BITCOIN_FASTSYNC_FILE} is an unexpected size, {file_size} < {remote_file_length}" + ) if not can_resume: - logger.warning(f'{BITCOIN_FASTSYNC_URL} can not be resumed, restarting download.') + logger.warning( + f"{BITCOIN_FASTSYNC_URL} can not be resumed, restarting download." + ) file_size = 0 - downloadFile(sync_file_url, sync_file_path, timeout=50, resume_from=file_size) + 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}') + 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': shouldManageDaemon('PART'), - '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, + "particl": { + "connection_type": "rpc", + "manage_daemon": shouldManageDaemon("PART"), + "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', - 'manage_daemon': shouldManageDaemon('BTC'), - '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, + "bitcoin": { + "connection_type": "rpc", + "manage_daemon": shouldManageDaemon("BTC"), + "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, }, - 'bitcoincash': { - 'connection_type': 'rpc', - 'manage_daemon': shouldManageDaemon('BCH'), - 'rpchost': BCH_RPC_HOST, - 'rpcport': BCH_RPC_PORT + port_offset, - 'onionport': BCH_ONION_PORT + port_offset, - 'datadir': os.getenv('BCH_DATA_DIR', os.path.join(data_dir, 'bitcoincash')), - 'bindir': os.path.join(bin_dir, 'bitcoincash'), - 'port': BCH_PORT + port_offset, - 'config_filename': 'bitcoin.conf', - 'use_segwit': False, - 'blocks_confirmed': 1, - 'conf_target': 2, - 'core_version_group': 22, + "bitcoincash": { + "connection_type": "rpc", + "manage_daemon": shouldManageDaemon("BCH"), + "rpchost": BCH_RPC_HOST, + "rpcport": BCH_RPC_PORT + port_offset, + "onionport": BCH_ONION_PORT + port_offset, + "datadir": os.getenv("BCH_DATA_DIR", os.path.join(data_dir, "bitcoincash")), + "bindir": os.path.join(bin_dir, "bitcoincash"), + "port": BCH_PORT + port_offset, + "config_filename": "bitcoin.conf", + "use_segwit": False, + "blocks_confirmed": 1, + "conf_target": 2, + "core_version_group": 22, }, - 'litecoin': { - 'connection_type': 'rpc', - 'manage_daemon': shouldManageDaemon('LTC'), - '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, + "litecoin": { + "connection_type": "rpc", + "manage_daemon": shouldManageDaemon("LTC"), + "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', - 'manage_daemon': shouldManageDaemon('DCR'), - 'manage_wallet_daemon': shouldManageDaemon('DCR_WALLET'), - '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', - 'config_filename': 'dcrd.conf', - 'min_relay_fee': 0.00001, + "decred": { + "connection_type": "rpc", + "manage_daemon": shouldManageDaemon("DCR"), + "manage_wallet_daemon": shouldManageDaemon("DCR_WALLET"), + "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", + "config_filename": "dcrd.conf", + "min_relay_fee": 0.00001, }, - 'namecoin': { - 'connection_type': 'rpc', - 'manage_daemon': shouldManageDaemon('NMC'), - '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', + "namecoin": { + "connection_type": "rpc", + "manage_daemon": shouldManageDaemon("NMC"), + "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', - 'manage_daemon': shouldManageDaemon('XMR'), - 'manage_wallet_daemon': shouldManageDaemon('XMR_WALLET'), - 'rpcport': XMR_RPC_PORT + port_offset, - 'zmqport': XMR_ZMQ_PORT + port_offset, - 'walletrpcport': XMR_WALLET_RPC_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, - 'wallet_config_filename': 'monero_wallet.conf', - 'core_type_group': 'xmr', + "monero": { + "connection_type": "rpc", + "manage_daemon": shouldManageDaemon("XMR"), + "manage_wallet_daemon": shouldManageDaemon("XMR_WALLET"), + "rpcport": XMR_RPC_PORT + port_offset, + "zmqport": XMR_ZMQ_PORT + port_offset, + "walletrpcport": XMR_WALLET_RPC_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, + "wallet_config_filename": "monero_wallet.conf", + "core_type_group": "xmr", }, - 'pivx': { - 'connection_type': 'rpc', - 'manage_daemon': shouldManageDaemon('PIVX'), - '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, + "pivx": { + "connection_type": "rpc", + "manage_daemon": shouldManageDaemon("PIVX"), + "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', - 'manage_daemon': shouldManageDaemon('DASH'), - '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, + "dash": { + "connection_type": "rpc", + "manage_daemon": shouldManageDaemon("DASH"), + "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', - 'manage_daemon': shouldManageDaemon('FIRO'), - '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, + "firo": { + "connection_type": "rpc", + "manage_daemon": shouldManageDaemon("FIRO"), + "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', - 'manage_daemon': shouldManageDaemon('NAV'), - '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, + "navcoin": { + "connection_type": "rpc", + "manage_daemon": shouldManageDaemon("NAV"), + "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, + }, + "wownero": { + "connection_type": "rpc", + "manage_daemon": shouldManageDaemon("WOW"), + "manage_wallet_daemon": shouldManageDaemon("WOW_WALLET"), + "rpcport": WOW_RPC_PORT + port_offset, + "zmqport": WOW_ZMQ_PORT + port_offset, + "walletrpcport": WOW_WALLET_RPC_PORT + port_offset, + "rpchost": WOW_RPC_HOST, + "trusted_daemon": extra_opts.get("trust_remote_node", "auto"), + "walletrpchost": WOW_WALLET_RPC_HOST, + "walletrpcuser": WOW_WALLET_RPC_USER, + "walletrpcpassword": WOW_WALLET_RPC_PWD, + "walletfile": "swap_wallet", + "datadir": os.getenv("WOW_DATA_DIR", os.path.join(data_dir, "wownero")), + "bindir": os.path.join(bin_dir, "wownero"), + "restore_height": wow_restore_height, + "blocks_confirmed": 2, + "rpctimeout": 60, + "walletrpctimeout": 120, + "walletrpctimeoutlong": 300, + "core_type_group": "xmr", }, - 'wownero': { - 'connection_type': 'rpc', - 'manage_daemon': shouldManageDaemon('WOW'), - 'manage_wallet_daemon': shouldManageDaemon('WOW_WALLET'), - 'rpcport': WOW_RPC_PORT + port_offset, - 'zmqport': WOW_ZMQ_PORT + port_offset, - 'walletrpcport': WOW_WALLET_RPC_PORT + port_offset, - 'rpchost': WOW_RPC_HOST, - 'trusted_daemon': extra_opts.get('trust_remote_node', 'auto'), - 'walletrpchost': WOW_WALLET_RPC_HOST, - 'walletrpcuser': WOW_WALLET_RPC_USER, - 'walletrpcpassword': WOW_WALLET_RPC_PWD, - 'walletfile': 'swap_wallet', - 'datadir': os.getenv('WOW_DATA_DIR', os.path.join(data_dir, 'wownero')), - 'bindir': os.path.join(bin_dir, 'wownero'), - 'restore_height': wow_restore_height, - 'blocks_confirmed': 2, - 'rpctimeout': 60, - 'walletrpctimeout': 120, - 'walletrpctimeoutlong': 300, - 'core_type_group': 'xmr', - } } - 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 BCH_RPC_USER != '': - chainclients['bitcoincash']['rpcuser'] = BCH_RPC_USER - chainclients['bitcoincash']['rpcpassword'] = BCH_RPC_PWD - if XMR_RPC_USER != '': - chainclients['monero']['rpcuser'] = XMR_RPC_USER - chainclients['monero']['rpcpassword'] = XMR_RPC_PWD - if WOW_RPC_USER != '': - chainclients['wownero']['rpcuser'] = WOW_RPC_USER - chainclients['wownero']['rpcpassword'] = WOW_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 + 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 BCH_RPC_USER != "": + chainclients["bitcoincash"]["rpcuser"] = BCH_RPC_USER + chainclients["bitcoincash"]["rpcpassword"] = BCH_RPC_PWD + if XMR_RPC_USER != "": + chainclients["monero"]["rpcuser"] = XMR_RPC_USER + chainclients["monero"]["rpcpassword"] = XMR_RPC_PWD + if WOW_RPC_USER != "": + chainclients["wownero"]["rpcuser"] = WOW_RPC_USER + chainclients["wownero"]["rpcpassword"] = WOW_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']) - chainclients['wownero']['walletsdir'] = os.getenv('WOW_WALLETS_DIR', chainclients['wownero']['datadir']) + chainclients["monero"]["walletsdir"] = os.getenv( + "XMR_WALLETS_DIR", chainclients["monero"]["datadir"] + ) + chainclients["wownero"]["walletsdir"] = os.getenv( + "WOW_WALLETS_DIR", chainclients["wownero"]["datadir"] + ) - if extra_opts.get('dash_v20_compatible', False): - chainclients['dash']['wallet_v20_compatible'] = True + if extra_opts.get("dash_v20_compatible", False): + chainclients["dash"]["wallet_v20_compatible"] = True if initwalletsonly: - logger.info('Initialising wallets') + logger.info("Initialising wallets") settings = load_config(config_path) - init_coins = settings['chainclients'].keys() - logger.info('Active coins: %s', ', '.join(init_coins)) + 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) + logger.info("Initialising coins: %s", ", ".join(init_coins)) + initialise_wallets( + particl_wallet_mnemonic, + init_coins, + data_dir, + settings, + chain, + use_tor_proxy, + ) - print('Done.') + print("Done.") return 0 if enable_tor: - logger.info('Enabling TOR') + logger.info("Enabling TOR") settings = load_config(config_path) - tor_control_password = settings.get('tor_control_password', None) + 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 + 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) + 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: + with open(config_path, "w") as fp: json.dump(settings, fp, indent=4) - logger.info('Done.') + logger.info("Done.") return 0 if disable_tor: - logger.info('Disabling 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) + 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: + with open(config_path, "w") as fp: json.dump(settings, fp, indent=4) - logger.info('Done.') + logger.info("Done.") return 0 - if disable_coin != '': - logger.info('Disabling coin: %s', disable_coin) + 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') + 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 - if 'manage_wallet_daemon' in coin_settings: - coin_settings['manage_wallet_daemon'] = False + 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 + if "manage_wallet_daemon" in coin_settings: + coin_settings["manage_wallet_daemon"] = False - with open(config_path, 'w') as fp: + with open(config_path, "w") as fp: json.dump(settings, fp, indent=4) - logger.info('Done.') + logger.info("Done.") return 0 - extra_opts['data_dir'] = data_dir - extra_opts['tor_control_password'] = tor_control_password + extra_opts["data_dir"] = data_dir + extra_opts["tor_control_password"] = tor_control_password - if add_coin != '': - logger.info('Adding coin: %s', add_coin) + if add_coin != "": + logger.info("Adding coin: %s", add_coin) settings = load_config(config_path) - 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 - if 'manage_wallet_daemon' in coin_settings: - coin_settings['manage_wallet_daemon'] = True - with open(config_path, 'w') as fp: + 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 + if "manage_wallet_daemon" in coin_settings: + coin_settings["manage_wallet_daemon"] = True + with open(config_path, "w") as fp: json.dump(settings, fp, indent=4) - logger.info('Done.') + logger.info("Done.") return 0 - exitWithError('{} is already in the settings file'.format(add_coin)) + exitWithError("{} is already in the settings file".format(add_coin)) - if tor_control_password is None and settings.get('use_tor', False): - extra_opts['tor_control_password'] = settings.get('tor_control_password', None) + 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': + 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) - settings['chainclients'][add_coin] = chainclients[add_coin] - settings['use_tor_proxy'] = use_tor_proxy + 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) + 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) + if particl_wallet_mnemonic != "none": + initialise_wallets( + None, + { + add_coin, + }, + data_dir, + settings, + chain, + use_tor_proxy, + ) - with open(config_path, 'w') as fp: + with open(config_path, "w") as fp: json.dump(settings, fp, indent=4) - logger.info(f'Done. Coin {add_coin} successfully added.') + logger.info(f"Done. Coin {add_coin} successfully added.") return 0 - logger.info('With coins: %s', ', '.join(with_coins)) + logger.info("With coins: %s", ", ".join(with_coins)) if os.path.exists(config_path): if not prepare_bin_only: - exitWithError('{} exists'.format(config_path)) + 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] + 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 + "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 + 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) @@ -2162,22 +2624,24 @@ def main(): prepareCore(c, known_coins[c], settings, data_dir, extra_opts) if prepare_bin_only: - logger.info('Done.') + 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: + with open(config_path, "w") as fp: json.dump(settings, fp, indent=4) - if particl_wallet_mnemonic == 'none': - logger.info('Done.') + 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.') + initialise_wallets( + particl_wallet_mnemonic, with_coins, data_dir, settings, chain, use_tor_proxy + ) + print("Done.") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/basicswap/bin/run.py b/basicswap/bin/run.py index a0db229..1dfb18f 100755 --- a/basicswap/bin/run.py +++ b/basicswap/bin/run.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019-2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -32,7 +33,7 @@ swap_client = None class Daemon: - __slots__ = ('handle', 'files') + __slots__ = ("handle", "files") def __init__(self, handle, files): self.handle = handle @@ -41,14 +42,14 @@ class Daemon: def is_known_coin(coin_name: str) -> bool: for k, v in chainparams.items(): - if coin_name == v['name']: + 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)) + logger.info("Signal %d detected, ending program." % (sig)) if swap_client is not None: swap_client.stopRunning() @@ -59,9 +60,9 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}): # Rewrite litecoin.conf for 0.21.3 # TODO: Remove - ltc_conf_path = os.path.join(datadir_path, 'litecoin.conf') + ltc_conf_path = os.path.join(datadir_path, "litecoin.conf") if os.path.exists(ltc_conf_path): - config_to_add = ['blockfilterindex=0', 'peerblockfilters=0'] + config_to_add = ["blockfilterindex=0", "peerblockfilters=0"] with open(ltc_conf_path) as fp: for line in fp: line = line.strip() @@ -69,23 +70,30 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}): config_to_add.remove(line) if len(config_to_add) > 0: - logger.info('Rewriting litecoin.conf') - shutil.copyfile(ltc_conf_path, ltc_conf_path + '.last') - with open(ltc_conf_path, 'a') as fp: + logger.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') + fp.write(line + "\n") - args = [daemon_bin, ] - add_datadir: bool = extra_config.get('add_datadir', True) + args = [ + daemon_bin, + ] + add_datadir: bool = extra_config.get("add_datadir", True) if add_datadir: - args.append('-datadir=' + datadir_path) + args.append("-datadir=" + datadir_path) args += opts - logger.info('Starting node {}'.format(daemon_bin)) - logger.debug('Arguments {}'.format(' '.join(args))) + logger.info("Starting node {}".format(daemon_bin)) + logger.debug("Arguments {}".format(" ".join(args))) 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') + 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: @@ -93,62 +101,113 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[], extra_config={}): stderr_dest = subprocess.PIPE shell: bool = False - if extra_config.get('use_shell', False): - args = ' '.join(args) + if extra_config.get("use_shell", False): + args = " ".join(args) shell = True - return Daemon(subprocess.Popen(args, shell=shell, stdin=subprocess.PIPE, stdout=stdout_dest, stderr=stderr_dest, cwd=datadir_path), opened_files) + return Daemon( + subprocess.Popen( + args, + shell=shell, + stdin=subprocess.PIPE, + stdout=stdout_dest, + stderr=stderr_dest, + cwd=datadir_path, + ), + opened_files, + ) def startXmrDaemon(node_dir, bin_dir, daemon_bin, opts=[]): daemon_path = os.path.expanduser(os.path.join(bin_dir, daemon_bin)) datadir_path = os.path.expanduser(node_dir) - config_filename = 'wownerod.conf' if daemon_bin.startswith('wow') else 'monerod.conf' - args = [daemon_path, '--non-interactive', '--config-file=' + os.path.join(datadir_path, config_filename)] + opts - logger.info('Starting node {}'.format(daemon_bin)) - logger.debug('Arguments {}'.format(' '.join(args))) + config_filename = ( + "wownerod.conf" if daemon_bin.startswith("wow") else "monerod.conf" + ) + args = [ + daemon_path, + "--non-interactive", + "--config-file=" + os.path.join(datadir_path, config_filename), + ] + opts + logger.info("Starting node {}".format(daemon_bin)) + logger.debug("Arguments {}".format(" ".join(args))) # 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]) + 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_path = os.path.expanduser(os.path.join(bin_dir, wallet_bin)) - args = [daemon_path, '--non-interactive'] + args = [daemon_path, "--non-interactive"] needs_rewrite: bool = False - config_to_remove = ['daemon-address=', 'untrusted-daemon=', 'trusted-daemon=', 'proxy='] + config_to_remove = [ + "daemon-address=", + "untrusted-daemon=", + "trusted-daemon=", + "proxy=", + ] data_dir = os.path.expanduser(node_dir) - wallet_config_filename = 'wownero-wallet-rpc.conf' if wallet_bin.startswith('wow') else 'monero_wallet.conf' + wallet_config_filename = ( + "wownero-wallet-rpc.conf" + if wallet_bin.startswith("wow") + else "monero_wallet.conf" + ) config_path = os.path.join(data_dir, wallet_config_filename) if os.path.exists(config_path): - args += ['--config-file=' + 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): - logger.warning('Found old config in monero_wallet.conf: {}'.format(line.strip())) + if any( + line.startswith(config_line) for config_line in config_to_remove + ): + logger.warning( + "Found old config in monero_wallet.conf: {}".format( + line.strip() + ) + ) needs_rewrite = True args += opts if needs_rewrite: - logger.info('Rewriting wallet config') - shutil.copyfile(config_path, config_path + '.last') - with open(config_path + '.last') as fp_from, open(config_path, 'w') as fp_to: + logger.info("Rewriting wallet config") + 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): + if not any( + line.startswith(config_line) for config_line in config_to_remove + ): fp_to.write(line) - logger.info('Starting wallet daemon {}'.format(wallet_bin)) - logger.debug('Arguments {}'.format(' '.join(args))) + logger.info("Starting wallet daemon {}".format(wallet_bin)) + logger.debug("Arguments {}".format(" ".join(args))) # 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]) + 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): @@ -165,25 +224,29 @@ def ws_client_left(client, server): def ws_message_received(client, server, message): if len(message) > 200: - message = message[:200] + '..' + message = message[:200] + ".." if swap_client: swap_client.log.debug(f'ws_message_received {client["id"]} {message}') def getCoreBinName(coin_id: int, coin_settings, default_name: str) -> str: - return coin_settings.get('core_binname', chainparams[coin_id].get('core_binname', default_name)) + ('.exe' if os.name == 'nt' else '') + return coin_settings.get( + "core_binname", chainparams[coin_id].get("core_binname", default_name) + ) + (".exe" if os.name == "nt" else "") def getWalletBinName(coin_id: int, coin_settings, default_name: str) -> str: - return coin_settings.get('wallet_binname', chainparams[coin_id].get('wallet_binname', default_name)) + ('.exe' if os.name == 'nt' else '') + return coin_settings.get( + "wallet_binname", chainparams[coin_id].get("wallet_binname", default_name) + ) + (".exe" if os.name == "nt" else "") def getCoreBinArgs(coin_id: int, coin_settings): extra_args = [] - if 'config_filename' in coin_settings: - extra_args.append('--conf=' + coin_settings['config_filename']) - if 'port' in coin_settings: - extra_args.append('--port=' + str(int(coin_settings['port']))) + if "config_filename" in coin_settings: + extra_args.append("--conf=" + coin_settings["config_filename"]) + if "port" in coin_settings: + extra_args.append("--port=" + str(int(coin_settings["port"]))) return extra_args @@ -193,17 +256,21 @@ def runClient(fp, data_dir, chain, start_only_coins): pids = [] threads = [] settings_path = os.path.join(data_dir, cfg.CONFIG_FILENAME) - pids_path = os.path.join(data_dir, '.pids') + pids_path = os.path.join(data_dir, ".pids") - if os.getenv('WALLET_ENCRYPTION_PWD', '') != '': - if 'decred' in start_only_coins: + 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.') + logger.warning( + "Allowing set WALLET_ENCRYPTION_PWD var with --startonlycoin=decred." + ) else: - raise ValueError('Please unset the WALLET_ENCRYPTION_PWD environment variable.') + 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)) + raise ValueError("Settings file not found: " + str(settings_path)) with open(settings_path) as fs: settings = json.load(fs) @@ -215,7 +282,7 @@ def runClient(fp, data_dir, chain, start_only_coins): with open(pids_path) as fd: for ln in fd: # TODO: try close - logger.warning('Found pid for daemon {} '.format(ln.strip())) + logger.warning("Found pid for daemon {} ".format(ln.strip())) # Ensure daemons are stopped swap_client.stopDaemons() @@ -224,155 +291,227 @@ def runClient(fp, data_dir, chain, start_only_coins): settings = swap_client.settings try: # Try start daemons - for c, v in settings['chainclients'].items(): + 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('Not starting unknown coin: {}'.format(c)) + except Exception as e: # noqa: F841 + logger.warning("Not starting unknown coin: {}".format(c)) continue - if c in ('monero', 'wownero'): - if v['manage_daemon'] is True: - swap_client.log.info(f'Starting {display_name} daemon') - filename: str = getCoreBinName(coin_id, v, c + 'd') + if c in ("monero", "wownero"): + if v["manage_daemon"] is True: + swap_client.log.info(f"Starting {display_name} daemon") + filename: str = getCoreBinName(coin_id, v, c + "d") - daemons.append(startXmrDaemon(v['datadir'], v['bindir'], filename)) + daemons.append(startXmrDaemon(v["datadir"], v["bindir"], filename)) pid = daemons[-1].handle.pid - swap_client.log.info('Started {} {}'.format(filename, 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, ] + 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']) + 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}', '--daemon-ssl-allow-any-cert', ] + proxy_log_str = " through proxy" + opts += [ + "--proxy", + f"{proxy_host}:{proxy_port}", + "--daemon-ssl-allow-any-cert", + ] - swap_client.log.info('daemon-address: {} ({}){}'.format(daemon_addr, 'trusted' if trusted_daemon else 'untrusted', proxy_log_str)) + 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) + 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: str = getWalletBinName(coin_id, v, c + '-wallet-rpc') + opts.append( + "--trusted-daemon" if trusted_daemon else "--untrusted-daemon" + ) + filename: str = getWalletBinName(coin_id, v, c + "-wallet-rpc") - daemons.append(startXmrWalletDaemon(v['datadir'], v['bindir'], filename, opts)) + daemons.append( + startXmrWalletDaemon(v["datadir"], v["bindir"], filename, opts) + ) pid = daemons[-1].handle.pid - swap_client.log.info('Started {} {}'.format(filename, 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: str = getCoreBinName(coin_id, v, 'dcrd') + 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: str = getCoreBinName(coin_id, v, "dcrd") - 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)) + 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)) + 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: str = getWalletBinName(coin_id, v, 'dcrwallet') + if v["manage_wallet_daemon"] is True: + swap_client.log.info(f"Starting {display_name} wallet daemon") + filename: str = getWalletBinName(coin_id, v, "dcrwallet") - wallet_pwd = v['wallet_pwd'] - if wallet_pwd == '': + wallet_pwd = v["wallet_pwd"] + if wallet_pwd == "": # Only set when in startonlycoin mode - wallet_pwd = os.getenv('WALLET_ENCRYPTION_PWD', '') - if wallet_pwd != '': + 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)) + 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)) + 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') + if v["manage_daemon"] is True: + swap_client.log.info(f"Starting {display_name} daemon") - filename: str = getCoreBinName(coin_id, v, c + 'd') + filename: str = getCoreBinName(coin_id, v, c + "d") extra_opts = getCoreBinArgs(coin_id, v) - daemons.append(startDaemon(v['datadir'], v['bindir'], filename, opts=extra_opts)) + daemons.append( + startDaemon(v["datadir"], v["bindir"], filename, opts=extra_opts) + ) pid = daemons[-1].handle.pid pids.append((c, pid)) swap_client.setDaemonPID(c, pid) - swap_client.log.info('Started {} {}'.format(filename, pid)) + swap_client.log.info("Started {} {}".format(filename, pid)) if len(pids) > 0: - with open(pids_path, 'w') as fd: + with open(pids_path, "w") as fd: for p in pids: - fd.write('{}:{}\n'.format(*p)) + 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.') + 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) + 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}.') + 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.client_port = settings.get('wsclientport', settings['wsport']) + swap_client.ws_server = WebsocketServer( + host=settings["wshost"], port=settings["wsport"] + ) + swap_client.ws_server.client_port = settings.get( + "wsclientport", 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.') + logger.info("Exit with Ctrl + c.") while not swap_client.delay_event.wait(0.5): swap_client.update() - except Exception as ex: + except Exception as e: # noqa: F841 traceback.print_exc() if swap_client.ws_server: try: - swap_client.log.info('Stopping websocket server.') + swap_client.log.info("Stopping websocket server.") swap_client.ws_server.shutdown_gracefully() - except Exception as ex: + except Exception as e: # noqa: F841 traceback.print_exc() swap_client.finalise() - swap_client.log.info('Stopping HTTP threads.') + swap_client.log.info("Stopping HTTP threads.") for t in threads: try: t.stop() t.join() - except Exception as ex: + except Exception as e: # noqa: F841 traceback.print_exc() closed_pids = [] for d in daemons: - swap_client.log.info('Interrupting {}'.format(d.handle.pid)) + 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) + 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)) + swap_client.log.info("Interrupting %d, error %s", d.handle.pid, str(e)) for d in daemons: try: d.handle.wait(timeout=120) @@ -381,96 +520,106 @@ def runClient(fp, data_dir, chain, start_only_coins): fp.close() closed_pids.append(d.handle.pid) except Exception as ex: - swap_client.log.error('Error: {}'.format(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 = '' + 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' + if int(ln.split(":")[1]) not in closed_pids: + still_running += ln + "\n" except Exception: pass - with open(pids_path, 'w') as fd: + with open(pids_path, "w") as fd: fd.write(still_running) def printVersion(): - logger.info('Basicswap version: %s', __version__) + 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.') + 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' + 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) + if len(v) < 2 or v[0] != "-": + logger.warning("Unknown argument %s", v) continue - s = v.split('=') + s = v.split("=") name = s[0].strip() for i in range(2): - if name[0] == '-': + if name[0] == "-": name = name[1:] - if name == 'v' or name == 'version': + if name == "v" or name == "version": printVersion() return 0 - if name == 'h' or name == 'help': + if name == "h" or name == "help": printHelp() return 0 - if name in ('mainnet', 'testnet', 'regtest'): + if name in ("mainnet", "testnet", "regtest"): chain = name continue if len(s) == 2: - if name == 'datadir': + 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 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}') + raise ValueError(f"Unknown coin: {coin}") start_only_coins.add(coin) continue - logger.warning('Unknown argument %s', v) + logger.warning("Unknown argument %s", v) - if os.name == 'nt': - logger.warning('Running on windows is discouraged and windows support may be discontinued in the future. Please consider using the WSL docker setup instead.') + if os.name == "nt": + logger.warning( + "Running on windows is discouraged and windows support may be discontinued in the future. Please consider using the WSL docker setup instead." + ) 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) + 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') + 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.') + logger.info("Done.") return swap_client.fail_code if swap_client is not None else 0 -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/basicswap/chainparams.py b/basicswap/chainparams.py index 0a5d8e3..e7a561f 100644 --- a/basicswap/chainparams.py +++ b/basicswap/chainparams.py @@ -9,8 +9,8 @@ from .util import ( COIN, ) -XMR_COIN = 10 ** 12 -WOW_COIN = 10 ** 11 +XMR_COIN = 10**12 +WOW_COIN = 10**11 class Coins(IntEnum): @@ -35,459 +35,459 @@ class Coins(IntEnum): chainparams = { Coins.PART: { - 'name': 'particl', - 'ticker': 'PART', - 'message_magic': 'Bitcoin Signed Message:\n', - 'blocks_target': 60 * 2, - 'decimal_places': 8, - 'mainnet': { - 'rpcport': 51735, - 'pubkey_address': 0x38, - 'script_address': 0x3c, - 'key_prefix': 0x6c, - 'stealth_key_prefix': 0x14, - 'hrp': 'pw', - 'bip44': 44, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, + "name": "particl", + "ticker": "PART", + "message_magic": "Bitcoin Signed Message:\n", + "blocks_target": 60 * 2, + "decimal_places": 8, + "mainnet": { + "rpcport": 51735, + "pubkey_address": 0x38, + "script_address": 0x3C, + "key_prefix": 0x6C, + "stealth_key_prefix": 0x14, + "hrp": "pw", + "bip44": 44, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'testnet': { - 'rpcport': 51935, - 'pubkey_address': 0x76, - 'script_address': 0x7a, - 'key_prefix': 0x2e, - 'stealth_key_prefix': 0x15, - 'hrp': 'tpw', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, + "testnet": { + "rpcport": 51935, + "pubkey_address": 0x76, + "script_address": 0x7A, + "key_prefix": 0x2E, + "stealth_key_prefix": 0x15, + "hrp": "tpw", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, + }, + "regtest": { + "rpcport": 51936, + "pubkey_address": 0x76, + "script_address": 0x7A, + "key_prefix": 0x2E, + "stealth_key_prefix": 0x15, + "hrp": "rtpw", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'regtest': { - 'rpcport': 51936, - 'pubkey_address': 0x76, - 'script_address': 0x7a, - 'key_prefix': 0x2e, - 'stealth_key_prefix': 0x15, - 'hrp': 'rtpw', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - } }, Coins.BTC: { - 'name': 'bitcoin', - 'ticker': 'BTC', - 'message_magic': 'Bitcoin Signed Message:\n', - 'blocks_target': 60 * 10, - 'decimal_places': 8, - 'mainnet': { - 'rpcport': 8332, - 'pubkey_address': 0, - 'script_address': 5, - 'key_prefix': 128, - 'hrp': 'bc', - 'bip44': 0, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, + "name": "bitcoin", + "ticker": "BTC", + "message_magic": "Bitcoin Signed Message:\n", + "blocks_target": 60 * 10, + "decimal_places": 8, + "mainnet": { + "rpcport": 8332, + "pubkey_address": 0, + "script_address": 5, + "key_prefix": 128, + "hrp": "bc", + "bip44": 0, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'testnet': { - 'rpcport': 18332, - 'pubkey_address': 111, - 'script_address': 196, - 'key_prefix': 239, - 'hrp': 'tb', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - 'name': 'testnet3', + "testnet": { + "rpcport": 18332, + "pubkey_address": 111, + "script_address": 196, + "key_prefix": 239, + "hrp": "tb", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, + "name": "testnet3", + }, + "regtest": { + "rpcport": 18443, + "pubkey_address": 111, + "script_address": 196, + "key_prefix": 239, + "hrp": "bcrt", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'regtest': { - 'rpcport': 18443, - 'pubkey_address': 111, - 'script_address': 196, - 'key_prefix': 239, - 'hrp': 'bcrt', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - } }, Coins.LTC: { - 'name': 'litecoin', - 'ticker': 'LTC', - 'message_magic': 'Litecoin Signed Message:\n', - 'blocks_target': 60 * 1, - 'decimal_places': 8, - 'mainnet': { - 'rpcport': 9332, - 'pubkey_address': 48, - 'script_address': 5, - 'script_address2': 50, - 'key_prefix': 176, - 'hrp': 'ltc', - 'bip44': 2, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, + "name": "litecoin", + "ticker": "LTC", + "message_magic": "Litecoin Signed Message:\n", + "blocks_target": 60 * 1, + "decimal_places": 8, + "mainnet": { + "rpcport": 9332, + "pubkey_address": 48, + "script_address": 5, + "script_address2": 50, + "key_prefix": 176, + "hrp": "ltc", + "bip44": 2, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'testnet': { - 'rpcport': 19332, - 'pubkey_address': 111, - 'script_address': 196, - 'script_address2': 58, - 'key_prefix': 239, - 'hrp': 'tltc', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - 'name': 'testnet4', + "testnet": { + "rpcport": 19332, + "pubkey_address": 111, + "script_address": 196, + "script_address2": 58, + "key_prefix": 239, + "hrp": "tltc", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, + "name": "testnet4", + }, + "regtest": { + "rpcport": 19443, + "pubkey_address": 111, + "script_address": 196, + "script_address2": 58, + "key_prefix": 239, + "hrp": "rltc", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'regtest': { - 'rpcport': 19443, - 'pubkey_address': 111, - 'script_address': 196, - 'script_address2': 58, - 'key_prefix': 239, - 'hrp': 'rltc', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - } }, Coins.DCR: { - 'name': 'decred', - 'ticker': 'DCR', - 'message_magic': 'Decred Signed Message:\n', - 'blocks_target': 60 * 5, - 'decimal_places': 8, - 'mainnet': { - 'rpcport': 9109, - 'pubkey_address': 0x073f, - 'script_address': 0x071a, - 'key_prefix': 0x22de, - 'bip44': 42, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, + "name": "decred", + "ticker": "DCR", + "message_magic": "Decred Signed Message:\n", + "blocks_target": 60 * 5, + "decimal_places": 8, + "mainnet": { + "rpcport": 9109, + "pubkey_address": 0x073F, + "script_address": 0x071A, + "key_prefix": 0x22DE, + "bip44": 42, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'testnet': { - 'rpcport': 19109, - 'pubkey_address': 0x0f21, - 'script_address': 0x0efc, - 'key_prefix': 0x230e, - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - 'name': 'testnet3', + "testnet": { + "rpcport": 19109, + "pubkey_address": 0x0F21, + "script_address": 0x0EFC, + "key_prefix": 0x230E, + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, + "name": "testnet3", + }, + "regtest": { # simnet + "rpcport": 18656, + "pubkey_address": 0x0E91, + "script_address": 0x0E6C, + "key_prefix": 0x2307, + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'regtest': { # simnet - 'rpcport': 18656, - 'pubkey_address': 0x0e91, - 'script_address': 0x0e6c, - 'key_prefix': 0x2307, - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - } }, Coins.NMC: { - 'name': 'namecoin', - 'ticker': 'NMC', - 'message_magic': 'Namecoin Signed Message:\n', - 'blocks_target': 60 * 10, - 'decimal_places': 8, - 'mainnet': { - 'rpcport': 8336, - 'pubkey_address': 52, - 'script_address': 13, - 'hrp': 'nc', - 'bip44': 7, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, + "name": "namecoin", + "ticker": "NMC", + "message_magic": "Namecoin Signed Message:\n", + "blocks_target": 60 * 10, + "decimal_places": 8, + "mainnet": { + "rpcport": 8336, + "pubkey_address": 52, + "script_address": 13, + "hrp": "nc", + "bip44": 7, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'testnet': { - 'rpcport': 18336, - 'pubkey_address': 111, - 'script_address': 196, - 'hrp': 'tn', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - 'name': 'testnet3', + "testnet": { + "rpcport": 18336, + "pubkey_address": 111, + "script_address": 196, + "hrp": "tn", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, + "name": "testnet3", + }, + "regtest": { + "rpcport": 18443, + "pubkey_address": 111, + "script_address": 196, + "hrp": "ncrt", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'regtest': { - 'rpcport': 18443, - 'pubkey_address': 111, - 'script_address': 196, - 'hrp': 'ncrt', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - } }, Coins.XMR: { - 'name': 'monero', - 'ticker': 'XMR', - 'client': 'xmr', - 'decimal_places': 12, - 'mainnet': { - 'rpcport': 18081, - 'walletrpcport': 18082, - 'min_amount': 100000, - 'max_amount': 10000 * XMR_COIN, - 'address_prefix': 18, + "name": "monero", + "ticker": "XMR", + "client": "xmr", + "decimal_places": 12, + "mainnet": { + "rpcport": 18081, + "walletrpcport": 18082, + "min_amount": 100000, + "max_amount": 10000 * XMR_COIN, + "address_prefix": 18, }, - 'testnet': { - 'rpcport': 28081, - 'walletrpcport': 28082, - 'min_amount': 100000, - 'max_amount': 10000 * XMR_COIN, - 'address_prefix': 18, + "testnet": { + "rpcport": 28081, + "walletrpcport": 28082, + "min_amount": 100000, + "max_amount": 10000 * XMR_COIN, + "address_prefix": 18, + }, + "regtest": { + "rpcport": 18081, + "walletrpcport": 18082, + "min_amount": 100000, + "max_amount": 10000 * XMR_COIN, + "address_prefix": 18, }, - 'regtest': { - 'rpcport': 18081, - 'walletrpcport': 18082, - 'min_amount': 100000, - 'max_amount': 10000 * XMR_COIN, - 'address_prefix': 18, - } }, Coins.WOW: { - 'name': 'wownero', - 'ticker': 'WOW', - 'client': 'wow', - 'decimal_places': 11, - 'mainnet': { - 'rpcport': 34568, - 'walletrpcport': 34572, # todo - 'min_amount': 100000, - 'max_amount': 10000 * WOW_COIN, - 'address_prefix': 4146, + "name": "wownero", + "ticker": "WOW", + "client": "wow", + "decimal_places": 11, + "mainnet": { + "rpcport": 34568, + "walletrpcport": 34572, # todo + "min_amount": 100000, + "max_amount": 10000 * WOW_COIN, + "address_prefix": 4146, }, - 'testnet': { - 'rpcport': 44568, - 'walletrpcport': 44572, - 'min_amount': 100000, - 'max_amount': 10000 * WOW_COIN, - 'address_prefix': 4146, + "testnet": { + "rpcport": 44568, + "walletrpcport": 44572, + "min_amount": 100000, + "max_amount": 10000 * WOW_COIN, + "address_prefix": 4146, + }, + "regtest": { + "rpcport": 54568, + "walletrpcport": 54572, + "min_amount": 100000, + "max_amount": 10000 * WOW_COIN, + "address_prefix": 4146, }, - 'regtest': { - 'rpcport': 54568, - 'walletrpcport': 54572, - 'min_amount': 100000, - 'max_amount': 10000 * WOW_COIN, - 'address_prefix': 4146, - } }, Coins.PIVX: { - 'name': 'pivx', - 'ticker': 'PIVX', - 'display_name': 'PIVX', - 'message_magic': 'DarkNet Signed Message:\n', - 'blocks_target': 60 * 1, - 'decimal_places': 8, - 'has_cltv': True, - 'has_csv': False, - 'has_segwit': False, - 'mainnet': { - 'rpcport': 51473, - 'pubkey_address': 30, - 'script_address': 13, - 'key_prefix': 212, - 'bip44': 119, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, + "name": "pivx", + "ticker": "PIVX", + "display_name": "PIVX", + "message_magic": "DarkNet Signed Message:\n", + "blocks_target": 60 * 1, + "decimal_places": 8, + "has_cltv": True, + "has_csv": False, + "has_segwit": False, + "mainnet": { + "rpcport": 51473, + "pubkey_address": 30, + "script_address": 13, + "key_prefix": 212, + "bip44": 119, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'testnet': { - 'rpcport': 51475, - 'pubkey_address': 139, - 'script_address': 19, - 'key_prefix': 239, - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - 'name': 'testnet4', + "testnet": { + "rpcport": 51475, + "pubkey_address": 139, + "script_address": 19, + "key_prefix": 239, + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, + "name": "testnet4", + }, + "regtest": { + "rpcport": 51477, + "pubkey_address": 139, + "script_address": 19, + "key_prefix": 239, + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'regtest': { - 'rpcport': 51477, - 'pubkey_address': 139, - 'script_address': 19, - 'key_prefix': 239, - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - } }, Coins.DASH: { - 'name': 'dash', - 'ticker': 'DASH', - 'message_magic': 'DarkCoin Signed Message:\n', - 'blocks_target': 60 * 2.5, - 'decimal_places': 8, - 'has_csv': True, - 'has_segwit': False, - 'mainnet': { - 'rpcport': 9998, - 'pubkey_address': 76, - 'script_address': 16, - 'key_prefix': 204, - 'hrp': '', - 'bip44': 5, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, + "name": "dash", + "ticker": "DASH", + "message_magic": "DarkCoin Signed Message:\n", + "blocks_target": 60 * 2.5, + "decimal_places": 8, + "has_csv": True, + "has_segwit": False, + "mainnet": { + "rpcport": 9998, + "pubkey_address": 76, + "script_address": 16, + "key_prefix": 204, + "hrp": "", + "bip44": 5, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'testnet': { - 'rpcport': 19998, - 'pubkey_address': 140, - 'script_address': 19, - 'key_prefix': 239, - 'hrp': '', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, + "testnet": { + "rpcport": 19998, + "pubkey_address": 140, + "script_address": 19, + "key_prefix": 239, + "hrp": "", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, + }, + "regtest": { + "rpcport": 18332, + "pubkey_address": 140, + "script_address": 19, + "key_prefix": 239, + "hrp": "", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'regtest': { - 'rpcport': 18332, - 'pubkey_address': 140, - 'script_address': 19, - 'key_prefix': 239, - 'hrp': '', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - } }, Coins.FIRO: { - 'name': 'firo', - 'ticker': 'FIRO', - 'message_magic': 'Zcoin Signed Message:\n', - 'blocks_target': 60 * 10, - 'decimal_places': 8, - 'has_cltv': False, - 'has_csv': False, - 'has_segwit': False, - 'mainnet': { - 'rpcport': 8888, - 'pubkey_address': 82, - 'script_address': 7, - 'key_prefix': 210, - 'hrp': '', - 'bip44': 136, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, + "name": "firo", + "ticker": "FIRO", + "message_magic": "Zcoin Signed Message:\n", + "blocks_target": 60 * 10, + "decimal_places": 8, + "has_cltv": False, + "has_csv": False, + "has_segwit": False, + "mainnet": { + "rpcport": 8888, + "pubkey_address": 82, + "script_address": 7, + "key_prefix": 210, + "hrp": "", + "bip44": 136, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'testnet': { - 'rpcport': 18888, - 'pubkey_address': 65, - 'script_address': 178, - 'key_prefix': 185, - 'hrp': '', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, + "testnet": { + "rpcport": 18888, + "pubkey_address": 65, + "script_address": 178, + "key_prefix": 185, + "hrp": "", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, + }, + "regtest": { + "rpcport": 28888, + "pubkey_address": 65, + "script_address": 178, + "key_prefix": 239, + "hrp": "", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'regtest': { - 'rpcport': 28888, - 'pubkey_address': 65, - 'script_address': 178, - 'key_prefix': 239, - 'hrp': '', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - } }, Coins.NAV: { - 'name': 'navcoin', - 'ticker': 'NAV', - 'message_magic': 'Navcoin Signed Message:\n', - 'blocks_target': 30, - 'decimal_places': 8, - 'has_csv': True, - 'has_segwit': True, - 'mainnet': { - 'rpcport': 44444, - 'pubkey_address': 53, - 'script_address': 85, - 'key_prefix': 150, - 'hrp': '', - 'bip44': 130, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, + "name": "navcoin", + "ticker": "NAV", + "message_magic": "Navcoin Signed Message:\n", + "blocks_target": 30, + "decimal_places": 8, + "has_csv": True, + "has_segwit": True, + "mainnet": { + "rpcport": 44444, + "pubkey_address": 53, + "script_address": 85, + "key_prefix": 150, + "hrp": "", + "bip44": 130, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'testnet': { - 'rpcport': 44445, - 'pubkey_address': 111, - 'script_address': 196, - 'key_prefix': 239, - 'hrp': '', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, + "testnet": { + "rpcport": 44445, + "pubkey_address": 111, + "script_address": 196, + "key_prefix": 239, + "hrp": "", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, + }, + "regtest": { + "rpcport": 44446, + "pubkey_address": 111, + "script_address": 196, + "key_prefix": 239, + "hrp": "", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'regtest': { - 'rpcport': 44446, - 'pubkey_address': 111, - 'script_address': 196, - 'key_prefix': 239, - 'hrp': '', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - } }, Coins.BCH: { - 'name': 'bitcoincash', - 'ticker': 'BCH', - 'display_name': 'Bitcoin Cash', - 'message_magic': 'Bitcoin Signed Message:\n', - 'blocks_target': 60 * 2, - 'decimal_places': 8, - 'has_cltv': True, - 'has_csv': True, - 'has_segwit': False, - 'cli_binname': 'bitcoin-cli', - 'core_binname': 'bitcoind', - 'mainnet': { - 'rpcport': 8332, - 'pubkey_address': 0, - 'script_address': 5, - 'key_prefix': 128, - 'hrp': 'bitcoincash', - 'bip44': 0, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, + "name": "bitcoincash", + "ticker": "BCH", + "display_name": "Bitcoin Cash", + "message_magic": "Bitcoin Signed Message:\n", + "blocks_target": 60 * 2, + "decimal_places": 8, + "has_cltv": True, + "has_csv": True, + "has_segwit": False, + "cli_binname": "bitcoin-cli", + "core_binname": "bitcoind", + "mainnet": { + "rpcport": 8332, + "pubkey_address": 0, + "script_address": 5, + "key_prefix": 128, + "hrp": "bitcoincash", + "bip44": 0, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'testnet': { - 'rpcport': 18332, - 'pubkey_address': 111, - 'script_address': 196, - 'key_prefix': 239, - 'hrp': 'bchtest', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - 'name': 'testnet3', + "testnet": { + "rpcport": 18332, + "pubkey_address": 111, + "script_address": 196, + "key_prefix": 239, + "hrp": "bchtest", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, + "name": "testnet3", + }, + "regtest": { + "rpcport": 18443, + "pubkey_address": 111, + "script_address": 196, + "key_prefix": 239, + "hrp": "bchreg", + "bip44": 1, + "min_amount": 1000, + "max_amount": 100000 * COIN, }, - 'regtest': { - 'rpcport': 18443, - 'pubkey_address': 111, - 'script_address': 196, - 'key_prefix': 239, - 'hrp': 'bchreg', - 'bip44': 1, - 'min_amount': 1000, - 'max_amount': 100000 * COIN, - } }, } ticker_map = {} for c, params in chainparams.items(): - ticker_map[params['ticker'].lower()] = c + ticker_map[params["ticker"].lower()] = c def getCoinIdFromTicker(ticker: str) -> str: try: return ticker_map[ticker.lower()] except Exception: - raise ValueError('Unknown coin') + raise ValueError("Unknown coin") diff --git a/basicswap/config.py b/basicswap/config.py index ea40bcb..2ee7648 100644 --- a/basicswap/config.py +++ b/basicswap/config.py @@ -6,35 +6,47 @@ import os -CONFIG_FILENAME = 'basicswap.json' -BASICSWAP_DATADIR = os.getenv('BASICSWAP_DATADIR', os.path.join('~', '.basicswap')) +CONFIG_FILENAME = "basicswap.json" +BASICSWAP_DATADIR = os.getenv("BASICSWAP_DATADIR", os.path.join("~", ".basicswap")) DEFAULT_ALLOW_CORS = False -TEST_DATADIRS = os.path.expanduser(os.getenv('DATADIRS', '/tmp/basicswap')) -DEFAULT_TEST_BINDIR = os.path.expanduser(os.getenv('DEFAULT_TEST_BINDIR', os.path.join('~', '.basicswap', 'bin'))) +TEST_DATADIRS = os.path.expanduser(os.getenv("DATADIRS", "/tmp/basicswap")) +DEFAULT_TEST_BINDIR = os.path.expanduser( + os.getenv("DEFAULT_TEST_BINDIR", os.path.join("~", ".basicswap", "bin")) +) -bin_suffix = ('.exe' if os.name == 'nt' else '') -PARTICL_BINDIR = os.path.expanduser(os.getenv('PARTICL_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'particl'))) -PARTICLD = os.getenv('PARTICLD', 'particld' + bin_suffix) -PARTICL_CLI = os.getenv('PARTICL_CLI', 'particl-cli' + bin_suffix) -PARTICL_TX = os.getenv('PARTICL_TX', 'particl-tx' + bin_suffix) +bin_suffix = ".exe" if os.name == "nt" else "" +PARTICL_BINDIR = os.path.expanduser( + os.getenv("PARTICL_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "particl")) +) +PARTICLD = os.getenv("PARTICLD", "particld" + bin_suffix) +PARTICL_CLI = os.getenv("PARTICL_CLI", "particl-cli" + bin_suffix) +PARTICL_TX = os.getenv("PARTICL_TX", "particl-tx" + bin_suffix) -BITCOIN_BINDIR = os.path.expanduser(os.getenv('BITCOIN_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'bitcoin'))) -BITCOIND = os.getenv('BITCOIND', 'bitcoind' + bin_suffix) -BITCOIN_CLI = os.getenv('BITCOIN_CLI', 'bitcoin-cli' + bin_suffix) -BITCOIN_TX = os.getenv('BITCOIN_TX', 'bitcoin-tx' + bin_suffix) +BITCOIN_BINDIR = os.path.expanduser( + os.getenv("BITCOIN_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "bitcoin")) +) +BITCOIND = os.getenv("BITCOIND", "bitcoind" + bin_suffix) +BITCOIN_CLI = os.getenv("BITCOIN_CLI", "bitcoin-cli" + bin_suffix) +BITCOIN_TX = os.getenv("BITCOIN_TX", "bitcoin-tx" + bin_suffix) -LITECOIN_BINDIR = os.path.expanduser(os.getenv('LITECOIN_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'litecoin'))) -LITECOIND = os.getenv('LITECOIND', 'litecoind' + bin_suffix) -LITECOIN_CLI = os.getenv('LITECOIN_CLI', 'litecoin-cli' + bin_suffix) -LITECOIN_TX = os.getenv('LITECOIN_TX', 'litecoin-tx' + bin_suffix) +LITECOIN_BINDIR = os.path.expanduser( + os.getenv("LITECOIN_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "litecoin")) +) +LITECOIND = os.getenv("LITECOIND", "litecoind" + bin_suffix) +LITECOIN_CLI = os.getenv("LITECOIN_CLI", "litecoin-cli" + bin_suffix) +LITECOIN_TX = os.getenv("LITECOIN_TX", "litecoin-tx" + bin_suffix) -NAMECOIN_BINDIR = os.path.expanduser(os.getenv('NAMECOIN_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'namecoin'))) -NAMECOIND = os.getenv('NAMECOIND', 'namecoind' + bin_suffix) -NAMECOIN_CLI = os.getenv('NAMECOIN_CLI', 'namecoin-cli' + bin_suffix) -NAMECOIN_TX = os.getenv('NAMECOIN_TX', 'namecoin-tx' + bin_suffix) +NAMECOIN_BINDIR = os.path.expanduser( + os.getenv("NAMECOIN_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "namecoin")) +) +NAMECOIND = os.getenv("NAMECOIND", "namecoind" + bin_suffix) +NAMECOIN_CLI = os.getenv("NAMECOIN_CLI", "namecoin-cli" + bin_suffix) +NAMECOIN_TX = os.getenv("NAMECOIN_TX", "namecoin-tx" + bin_suffix) -XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'monero'))) -XMRD = os.getenv('XMRD', 'monerod' + bin_suffix) -XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix) +XMR_BINDIR = os.path.expanduser( + os.getenv("XMR_BINDIR", os.path.join(DEFAULT_TEST_BINDIR, "monero")) +) +XMRD = os.getenv("XMRD", "monerod" + bin_suffix) +XMR_WALLET_RPC = os.getenv("XMR_WALLET_RPC", "monero-wallet-rpc" + bin_suffix) # NOTE: Adding coin definitions here is deprecated. Please add in coin test file. diff --git a/basicswap/db.py b/basicswap/db.py index d946997..1c3ae32 100644 --- a/basicswap/db.py +++ b/basicswap/db.py @@ -25,34 +25,34 @@ class Concepts(IntEnum): def strConcepts(state): if state == Concepts.OFFER: - return 'Offer' + return "Offer" if state == Concepts.BID: - return 'Bid' + return "Bid" if state == Concepts.NETWORK_MESSAGE: - return 'Network Message' - return 'Unknown' + return "Network Message" + return "Unknown" def pack_state(new_state: int, now: int) -> bytes: - return int(new_state).to_bytes(4, 'little') + now.to_bytes(8, 'little') + return int(new_state).to_bytes(4, "little") + now.to_bytes(8, "little") class DBKVInt(Base): - __tablename__ = 'kv_int' + __tablename__ = "kv_int" key = sa.Column(sa.String, primary_key=True) value = sa.Column(sa.Integer) class DBKVString(Base): - __tablename__ = 'kv_string' + __tablename__ = "kv_string" key = sa.Column(sa.String, primary_key=True) value = sa.Column(sa.String) class Offer(Base): - __tablename__ = 'offers' + __tablename__ = "offers" offer_id = sa.Column(sa.LargeBinary, primary_key=True) active_ind = sa.Column(sa.Integer) @@ -89,7 +89,9 @@ class Offer(Base): # Local fields auto_accept_bids = sa.Column(sa.Boolean) - withdraw_to_addr = sa.Column(sa.String) # Address to spend lock tx to - address from wallet if empty TODO + withdraw_to_addr = sa.Column( + sa.String + ) # Address to spend lock tx to - address from wallet if empty TODO security_token = sa.Column(sa.LargeBinary) bid_reversed = sa.Column(sa.Boolean) @@ -106,10 +108,10 @@ class Offer(Base): class Bid(Base): - __tablename__ = 'bids' + __tablename__ = "bids" bid_id = sa.Column(sa.LargeBinary, primary_key=True) - offer_id = sa.Column(sa.LargeBinary, sa.ForeignKey('offers.offer_id')) + offer_id = sa.Column(sa.LargeBinary, sa.ForeignKey("offers.offer_id")) active_ind = sa.Column(sa.Integer) protocol_version = sa.Column(sa.Integer) @@ -121,13 +123,17 @@ class Bid(Base): bid_addr = sa.Column(sa.String) proof_address = sa.Column(sa.String) proof_utxos = sa.Column(sa.LargeBinary) - withdraw_to_addr = sa.Column(sa.String) # Address to spend lock tx to - address from wallet if empty TODO + withdraw_to_addr = sa.Column( + sa.String + ) # Address to spend lock tx to - address from wallet if empty TODO recovered_secret = sa.Column(sa.LargeBinary) amount_to = sa.Column(sa.BigInteger) # amount * offer.rate pkhash_buyer = sa.Column(sa.LargeBinary) - pkhash_buyer_to = sa.Column(sa.LargeBinary) # Used for the ptx if coin pubkey hashes differ + pkhash_buyer_to = sa.Column( + sa.LargeBinary + ) # Used for the ptx if coin pubkey hashes differ amount = sa.Column(sa.BigInteger) rate = sa.Column(sa.BigInteger) @@ -149,8 +155,12 @@ class Bid(Base): debug_ind = sa.Column(sa.Integer) security_token = sa.Column(sa.LargeBinary) - chain_a_height_start = sa.Column(sa.Integer) # Height of script chain before the swap - chain_b_height_start = sa.Column(sa.Integer) # Height of scriptless chain before the swap + chain_a_height_start = sa.Column( + sa.Integer + ) # Height of script chain before the swap + chain_b_height_start = sa.Column( + sa.Integer + ) # Height of scriptless chain before the swap reject_code = sa.Column(sa.Integer) @@ -199,12 +209,12 @@ class Bid(Base): class SwapTx(Base): - __tablename__ = 'transactions' + __tablename__ = "transactions" - bid_id = sa.Column(sa.LargeBinary, sa.ForeignKey('bids.bid_id')) + bid_id = sa.Column(sa.LargeBinary, sa.ForeignKey("bids.bid_id")) tx_type = sa.Column(sa.Integer) # TxTypes __table_args__ = ( - sa.PrimaryKeyConstraint('bid_id', 'tx_type'), + sa.PrimaryKeyConstraint("bid_id", "tx_type"), {}, ) @@ -240,7 +250,7 @@ class SwapTx(Base): class PrefundedTx(Base): - __tablename__ = 'prefunded_transactions' + __tablename__ = "prefunded_transactions" record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) active_ind = sa.Column(sa.Integer) @@ -253,7 +263,7 @@ class PrefundedTx(Base): class PooledAddress(Base): - __tablename__ = 'addresspool' + __tablename__ = "addresspool" addr_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) coin_type = sa.Column(sa.Integer) @@ -263,13 +273,13 @@ class PooledAddress(Base): class SentOffer(Base): - __tablename__ = 'sentoffers' + __tablename__ = "sentoffers" offer_id = sa.Column(sa.LargeBinary, primary_key=True) class SmsgAddress(Base): - __tablename__ = 'smsgaddresses' + __tablename__ = "smsgaddresses" addr_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) active_ind = sa.Column(sa.Integer) @@ -281,7 +291,7 @@ class SmsgAddress(Base): class Action(Base): - __tablename__ = 'actions' + __tablename__ = "actions" action_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) active_ind = sa.Column(sa.Integer) @@ -293,7 +303,7 @@ class Action(Base): class EventLog(Base): - __tablename__ = 'eventlog' + __tablename__ = "eventlog" event_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) active_ind = sa.Column(sa.Integer) @@ -303,32 +313,38 @@ class EventLog(Base): event_type = sa.Column(sa.Integer) event_msg = sa.Column(sa.String) - __table_args__ = (sa.Index('main_index', 'linked_type', 'linked_id'), ) + __table_args__ = (sa.Index("main_index", "linked_type", "linked_id"),) class XmrOffer(Base): - __tablename__ = 'xmr_offers' + __tablename__ = "xmr_offers" # TODO: Merge to Offer swap_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - offer_id = sa.Column(sa.LargeBinary, sa.ForeignKey('offers.offer_id')) + offer_id = sa.Column(sa.LargeBinary, sa.ForeignKey("offers.offer_id")) a_fee_rate = sa.Column(sa.BigInteger) # Chain a fee rate b_fee_rate = sa.Column(sa.BigInteger) # Chain b fee rate - lock_time_1 = sa.Column(sa.Integer) # Delay before the chain a lock refund tx can be mined - lock_time_2 = sa.Column(sa.Integer) # Delay before the follower can spend from the chain a lock refund tx + lock_time_1 = sa.Column( + sa.Integer + ) # Delay before the chain a lock refund tx can be mined + lock_time_2 = sa.Column( + sa.Integer + ) # Delay before the follower can spend from the chain a lock refund tx class XmrSwap(Base): - __tablename__ = 'xmr_swaps' + __tablename__ = "xmr_swaps" swap_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) - bid_id = sa.Column(sa.LargeBinary, sa.ForeignKey('bids.bid_id')) + bid_id = sa.Column(sa.LargeBinary, sa.ForeignKey("bids.bid_id")) contract_count = sa.Column(sa.Integer) - dest_af = sa.Column(sa.LargeBinary) # Destination for coin A amount to follower when swap completes successfully + dest_af = sa.Column( + sa.LargeBinary + ) # Destination for coin A amount to follower when swap completes successfully pkal = sa.Column(sa.LargeBinary) pkasl = sa.Column(sa.LargeBinary) @@ -349,9 +365,9 @@ class XmrSwap(Base): kbsl_dleag = sa.Column(sa.LargeBinary) kbsf_dleag = sa.Column(sa.LargeBinary) - vkbv = sa.Column(sa.LargeBinary) # chain b view private key - pkbv = sa.Column(sa.LargeBinary) # chain b view public key - pkbs = sa.Column(sa.LargeBinary) # chain b spend public key + vkbv = sa.Column(sa.LargeBinary) # chain b view private key + pkbv = sa.Column(sa.LargeBinary) # chain b view public key + pkbs = sa.Column(sa.LargeBinary) # chain b spend public key a_lock_tx = sa.Column(sa.LargeBinary) a_lock_tx_script = sa.Column(sa.LargeBinary) @@ -376,13 +392,15 @@ class XmrSwap(Base): al_lock_spend_tx_esig = sa.Column(sa.LargeBinary) kal_sig = sa.Column(sa.LargeBinary) - a_lock_refund_swipe_tx = sa.Column(sa.LargeBinary) # Follower spends script coin lock refund tx + a_lock_refund_swipe_tx = sa.Column( + sa.LargeBinary + ) # Follower spends script coin lock refund tx b_lock_tx_id = sa.Column(sa.LargeBinary) class XmrSplitData(Base): - __tablename__ = 'xmr_split_data' + __tablename__ = "xmr_split_data" record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) addr_from = sa.Column(sa.String) @@ -393,11 +411,13 @@ class XmrSplitData(Base): dleag = sa.Column(sa.LargeBinary) created_at = sa.Column(sa.BigInteger) - __table_args__ = (sa.UniqueConstraint('bid_id', 'msg_type', 'msg_sequence', name='uc_1'),) + __table_args__ = ( + sa.UniqueConstraint("bid_id", "msg_type", "msg_sequence", name="uc_1"), + ) class RevokedMessage(Base): - __tablename__ = 'revoked_messages' + __tablename__ = "revoked_messages" record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) active_ind = sa.Column(sa.Integer) @@ -407,7 +427,7 @@ class RevokedMessage(Base): class Wallets(Base): - __tablename__ = 'wallets' + __tablename__ = "wallets" record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) active_ind = sa.Column(sa.Integer) @@ -419,7 +439,7 @@ class Wallets(Base): class KnownIdentity(Base): - __tablename__ = 'knownidentities' + __tablename__ = "knownidentities" record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) active_ind = sa.Column(sa.Integer) @@ -441,7 +461,7 @@ class KnownIdentity(Base): class AutomationStrategy(Base): - __tablename__ = 'automationstrategies' + __tablename__ = "automationstrategies" record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) active_ind = sa.Column(sa.Integer) @@ -457,7 +477,7 @@ class AutomationStrategy(Base): class AutomationLink(Base): - __tablename__ = 'automationlinks' + __tablename__ = "automationlinks" # Contains per order/bid options record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) @@ -474,11 +494,11 @@ class AutomationLink(Base): note = sa.Column(sa.String) created_at = sa.Column(sa.BigInteger) - __table_args__ = (sa.Index('linked_index', 'linked_type', 'linked_id'), ) + __table_args__ = (sa.Index("linked_index", "linked_type", "linked_id"),) class History(Base): - __tablename__ = 'history' + __tablename__ = "history" record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) concept_type = sa.Column(sa.Integer) @@ -489,7 +509,7 @@ class History(Base): class BidState(Base): - __tablename__ = 'bidstates' + __tablename__ = "bidstates" record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) active_ind = sa.Column(sa.Integer) @@ -505,7 +525,7 @@ class BidState(Base): class Notification(Base): - __tablename__ = 'notifications' + __tablename__ = "notifications" record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) active_ind = sa.Column(sa.Integer) @@ -515,7 +535,7 @@ class Notification(Base): class MessageLink(Base): - __tablename__ = 'message_links' + __tablename__ = "message_links" record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) active_ind = sa.Column(sa.Integer) @@ -531,7 +551,7 @@ class MessageLink(Base): class CheckedBlock(Base): - __tablename__ = 'checkedblocks' + __tablename__ = "checkedblocks" record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) created_at = sa.Column(sa.BigInteger) diff --git a/basicswap/db_upgrades.py b/basicswap/db_upgrades.py index 6a38c25..c4f0bb4 100644 --- a/basicswap/db_upgrades.py +++ b/basicswap/db_upgrades.py @@ -14,7 +14,8 @@ from .db import ( Concepts, AutomationStrategy, CURRENT_DB_VERSION, - CURRENT_DB_DATA_VERSION) + CURRENT_DB_DATA_VERSION, +) from .basicswap_util import ( BidStates, @@ -30,7 +31,11 @@ def upgradeDatabaseData(self, data_version): if data_version >= CURRENT_DB_DATA_VERSION: return - self.log.info('Upgrading database records from version %d to %d.', data_version, CURRENT_DB_DATA_VERSION) + self.log.info( + "Upgrading database records from version %d to %d.", + data_version, + CURRENT_DB_DATA_VERSION, + ) with self.mxDB: try: session = scoped_session(self.session_factory) @@ -38,65 +43,100 @@ def upgradeDatabaseData(self, data_version): now = int(time.time()) if data_version < 1: - session.add(AutomationStrategy( - active_ind=1, - label='Accept All', - type_ind=Concepts.OFFER, - data=json.dumps({'exact_rate_only': True, - 'max_concurrent_bids': 5}).encode('utf-8'), - only_known_identities=False, - created_at=now)) - session.add(AutomationStrategy( - active_ind=1, - label='Accept Known', - type_ind=Concepts.OFFER, - data=json.dumps({'exact_rate_only': True, - 'max_concurrent_bids': 5}).encode('utf-8'), - only_known_identities=True, - note='Accept bids from identities with previously successful swaps only', - created_at=now)) + session.add( + AutomationStrategy( + active_ind=1, + label="Accept All", + type_ind=Concepts.OFFER, + data=json.dumps( + {"exact_rate_only": True, "max_concurrent_bids": 5} + ).encode("utf-8"), + only_known_identities=False, + created_at=now, + ) + ) + session.add( + AutomationStrategy( + active_ind=1, + label="Accept Known", + type_ind=Concepts.OFFER, + data=json.dumps( + {"exact_rate_only": True, "max_concurrent_bids": 5} + ).encode("utf-8"), + only_known_identities=True, + note="Accept bids from identities with previously successful swaps only", + created_at=now, + ) + ) for state in BidStates: - session.add(BidState( - active_ind=1, - state_id=int(state), - in_progress=isActiveBidState(state), - in_error=isErrorBidState(state), - swap_failed=isFailingBidState(state), - swap_ended=isFinalBidState(state), - label=strBidState(state), - created_at=now)) + session.add( + BidState( + active_ind=1, + state_id=int(state), + in_progress=isActiveBidState(state), + in_error=isErrorBidState(state), + swap_failed=isFailingBidState(state), + swap_ended=isFinalBidState(state), + label=strBidState(state), + created_at=now, + ) + ) if data_version > 0 and data_version < 2: - for state in (BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS, BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX): - session.add(BidState( - active_ind=1, - state_id=int(state), - in_progress=isActiveBidState(state), - label=strBidState(state), - created_at=now)) + for state in ( + BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_TX_SIGS, + BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX, + ): + session.add( + BidState( + active_ind=1, + state_id=int(state), + in_progress=isActiveBidState(state), + label=strBidState(state), + created_at=now, + ) + ) if data_version > 0 and data_version < 3: for state in BidStates: in_error = isErrorBidState(state) swap_failed = isFailingBidState(state) swap_ended = isFinalBidState(state) - session.execute(text('UPDATE bidstates SET in_error = :in_error, swap_failed = :swap_failed, swap_ended = :swap_ended WHERE state_id = :state_id', {'in_error': in_error, 'swap_failed': swap_failed, 'swap_ended': swap_ended, 'state_id': int(state)})) + session.execute( + text( + "UPDATE bidstates SET in_error = :in_error, swap_failed = :swap_failed, swap_ended = :swap_ended WHERE state_id = :state_id", + { + "in_error": in_error, + "swap_failed": swap_failed, + "swap_ended": swap_ended, + "state_id": int(state), + }, + ) + ) if data_version > 0 and data_version < 4: - for state in (BidStates.BID_REQUEST_SENT, BidStates.BID_REQUEST_ACCEPTED): - session.add(BidState( - active_ind=1, - state_id=int(state), - in_progress=isActiveBidState(state), - in_error=isErrorBidState(state), - swap_failed=isFailingBidState(state), - swap_ended=isFinalBidState(state), - label=strBidState(state), - created_at=now)) + for state in ( + BidStates.BID_REQUEST_SENT, + BidStates.BID_REQUEST_ACCEPTED, + ): + session.add( + BidState( + active_ind=1, + state_id=int(state), + in_progress=isActiveBidState(state), + in_error=isErrorBidState(state), + swap_failed=isFailingBidState(state), + swap_ended=isFinalBidState(state), + label=strBidState(state), + created_at=now, + ) + ) self.db_data_version = CURRENT_DB_DATA_VERSION - self.setIntKV('db_data_version', self.db_data_version, session) + self.setIntKV("db_data_version", self.db_data_version, session) session.commit() - self.log.info('Upgraded database records to version {}'.format(self.db_data_version)) + self.log.info( + "Upgraded database records to version {}".format(self.db_data_version) + ) finally: session.close() session.remove() @@ -106,23 +146,31 @@ def upgradeDatabase(self, db_version): if db_version >= CURRENT_DB_VERSION: return - self.log.info('Upgrading database from version %d to %d.', db_version, CURRENT_DB_VERSION) + self.log.info( + "Upgrading database from version %d to %d.", db_version, CURRENT_DB_VERSION + ) while True: session = scoped_session(self.session_factory) current_version = db_version if current_version == 6: - session.execute(text('ALTER TABLE bids ADD COLUMN security_token BLOB')) - session.execute(text('ALTER TABLE offers ADD COLUMN security_token BLOB')) + session.execute(text("ALTER TABLE bids ADD COLUMN security_token BLOB")) + session.execute(text("ALTER TABLE offers ADD COLUMN security_token BLOB")) db_version += 1 elif current_version == 7: - session.execute(text('ALTER TABLE transactions ADD COLUMN block_hash BLOB')) - session.execute(text('ALTER TABLE transactions ADD COLUMN block_height INTEGER')) - session.execute(text('ALTER TABLE transactions ADD COLUMN block_time INTEGER')) + session.execute(text("ALTER TABLE transactions ADD COLUMN block_hash BLOB")) + session.execute( + text("ALTER TABLE transactions ADD COLUMN block_height INTEGER") + ) + session.execute( + text("ALTER TABLE transactions ADD COLUMN block_time INTEGER") + ) db_version += 1 elif current_version == 8: - session.execute(text(''' + session.execute( + text( + """ CREATE TABLE wallets ( record_id INTEGER NOT NULL, coin_id INTEGER, @@ -130,30 +178,48 @@ def upgradeDatabase(self, db_version): wallet_data VARCHAR, balance_type INTEGER, created_at BIGINT, - PRIMARY KEY (record_id))''')) + PRIMARY KEY (record_id))""" + ) + ) db_version += 1 elif current_version == 9: - session.execute(text('ALTER TABLE wallets ADD COLUMN wallet_data VARCHAR')) + session.execute(text("ALTER TABLE wallets ADD COLUMN wallet_data VARCHAR")) db_version += 1 elif current_version == 10: - session.execute(text('ALTER TABLE smsgaddresses ADD COLUMN active_ind INTEGER')) - session.execute(text('ALTER TABLE smsgaddresses ADD COLUMN created_at INTEGER')) - session.execute(text('ALTER TABLE smsgaddresses ADD COLUMN note VARCHAR')) - session.execute(text('ALTER TABLE smsgaddresses ADD COLUMN pubkey VARCHAR')) - session.execute(text('UPDATE smsgaddresses SET active_ind = 1, created_at = 1')) + session.execute( + text("ALTER TABLE smsgaddresses ADD COLUMN active_ind INTEGER") + ) + session.execute( + text("ALTER TABLE smsgaddresses ADD COLUMN created_at INTEGER") + ) + session.execute(text("ALTER TABLE smsgaddresses ADD COLUMN note VARCHAR")) + session.execute(text("ALTER TABLE smsgaddresses ADD COLUMN pubkey VARCHAR")) + session.execute( + text("UPDATE smsgaddresses SET active_ind = 1, created_at = 1") + ) - session.execute(text('ALTER TABLE offers ADD COLUMN addr_to VARCHAR')) + session.execute(text("ALTER TABLE offers ADD COLUMN addr_to VARCHAR")) session.execute(text(f'UPDATE offers SET addr_to = "{self.network_addr}"')) db_version += 1 elif current_version == 11: - session.execute(text('ALTER TABLE bids ADD COLUMN chain_a_height_start INTEGER')) - session.execute(text('ALTER TABLE bids ADD COLUMN chain_b_height_start INTEGER')) - session.execute(text('ALTER TABLE bids ADD COLUMN protocol_version INTEGER')) - session.execute(text('ALTER TABLE offers ADD COLUMN protocol_version INTEGER')) - session.execute(text('ALTER TABLE transactions ADD COLUMN tx_data BLOB')) + session.execute( + text("ALTER TABLE bids ADD COLUMN chain_a_height_start INTEGER") + ) + session.execute( + text("ALTER TABLE bids ADD COLUMN chain_b_height_start INTEGER") + ) + session.execute( + text("ALTER TABLE bids ADD COLUMN protocol_version INTEGER") + ) + session.execute( + text("ALTER TABLE offers ADD COLUMN protocol_version INTEGER") + ) + session.execute(text("ALTER TABLE transactions ADD COLUMN tx_data BLOB")) db_version += 1 elif current_version == 12: - session.execute(text(''' + session.execute( + text( + """ CREATE TABLE knownidentities ( record_id INTEGER NOT NULL, address VARCHAR, @@ -168,15 +234,23 @@ def upgradeDatabase(self, db_version): note VARCHAR, updated_at BIGINT, created_at BIGINT, - PRIMARY KEY (record_id))''')) - session.execute(text('ALTER TABLE bids ADD COLUMN reject_code INTEGER')) - session.execute(text('ALTER TABLE bids ADD COLUMN rate INTEGER')) - session.execute(text('ALTER TABLE offers ADD COLUMN amount_negotiable INTEGER')) - session.execute(text('ALTER TABLE offers ADD COLUMN rate_negotiable INTEGER')) + PRIMARY KEY (record_id))""" + ) + ) + session.execute(text("ALTER TABLE bids ADD COLUMN reject_code INTEGER")) + session.execute(text("ALTER TABLE bids ADD COLUMN rate INTEGER")) + session.execute( + text("ALTER TABLE offers ADD COLUMN amount_negotiable INTEGER") + ) + session.execute( + text("ALTER TABLE offers ADD COLUMN rate_negotiable INTEGER") + ) db_version += 1 elif current_version == 13: db_version += 1 - session.execute(text(''' + session.execute( + text( + """ CREATE TABLE automationstrategies ( record_id INTEGER NOT NULL, active_ind INTEGER, @@ -188,9 +262,13 @@ def upgradeDatabase(self, db_version): note VARCHAR, created_at BIGINT, - PRIMARY KEY (record_id))''')) + PRIMARY KEY (record_id))""" + ) + ) - session.execute(text(''' + session.execute( + text( + """ CREATE TABLE automationlinks ( record_id INTEGER NOT NULL, active_ind INTEGER, @@ -205,9 +283,13 @@ def upgradeDatabase(self, db_version): note VARCHAR, created_at BIGINT, - PRIMARY KEY (record_id))''')) + PRIMARY KEY (record_id))""" + ) + ) - session.execute(text(''' + session.execute( + text( + """ CREATE TABLE history ( record_id INTEGER NOT NULL, concept_type INTEGER, @@ -216,9 +298,13 @@ def upgradeDatabase(self, db_version): note VARCHAR, created_at BIGINT, - PRIMARY KEY (record_id))''')) + PRIMARY KEY (record_id))""" + ) + ) - session.execute(text(''' + session.execute( + text( + """ CREATE TABLE bidstates ( record_id INTEGER NOT NULL, active_ind INTEGER, @@ -228,31 +314,53 @@ def upgradeDatabase(self, db_version): note VARCHAR, created_at BIGINT, - PRIMARY KEY (record_id))''')) + PRIMARY KEY (record_id))""" + ) + ) - session.execute(text('ALTER TABLE wallets ADD COLUMN active_ind INTEGER')) - session.execute(text('ALTER TABLE knownidentities ADD COLUMN active_ind INTEGER')) - session.execute(text('ALTER TABLE eventqueue RENAME TO actions')) - session.execute(text('ALTER TABLE actions RENAME COLUMN event_id TO action_id')) - session.execute(text('ALTER TABLE actions RENAME COLUMN event_type TO action_type')) - session.execute(text('ALTER TABLE actions RENAME COLUMN event_data TO action_data')) + session.execute(text("ALTER TABLE wallets ADD COLUMN active_ind INTEGER")) + session.execute( + text("ALTER TABLE knownidentities ADD COLUMN active_ind INTEGER") + ) + session.execute(text("ALTER TABLE eventqueue RENAME TO actions")) + session.execute( + text("ALTER TABLE actions RENAME COLUMN event_id TO action_id") + ) + session.execute( + text("ALTER TABLE actions RENAME COLUMN event_type TO action_type") + ) + session.execute( + text("ALTER TABLE actions RENAME COLUMN event_data TO action_data") + ) elif current_version == 14: db_version += 1 - session.execute(text('ALTER TABLE xmr_swaps ADD COLUMN coin_a_lock_release_msg_id BLOB')) - session.execute(text('ALTER TABLE xmr_swaps RENAME COLUMN coin_a_lock_refund_spend_tx_msg_id TO coin_a_lock_spend_tx_msg_id')) + session.execute( + text("ALTER TABLE xmr_swaps ADD COLUMN coin_a_lock_release_msg_id BLOB") + ) + session.execute( + text( + "ALTER TABLE xmr_swaps RENAME COLUMN coin_a_lock_refund_spend_tx_msg_id TO coin_a_lock_spend_tx_msg_id" + ) + ) elif current_version == 15: db_version += 1 - session.execute(text(''' + session.execute( + text( + """ CREATE TABLE notifications ( record_id INTEGER NOT NULL, active_ind INTEGER, event_type INTEGER, event_data BLOB, created_at BIGINT, - PRIMARY KEY (record_id))''')) + PRIMARY KEY (record_id))""" + ) + ) elif current_version == 16: db_version += 1 - session.execute(text(''' + session.execute( + text( + """ CREATE TABLE prefunded_transactions ( record_id INTEGER NOT NULL, active_ind INTEGER, @@ -262,25 +370,43 @@ def upgradeDatabase(self, db_version): tx_type INTEGER, tx_data BLOB, used_by BLOB, - PRIMARY KEY (record_id))''')) + PRIMARY KEY (record_id))""" + ) + ) elif current_version == 17: db_version += 1 - session.execute(text('ALTER TABLE knownidentities ADD COLUMN automation_override INTEGER')) - session.execute(text('ALTER TABLE knownidentities ADD COLUMN visibility_override INTEGER')) - session.execute(text('ALTER TABLE knownidentities ADD COLUMN data BLOB')) - session.execute(text('UPDATE knownidentities SET active_ind = 1')) + session.execute( + text( + "ALTER TABLE knownidentities ADD COLUMN automation_override INTEGER" + ) + ) + session.execute( + text( + "ALTER TABLE knownidentities ADD COLUMN visibility_override INTEGER" + ) + ) + session.execute(text("ALTER TABLE knownidentities ADD COLUMN data BLOB")) + session.execute(text("UPDATE knownidentities SET active_ind = 1")) elif current_version == 18: db_version += 1 - session.execute(text('ALTER TABLE xmr_split_data ADD COLUMN addr_from STRING')) - session.execute(text('ALTER TABLE xmr_split_data ADD COLUMN addr_to STRING')) + session.execute( + text("ALTER TABLE xmr_split_data ADD COLUMN addr_from STRING") + ) + session.execute( + text("ALTER TABLE xmr_split_data ADD COLUMN addr_to STRING") + ) elif current_version == 19: db_version += 1 - session.execute(text('ALTER TABLE bidstates ADD COLUMN in_error INTEGER')) - session.execute(text('ALTER TABLE bidstates ADD COLUMN swap_failed INTEGER')) - session.execute(text('ALTER TABLE bidstates ADD COLUMN swap_ended INTEGER')) + session.execute(text("ALTER TABLE bidstates ADD COLUMN in_error INTEGER")) + session.execute( + text("ALTER TABLE bidstates ADD COLUMN swap_failed INTEGER") + ) + session.execute(text("ALTER TABLE bidstates ADD COLUMN swap_ended INTEGER")) elif current_version == 20: db_version += 1 - session.execute(text(''' + session.execute( + text( + """ CREATE TABLE message_links ( record_id INTEGER NOT NULL, active_ind INTEGER, @@ -292,18 +418,22 @@ def upgradeDatabase(self, db_version): msg_type INTEGER, msg_sequence INTEGER, msg_id BLOB, - PRIMARY KEY (record_id))''')) - session.execute(text('ALTER TABLE offers ADD COLUMN bid_reversed INTEGER')) + PRIMARY KEY (record_id))""" + ) + ) + session.execute(text("ALTER TABLE offers ADD COLUMN bid_reversed INTEGER")) elif current_version == 21: db_version += 1 - session.execute(text('ALTER TABLE offers ADD COLUMN proof_utxos BLOB')) - session.execute(text('ALTER TABLE bids ADD COLUMN proof_utxos BLOB')) + session.execute(text("ALTER TABLE offers ADD COLUMN proof_utxos BLOB")) + session.execute(text("ALTER TABLE bids ADD COLUMN proof_utxos BLOB")) elif current_version == 22: db_version += 1 - session.execute(text('ALTER TABLE offers ADD COLUMN amount_to INTEGER')) + session.execute(text("ALTER TABLE offers ADD COLUMN amount_to INTEGER")) elif current_version == 23: db_version += 1 - session.execute(text(''' + session.execute( + text( + """ CREATE TABLE checkedblocks ( record_id INTEGER NOT NULL, created_at BIGINT, @@ -311,17 +441,19 @@ def upgradeDatabase(self, db_version): block_height INTEGER, block_hash BLOB, block_time INTEGER, - PRIMARY KEY (record_id))''')) - session.execute(text('ALTER TABLE bids ADD COLUMN pkhash_buyer_to BLOB')) + PRIMARY KEY (record_id))""" + ) + ) + session.execute(text("ALTER TABLE bids ADD COLUMN pkhash_buyer_to BLOB")) if current_version != db_version: self.db_version = db_version - self.setIntKV('db_version', db_version, session) + self.setIntKV("db_version", db_version, session) session.commit() session.close() session.remove() - self.log.info('Upgraded database to version {}'.format(self.db_version)) + self.log.info("Upgraded database to version {}".format(self.db_version)) continue break if db_version != CURRENT_DB_VERSION: - raise ValueError('Unable to upgrade database.') + raise ValueError("Unable to upgrade database.") diff --git a/basicswap/db_util.py b/basicswap/db_util.py index 3102a81..65a491f 100644 --- a/basicswap/db_util.py +++ b/basicswap/db_util.py @@ -15,45 +15,142 @@ def remove_expired_data(self, time_offset: int = 0): try: session = self.openSession() - active_bids_insert = self.activeBidsQueryStr(now, '', 'b2') - query_str = f''' + active_bids_insert = self.activeBidsQueryStr(now, "", "b2") + query_str = f""" SELECT o.offer_id FROM offers o WHERE o.expire_at <= :expired_at AND 0 = (SELECT COUNT(*) FROM bids b2 WHERE b2.offer_id = o.offer_id AND {active_bids_insert}) - ''' + """ num_offers = 0 num_bids = 0 - offer_rows = session.execute(text(query_str), {'expired_at': now - time_offset}) + offer_rows = session.execute(text(query_str), {"expired_at": now - time_offset}) for offer_row in offer_rows: num_offers += 1 - bid_rows = session.execute(text('SELECT bids.bid_id FROM bids WHERE bids.offer_id = :offer_id'), {'offer_id': offer_row[0]}) + bid_rows = session.execute( + text("SELECT bids.bid_id FROM bids WHERE bids.offer_id = :offer_id"), + {"offer_id": offer_row[0]}, + ) for bid_row in bid_rows: num_bids += 1 - session.execute(text('DELETE FROM transactions WHERE transactions.bid_id = :bid_id'), {'bid_id': bid_row[0]}) - session.execute(text('DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :bid_id'), {'type_ind': int(Concepts.BID), 'bid_id': bid_row[0]}) - session.execute(text('DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :bid_id'), {'type_ind': int(Concepts.BID), 'bid_id': bid_row[0]}) - session.execute(text('DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :bid_id'), {'type_ind': int(Concepts.BID), 'bid_id': bid_row[0]}) - session.execute(text('DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :bid_id'), {'type_ind': int(Concepts.BID), 'bid_id': bid_row[0]}) - session.execute(text('DELETE FROM xmr_swaps WHERE xmr_swaps.bid_id = :bid_id'), {'bid_id': bid_row[0]}) - session.execute(text('DELETE FROM actions WHERE actions.linked_id = :bid_id'), {'bid_id': bid_row[0]}) - session.execute(text('DELETE FROM addresspool WHERE addresspool.bid_id = :bid_id'), {'bid_id': bid_row[0]}) - session.execute(text('DELETE FROM xmr_split_data WHERE xmr_split_data.bid_id = :bid_id'), {'bid_id': bid_row[0]}) - session.execute(text('DELETE FROM bids WHERE bids.bid_id = :bid_id'), {'bid_id': bid_row[0]}) - session.execute(text('DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :linked_id'), {'type_ind': int(Concepts.BID), 'linked_id': bid_row[0]}) + session.execute( + text( + "DELETE FROM transactions WHERE transactions.bid_id = :bid_id" + ), + {"bid_id": bid_row[0]}, + ) + session.execute( + text( + "DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :bid_id" + ), + {"type_ind": int(Concepts.BID), "bid_id": bid_row[0]}, + ) + session.execute( + text( + "DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :bid_id" + ), + {"type_ind": int(Concepts.BID), "bid_id": bid_row[0]}, + ) + session.execute( + text( + "DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :bid_id" + ), + {"type_ind": int(Concepts.BID), "bid_id": bid_row[0]}, + ) + session.execute( + text( + "DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :bid_id" + ), + {"type_ind": int(Concepts.BID), "bid_id": bid_row[0]}, + ) + session.execute( + text("DELETE FROM xmr_swaps WHERE xmr_swaps.bid_id = :bid_id"), + {"bid_id": bid_row[0]}, + ) + session.execute( + text("DELETE FROM actions WHERE actions.linked_id = :bid_id"), + {"bid_id": bid_row[0]}, + ) + session.execute( + text("DELETE FROM addresspool WHERE addresspool.bid_id = :bid_id"), + {"bid_id": bid_row[0]}, + ) + session.execute( + text( + "DELETE FROM xmr_split_data WHERE xmr_split_data.bid_id = :bid_id" + ), + {"bid_id": bid_row[0]}, + ) + session.execute( + text("DELETE FROM bids WHERE bids.bid_id = :bid_id"), + {"bid_id": bid_row[0]}, + ) + session.execute( + text( + "DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :linked_id" + ), + {"type_ind": int(Concepts.BID), "linked_id": bid_row[0]}, + ) - session.execute(text('DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :offer_id'), {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]}) - session.execute(text('DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :offer_id'), {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]}) - session.execute(text('DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :offer_id'), {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]}) - session.execute(text('DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :offer_id'), {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]}) - session.execute(text('DELETE FROM xmr_offers WHERE xmr_offers.offer_id = :offer_id'), {'offer_id': offer_row[0]}) - session.execute(text('DELETE FROM sentoffers WHERE sentoffers.offer_id = :offer_id'), {'offer_id': offer_row[0]}) - session.execute(text('DELETE FROM actions WHERE actions.linked_id = :offer_id'), {'offer_id': offer_row[0]}) - session.execute(text('DELETE FROM offers WHERE offers.offer_id = :offer_id'), {'offer_id': offer_row[0]}) - session.execute(text('DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :offer_id'), {'type_ind': int(Concepts.OFFER), 'offer_id': offer_row[0]}) + session.execute( + text( + "DELETE FROM eventlog WHERE eventlog.linked_type = :type_ind AND eventlog.linked_id = :offer_id" + ), + {"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]}, + ) + session.execute( + text( + "DELETE FROM automationlinks WHERE automationlinks.linked_type = :type_ind AND automationlinks.linked_id = :offer_id" + ), + {"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]}, + ) + session.execute( + text( + "DELETE FROM prefunded_transactions WHERE prefunded_transactions.linked_type = :type_ind AND prefunded_transactions.linked_id = :offer_id" + ), + {"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]}, + ) + session.execute( + text( + "DELETE FROM history WHERE history.concept_type = :type_ind AND history.concept_id = :offer_id" + ), + {"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]}, + ) + session.execute( + text("DELETE FROM xmr_offers WHERE xmr_offers.offer_id = :offer_id"), + {"offer_id": offer_row[0]}, + ) + session.execute( + text("DELETE FROM sentoffers WHERE sentoffers.offer_id = :offer_id"), + {"offer_id": offer_row[0]}, + ) + session.execute( + text("DELETE FROM actions WHERE actions.linked_id = :offer_id"), + {"offer_id": offer_row[0]}, + ) + session.execute( + text("DELETE FROM offers WHERE offers.offer_id = :offer_id"), + {"offer_id": offer_row[0]}, + ) + session.execute( + text( + "DELETE FROM message_links WHERE linked_type = :type_ind AND linked_id = :offer_id" + ), + {"type_ind": int(Concepts.OFFER), "offer_id": offer_row[0]}, + ) if num_offers > 0 or num_bids > 0: - self.log.info('Removed data for {} expired offer{} and {} bid{}.'.format(num_offers, 's' if num_offers != 1 else '', num_bids, 's' if num_bids != 1 else '')) + self.log.info( + "Removed data for {} expired offer{} and {} bid{}.".format( + num_offers, + "s" if num_offers != 1 else "", + num_bids, + "s" if num_bids != 1 else "", + ) + ) - session.execute(text('DELETE FROM checkedblocks WHERE created_at <= :expired_at'), {'expired_at': now - time_offset}) + session.execute( + text("DELETE FROM checkedblocks WHERE created_at <= :expired_at"), + {"expired_at": now - time_offset}, + ) finally: self.closeSession(session) diff --git a/basicswap/ed25519_fast_util.py b/basicswap/ed25519_fast_util.py index c54ce9a..d9b2d01 100644 --- a/basicswap/ed25519_fast_util.py +++ b/basicswap/ed25519_fast_util.py @@ -13,8 +13,8 @@ def encodepoint(P): zi = edf.inv(P[2]) x = (P[0] * zi) % edf.q y = (P[1] * zi) % edf.q - y += ((x & 1) << 255) - return y.to_bytes(32, byteorder='little') + y += (x & 1) << 255 + return y.to_bytes(32, byteorder="little") def hashToEd25519(bytes_in): @@ -22,8 +22,8 @@ def hashToEd25519(bytes_in): for i in range(1000): h255 = bytearray(hashed) x_sign = 0 if h255[31] & 0x80 == 0 else 1 - h255[31] &= 0x7f # Clear top bit - y = int.from_bytes(h255, byteorder='little') + h255[31] &= 0x7F # Clear top bit + y = int.from_bytes(h255, byteorder="little") x = edf.xrecover(y, x_sign) if x == 0 and y == 1: # Skip infinity point continue @@ -33,4 +33,4 @@ def hashToEd25519(bytes_in): if edf.isoncurve(P) and edf.is_identity(edf.scalarmult(P, edf.l)): return P hashed = hashlib.sha256(hashed).digest() - raise ValueError('hashToEd25519 failed') + raise ValueError("hashToEd25519 failed") diff --git a/basicswap/explorers.py b/basicswap/explorers.py index 7b26c45..4cf2233 100644 --- a/basicswap/explorers.py +++ b/basicswap/explorers.py @@ -7,7 +7,7 @@ import json -class Explorer(): +class Explorer: def __init__(self, swapclient, coin_type, base_url): self.swapclient = swapclient self.coin_type = coin_type @@ -15,82 +15,94 @@ class Explorer(): self.log = self.swapclient.log def readURL(self, url): - self.log.debug('Explorer url: {}'.format(url)) + self.log.debug("Explorer url: {}".format(url)) return self.swapclient.readURL(url) class ExplorerInsight(Explorer): def getChainHeight(self): - return json.loads(self.readURL(self.base_url + '/sync'))['blockChainHeight'] + return json.loads(self.readURL(self.base_url + "/sync"))["blockChainHeight"] def getBlock(self, block_hash): - data = json.loads(self.readURL(self.base_url + '/block/{}'.format(block_hash))) + data = json.loads(self.readURL(self.base_url + "/block/{}".format(block_hash))) return data def getTransaction(self, txid): - data = json.loads(self.readURL(self.base_url + '/tx/{}'.format(txid))) + data = json.loads(self.readURL(self.base_url + "/tx/{}".format(txid))) return data def getBalance(self, address): - data = json.loads(self.readURL(self.base_url + '/addr/{}/balance'.format(address))) + data = json.loads( + self.readURL(self.base_url + "/addr/{}/balance".format(address)) + ) return data def lookupUnspentByAddress(self, address): - data = json.loads(self.readURL(self.base_url + '/addr/{}/utxo'.format(address))) + data = json.loads(self.readURL(self.base_url + "/addr/{}/utxo".format(address))) rv = [] for utxo in data: - rv.append({ - 'txid': utxo['txid'], - 'index': utxo['vout'], - 'height': utxo['height'], - 'n_conf': utxo['confirmations'], - 'value': utxo['satoshis'], - }) + rv.append( + { + "txid": utxo["txid"], + "index": utxo["vout"], + "height": utxo["height"], + "n_conf": utxo["confirmations"], + "value": utxo["satoshis"], + } + ) return rv class ExplorerBitAps(Explorer): def getChainHeight(self): - return json.loads(self.readURL(self.base_url + '/block/last'))['data']['block']['height'] + return json.loads(self.readURL(self.base_url + "/block/last"))["data"]["block"][ + "height" + ] def getBlock(self, block_hash): - data = json.loads(self.readURL(self.base_url + '/block/{}'.format(block_hash))) + data = json.loads(self.readURL(self.base_url + "/block/{}".format(block_hash))) return data def getTransaction(self, txid): - data = json.loads(self.readURL(self.base_url + '/transaction/{}'.format(txid))) + data = json.loads(self.readURL(self.base_url + "/transaction/{}".format(txid))) return data def getBalance(self, address): - data = json.loads(self.readURL(self.base_url + '/address/state/' + address)) - return data['data']['balance'] + data = json.loads(self.readURL(self.base_url + "/address/state/" + address)) + return data["data"]["balance"] def lookupUnspentByAddress(self, address): # Can't get unspents return only if exactly one transaction exists - data = json.loads(self.readURL(self.base_url + '/address/transactions/' + address)) + data = json.loads( + self.readURL(self.base_url + "/address/transactions/" + address) + ) try: - assert data['data']['list'] == 1 + assert data["data"]["list"] == 1 except Exception as ex: - self.log.debug('Explorer error: {}'.format(str(ex))) + self.log.debug("Explorer error: {}".format(str(ex))) return None - tx = data['data']['list'][0] - tx_data = json.loads(self.readURL(self.base_url + '/transaction/{}'.format(tx['txId'])))['data'] + tx = data["data"]["list"][0] + tx_data = json.loads( + self.readURL(self.base_url + "/transaction/{}".format(tx["txId"])) + )["data"] - for i, vout in tx_data['vOut'].items(): - if vout['address'] == address: - return [{ - 'txid': tx_data['txId'], - 'index': int(i), - 'height': tx_data['blockHeight'], - 'n_conf': tx_data['confirmations'], - 'value': vout['value'], - }] + for i, vout in tx_data["vOut"].items(): + if vout["address"] == address: + return [ + { + "txid": tx_data["txId"], + "index": int(i), + "height": tx_data["blockHeight"], + "n_conf": tx_data["confirmations"], + "value": vout["value"], + } + ] class ExplorerChainz(Explorer): def getChainHeight(self): - return int(self.readURL(self.base_url + '?q=getblockcount')) + return int(self.readURL(self.base_url + "?q=getblockcount")) def lookupUnspentByAddress(self, address): chain_height = self.getChainHeight() - self.log.debug('[rm] chain_height %d', chain_height) + self.log.debug("[rm] chain_height %d", chain_height) diff --git a/basicswap/http_server.py b/basicswap/http_server.py index 7ce3cac..bbcbdea 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019-2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -56,12 +57,12 @@ from .ui.page_identity import page_identity from .ui.page_smsgaddresses import page_smsgaddresses from .ui.page_debug import page_debug -env = Environment(loader=PackageLoader('basicswap', 'templates')) -env.filters['formatts'] = format_timestamp +env = Environment(loader=PackageLoader("basicswap", "templates")) +env.filters["formatts"] = format_timestamp def extractDomain(url): - return url.split('://', 1)[1].split('/', 1)[0] + return url.split("://", 1)[1].split("/", 1)[0] def listAvailableExplorers(swap_client): @@ -69,40 +70,47 @@ def listAvailableExplorers(swap_client): for c in Coins: if c not in chainparams: continue - for i, e in enumerate(swap_client.coin_clients[c]['explorers']): - explorers.append(('{}_{}'.format(int(c), i), getCoinName(c) + ' - ' + extractDomain(e.base_url))) + for i, e in enumerate(swap_client.coin_clients[c]["explorers"]): + explorers.append( + ( + "{}_{}".format(int(c), i), + getCoinName(c) + " - " + extractDomain(e.base_url), + ) + ) return explorers def listExplorerActions(swap_client): - actions = [('height', 'Chain Height'), - ('block', 'Get Block'), - ('tx', 'Get Transaction'), - ('balance', 'Address Balance'), - ('unspent', 'List Unspent')] + actions = [ + ("height", "Chain Height"), + ("block", "Get Block"), + ("tx", "Get Transaction"), + ("balance", "Address Balance"), + ("unspent", "List Unspent"), + ] return actions def parse_cmd(cmd: str, type_map: str): params = shlex.split(cmd) if len(params) < 1: - return '', [] + return "", [] method = params[0] typed_params = [] params = params[1:] for i, param in enumerate(params): if i >= len(type_map): - type_ind = 's' + type_ind = "s" else: type_ind = type_map[i] - if type_ind == 'i': + if type_ind == "i": typed_params.append(int(param)) - elif type_ind == 'f': + elif type_ind == "f": typed_params.append(float(param)) - elif type_ind == 'b': + elif type_ind == "b": typed_params.append(toBool(param)) - elif type_ind == 'j': + elif type_ind == "j": typed_params.append(json.loads(param)) else: typed_params.append(param) @@ -122,99 +130,112 @@ class HttpHandler(BaseHTTPRequestHandler): return os.urandom(8).hex() def checkForm(self, post_string, name, messages): - if post_string == '': + if post_string == "": return None form_data = parse.parse_qs(post_string) - form_id = form_data[b'formid'][0].decode('utf-8') + form_id = form_data[b"formid"][0].decode("utf-8") if self.server.last_form_id.get(name, None) == form_id: - messages.append('Prevented double submit for form {}.'.format(form_id)) + messages.append("Prevented double submit for form {}.".format(form_id)) return None self.server.last_form_id[name] = form_id return form_data - def render_template(self, template, args_dict, status_code=200, version=__version__): + def render_template( + self, template, args_dict, status_code=200, version=__version__ + ): swap_client = self.server.swap_client if swap_client.ws_server: - args_dict['ws_port'] = swap_client.ws_server.client_port + args_dict["ws_port"] = swap_client.ws_server.client_port if swap_client.debug: - args_dict['debug_mode'] = True + args_dict["debug_mode"] = True if swap_client.debug_ui: - args_dict['debug_ui_mode'] = True + args_dict["debug_ui_mode"] = True if swap_client.use_tor_proxy: - args_dict['use_tor_proxy'] = True + args_dict["use_tor_proxy"] = True # TODO: Cache value? try: tor_state = get_tor_established_state(swap_client) - args_dict['tor_established'] = True if tor_state == '1' else False + args_dict["tor_established"] = True if tor_state == "1" else False except Exception as e: - args_dict['tor_established'] = False + args_dict["tor_established"] = False if swap_client.debug: swap_client.log.error(f"Error getting Tor state: {str(e)}") swap_client.log.error(traceback.format_exc()) if swap_client._show_notifications: - args_dict['notifications'] = swap_client.getNotifications() + args_dict["notifications"] = swap_client.getNotifications() - if 'messages' in args_dict: + if "messages" in args_dict: messages_with_ids = [] - for msg in args_dict['messages']: + for msg in args_dict["messages"]: messages_with_ids.append((self.server.msg_id_counter, msg)) self.server.msg_id_counter += 1 - args_dict['messages'] = messages_with_ids - if 'err_messages' in args_dict: + args_dict["messages"] = messages_with_ids + if "err_messages" in args_dict: err_messages_with_ids = [] - for msg in args_dict['err_messages']: + for msg in args_dict["err_messages"]: err_messages_with_ids.append((self.server.msg_id_counter, msg)) self.server.msg_id_counter += 1 - args_dict['err_messages'] = err_messages_with_ids + args_dict["err_messages"] = err_messages_with_ids shutdown_token = os.urandom(8).hex() - self.server.session_tokens['shutdown'] = shutdown_token - args_dict['shutdown_token'] = shutdown_token + self.server.session_tokens["shutdown"] = shutdown_token + args_dict["shutdown_token"] = shutdown_token encrypted, locked = swap_client.getLockedState() - args_dict['encrypted'] = encrypted - args_dict['locked'] = locked + args_dict["encrypted"] = encrypted + args_dict["locked"] = locked if self.server.msg_id_counter >= 0x7FFFFFFF: self.server.msg_id_counter = 0 - args_dict['version'] = version + args_dict["version"] = version - self.putHeaders(status_code, 'text/html') - return bytes(template.render( - title=self.server.title, - h2=self.server.title, - form_id=self.generate_form_id(), - **args_dict, - ), 'UTF-8') + self.putHeaders(status_code, "text/html") + return bytes( + template.render( + title=self.server.title, + h2=self.server.title, + form_id=self.generate_form_id(), + **args_dict, + ), + "UTF-8", + ) def render_simple_template(self, template, args_dict): - swap_client = self.server.swap_client - return bytes(template.render( - title=self.server.title, - **args_dict, - ), 'UTF-8') + return bytes( + template.render( + title=self.server.title, + **args_dict, + ), + "UTF-8", + ) def page_info(self, info_str, post_string=None): - template = env.get_template('info.html') + template = env.get_template("info.html") swap_client = self.server.swap_client summary = swap_client.getSummary() - return self.render_template(template, { - 'title_str': 'BasicSwap Info', - 'message_str': info_str, - 'summary': summary, - }) + return self.render_template( + template, + { + "title_str": "BasicSwap Info", + "message_str": info_str, + "summary": summary, + }, + ) def page_error(self, error_str, post_string=None): - template = env.get_template('error.html') + template = env.get_template("error.html") swap_client = self.server.swap_client summary = swap_client.getSummary() - return self.render_template(template, { - 'title_str': 'BasicSwap Error', - 'message_str': error_str, - 'summary': summary, - }) + return self.render_template( + template, + { + "title_str": "BasicSwap Error", + "message_str": error_str, + "summary": summary, + }, + ) def page_explorers(self, url_split, post_string): swap_client = self.server.swap_client @@ -226,42 +247,49 @@ class HttpHandler(BaseHTTPRequestHandler): action = -1 messages = [] err_messages = [] - form_data = self.checkForm(post_string, 'explorers', err_messages) + form_data = self.checkForm(post_string, "explorers", err_messages) if form_data: - explorer = form_data[b'explorer'][0].decode('utf-8') - action = form_data[b'action'][0].decode('utf-8') + explorer = form_data[b"explorer"][0].decode("utf-8") + action = form_data[b"action"][0].decode("utf-8") - args = '' if b'args' not in form_data else form_data[b'args'][0].decode('utf-8') + args = ( + "" + if b"args" not in form_data + else form_data[b"args"][0].decode("utf-8") + ) try: - c, e = explorer.split('_') - exp = swap_client.coin_clients[Coins(int(c))]['explorers'][int(e)] - if action == 'height': + c, e = explorer.split("_") + exp = swap_client.coin_clients[Coins(int(c))]["explorers"][int(e)] + if action == "height": result = str(exp.getChainHeight()) - elif action == 'block': + elif action == "block": result = dumpj(exp.getBlock(args)) - elif action == 'tx': + elif action == "tx": result = dumpj(exp.getTransaction(args)) - elif action == 'balance': + elif action == "balance": result = dumpj(exp.getBalance(args)) - elif action == 'unspent': + elif action == "unspent": result = dumpj(exp.lookupUnspentByAddress(args)) else: - result = 'Unknown action' + result = "Unknown action" except Exception as ex: result = str(ex) - template = env.get_template('explorers.html') - return self.render_template(template, { - 'messages': messages, - 'err_messages': err_messages, - 'explorers': listAvailableExplorers(swap_client), - 'explorer': explorer, - 'actions': listExplorerActions(swap_client), - 'action': action, - 'result': result, - 'summary': summary, - }) + template = env.get_template("explorers.html") + return self.render_template( + template, + { + "messages": messages, + "err_messages": err_messages, + "explorers": listAvailableExplorers(swap_client), + "explorer": explorer, + "actions": listExplorerActions(swap_client), + "action": action, + "result": result, + "summary": summary, + }, + ) def page_rpc(self, url_split, post_string): swap_client = self.server.swap_client @@ -269,34 +297,33 @@ class HttpHandler(BaseHTTPRequestHandler): summary = swap_client.getSummary() result = None - cmd = '' + cmd = "" coin_type_selected = -1 coin_type = -1 - coin_id = -1 - call_type = 'cli' - type_map = '' + call_type = "cli" + type_map = "" messages = [] err_messages = [] - form_data = self.checkForm(post_string, 'rpc', err_messages) + form_data = self.checkForm(post_string, "rpc", err_messages) if form_data: try: - call_type = get_data_entry_or(form_data, 'call_type', 'cli') - type_map = get_data_entry_or(form_data, 'type_map', '') + call_type = get_data_entry_or(form_data, "call_type", "cli") + type_map = get_data_entry_or(form_data, "type_map", "") try: - coin_type_selected = get_data_entry(form_data, 'coin_type') - coin_type_split = coin_type_selected.split(',') + coin_type_selected = get_data_entry(form_data, "coin_type") + coin_type_split = coin_type_selected.split(",") coin_type = Coins(int(coin_type_split[0])) coin_variant = int(coin_type_split[1]) except Exception: - raise ValueError('Unknown Coin Type') + raise ValueError("Unknown Coin Type") if coin_type in (Coins.DCR,): - call_type = 'http' + call_type = "http" try: - cmd = get_data_entry(form_data, 'cmd') + cmd = get_data_entry(form_data, "cmd") except Exception: - raise ValueError('Invalid command') + raise ValueError("Invalid command") if coin_type in (Coins.XMR, Coins.WOW): ci = swap_client.ci(coin_type) arr = cmd.split(None, 1) @@ -311,10 +338,10 @@ class HttpHandler(BaseHTTPRequestHandler): params = None rv = ci.rpc2(method, params) else: - raise ValueError('Unknown RPC variant') + raise ValueError("Unknown RPC variant") result = json.dumps(rv, indent=4) else: - if call_type == 'http': + if call_type == "http": ci = swap_client.ci(coin_type) method, params = parse_cmd(cmd, type_map) if coin_variant == 1: @@ -322,50 +349,56 @@ class HttpHandler(BaseHTTPRequestHandler): elif coin_variant == 2: rv = ci.rpc_wallet_mweb(method, params) else: - if coin_type in (Coins.DCR, ): + if coin_type in (Coins.DCR,): rv = ci.rpc(method, params) else: rv = ci.rpc_wallet(method, params) if not isinstance(rv, str): rv = json.dumps(rv, indent=4) - result = cmd + '\n' + rv + result = cmd + "\n" + rv else: - result = cmd + '\n' + swap_client.callcoincli(coin_type, cmd) + result = cmd + "\n" + swap_client.callcoincli(coin_type, cmd) except Exception as ex: - result = cmd + '\n' + str(ex) + result = cmd + "\n" + str(ex) if self.server.swap_client.debug is True: self.server.swap_client.log.error(traceback.format_exc()) - template = env.get_template('rpc.html') + template = env.get_template("rpc.html") coin_available = listAvailableCoins(swap_client, with_variants=False) with_xmr: bool = any(c[0] == Coins.XMR for c in coin_available) with_wow: bool = any(c[0] == Coins.WOW for c in coin_available) - coins = [(str(c[0]) + ',0', c[1]) for c in coin_available if c[0] not in (Coins.XMR, Coins.WOW)] + coins = [ + (str(c[0]) + ",0", c[1]) + for c in coin_available + if c[0] not in (Coins.XMR, Coins.WOW) + ] if any(c[0] == Coins.DCR for c in coin_available): - coins.append((str(int(Coins.DCR)) + ',1', 'Decred Wallet')) + coins.append((str(int(Coins.DCR)) + ",1", "Decred Wallet")) if any(c[0] == Coins.LTC for c in coin_available): - coins.append((str(int(Coins.LTC)) + ',2', 'Litecoin MWEB Wallet')) + coins.append((str(int(Coins.LTC)) + ",2", "Litecoin MWEB Wallet")) if with_xmr: - coins.append((str(int(Coins.XMR)) + ',0', 'Monero')) - coins.append((str(int(Coins.XMR)) + ',1', 'Monero JSON')) - coins.append((str(int(Coins.XMR)) + ',2', 'Monero Wallet')) + coins.append((str(int(Coins.XMR)) + ",0", "Monero")) + coins.append((str(int(Coins.XMR)) + ",1", "Monero JSON")) + coins.append((str(int(Coins.XMR)) + ",2", "Monero Wallet")) if with_wow: - coins.append((str(int(Coins.WOW)) + ',0', 'Wownero')) - coins.append((str(int(Coins.WOW)) + ',1', 'Wownero JSON')) - coins.append((str(int(Coins.WOW)) + ',2', 'Wownero Wallet')) + coins.append((str(int(Coins.WOW)) + ",0", "Wownero")) + coins.append((str(int(Coins.WOW)) + ",1", "Wownero JSON")) + coins.append((str(int(Coins.WOW)) + ",2", "Wownero Wallet")) - return self.render_template(template, { - 'messages': messages, - 'err_messages': err_messages, - 'coins': coins, - 'coin_type': coin_type_selected, - 'call_type': call_type, - 'result': result, - 'messages': messages, - 'summary': summary, - }) + return self.render_template( + template, + { + "messages": messages, + "err_messages": err_messages, + "coins": coins, + "coin_type": coin_type_selected, + "call_type": call_type, + "result": result, + "summary": summary, + }, + ) def page_active(self, url_split, post_string): swap_client = self.server.swap_client @@ -373,12 +406,24 @@ class HttpHandler(BaseHTTPRequestHandler): active_swaps = swap_client.listSwapsInProgress() summary = swap_client.getSummary() - template = env.get_template('active.html') - return self.render_template(template, { - 'refresh': 30, - 'active_swaps': [(s[0].hex(), s[1], strBidState(s[2]), strTxState(s[3]), strTxState(s[4])) for s in active_swaps], - 'summary': summary, - }) + template = env.get_template("active.html") + return self.render_template( + template, + { + "refresh": 30, + "active_swaps": [ + ( + s[0].hex(), + s[1], + strBidState(s[2]), + strTxState(s[3]), + strTxState(s[4]), + ) + for s in active_swaps + ], + "summary": summary, + }, + ) def page_watched(self, url_split, post_string): swap_client = self.server.swap_client @@ -386,60 +431,68 @@ class HttpHandler(BaseHTTPRequestHandler): watched_outputs, last_scanned = swap_client.listWatchedOutputs() summary = swap_client.getSummary() - template = env.get_template('watched.html') - return self.render_template(template, { - 'refresh': 30, - 'last_scanned': [(getCoinName(ls[0]), ls[1]) for ls in last_scanned], - 'watched_outputs': [(wo[1].hex(), getCoinName(wo[0]), wo[2], wo[3], int(wo[4])) for wo in watched_outputs], - 'summary': summary, - }) + template = env.get_template("watched.html") + return self.render_template( + template, + { + "refresh": 30, + "last_scanned": [(getCoinName(ls[0]), ls[1]) for ls in last_scanned], + "watched_outputs": [ + (wo[1].hex(), getCoinName(wo[0]), wo[2], wo[3], int(wo[4])) + for wo in watched_outputs + ], + "summary": summary, + }, + ) def page_shutdown(self, url_split, post_string): swap_client = self.server.swap_client if len(url_split) > 2: token = url_split[2] - expect_token = self.server.session_tokens.get('shutdown', None) + expect_token = self.server.session_tokens.get("shutdown", None) if token != expect_token: - return self.page_info('Unexpected token, still running.') + return self.page_info("Unexpected token, still running.") swap_client.stopRunning() - return self.page_info('Shutting down') + return self.page_info("Shutting down") def page_index(self, url_split): swap_client = self.server.swap_client swap_client.checkSystemStatus() - summary = swap_client.getSummary() self.send_response(302) - self.send_header('Location', '/offers') + self.send_header("Location", "/offers") self.end_headers() - return b'' + return b"" def page_404(self, url_split): swap_client = self.server.swap_client summary = swap_client.getSummary() - template = env.get_template('404.html') - return self.render_template(template, { - 'summary': summary, - }) + template = env.get_template("404.html") + return self.render_template( + template, + { + "summary": summary, + }, + ) def putHeaders(self, status_code, content_type): self.send_response(status_code) if self.server.allow_cors: - self.send_header('Access-Control-Allow-Origin', '*') - self.send_header('Content-Type', content_type) + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Content-Type", content_type) self.end_headers() - def handle_http(self, status_code, path, post_string='', is_json=False): + def handle_http(self, status_code, path, post_string="", is_json=False): swap_client = self.server.swap_client parsed = parse.urlparse(self.path) - url_split = parsed.path.split('/') - if post_string == '' and len(parsed.query) > 0: + url_split = parsed.path.split("/") + if post_string == "" and len(parsed.query) > 0: post_string = parsed.query - if len(url_split) > 1 and url_split[1] == 'json': + if len(url_split) > 1 and url_split[1] == "json": try: - self.putHeaders(status_code, 'text/plain') + self.putHeaders(status_code, "text/plain") func = js_url_to_function(url_split) return func(self, url_split, post_string, is_json) except Exception as ex: @@ -447,37 +500,42 @@ class HttpHandler(BaseHTTPRequestHandler): swap_client.log.error(traceback.format_exc()) return js_error(self, str(ex)) - if len(url_split) > 1 and url_split[1] == 'static': + if len(url_split) > 1 and url_split[1] == "static": try: - static_path = os.path.join(os.path.dirname(__file__), 'static') - if len(url_split) > 3 and url_split[2] == 'sequence_diagrams': - with open(os.path.join(static_path, 'sequence_diagrams', url_split[3]), 'rb') as fp: - self.putHeaders(status_code, 'image/svg+xml') + static_path = os.path.join(os.path.dirname(__file__), "static") + if len(url_split) > 3 and url_split[2] == "sequence_diagrams": + with open( + os.path.join(static_path, "sequence_diagrams", url_split[3]), + "rb", + ) as fp: + self.putHeaders(status_code, "image/svg+xml") return fp.read() - elif len(url_split) > 3 and url_split[2] == 'images': + elif len(url_split) > 3 and url_split[2] == "images": filename = os.path.join(*url_split[3:]) _, extension = os.path.splitext(filename) mime_type = { - '.svg': 'image/svg+xml', - '.png': 'image/png', - '.jpg': 'image/jpeg', - '.gif': 'image/gif', - '.ico': 'image/x-icon', - }.get(extension, '') - if mime_type == '': - raise ValueError('Unknown file type ' + filename) - with open(os.path.join(static_path, 'images', filename), 'rb') as fp: + ".svg": "image/svg+xml", + ".png": "image/png", + ".jpg": "image/jpeg", + ".gif": "image/gif", + ".ico": "image/x-icon", + }.get(extension, "") + if mime_type == "": + raise ValueError("Unknown file type " + filename) + with open( + os.path.join(static_path, "images", filename), "rb" + ) as fp: self.putHeaders(status_code, mime_type) return fp.read() - elif len(url_split) > 3 and url_split[2] == 'css': + elif len(url_split) > 3 and url_split[2] == "css": filename = os.path.join(*url_split[3:]) - with open(os.path.join(static_path, 'css', filename), 'rb') as fp: - self.putHeaders(status_code, 'text/css; charset=utf-8') + with open(os.path.join(static_path, "css", filename), "rb") as fp: + self.putHeaders(status_code, "text/css; charset=utf-8") return fp.read() - elif len(url_split) > 3 and url_split[2] == 'js': + elif len(url_split) > 3 and url_split[2] == "js": filename = os.path.join(*url_split[3:]) - with open(os.path.join(static_path, 'js', filename), 'rb') as fp: - self.putHeaders(status_code, 'application/javascript') + with open(os.path.join(static_path, "js", filename), "rb") as fp: + self.putHeaders(status_code, "application/javascript") return fp.read() else: return self.page_404(url_split) @@ -492,63 +550,63 @@ class HttpHandler(BaseHTTPRequestHandler): if len(url_split) > 1: page = url_split[1] - if page == 'active': + if page == "active": return self.page_active(url_split, post_string) - if page == 'wallets': + if page == "wallets": return page_wallets(self, url_split, post_string) - if page == 'wallet': + if page == "wallet": return page_wallet(self, url_split, post_string) - if page == 'settings': + if page == "settings": return page_settings(self, url_split, post_string) - if page == 'error': + if page == "error": return self.page_error(url_split, post_string) - if page == 'info': + if page == "info": return self.page_info(url_split, post_string) - if page == 'rpc': + if page == "rpc": return self.page_rpc(url_split, post_string) - if page == 'debug': + if page == "debug": return page_debug(self, url_split, post_string) - if page == 'explorers': + if page == "explorers": return self.page_explorers(url_split, post_string) - if page == 'offer': + if page == "offer": return page_offer(self, url_split, post_string) - if page == 'offers': + if page == "offers": return page_offers(self, url_split, post_string) - if page == 'newoffer': + if page == "newoffer": return page_newoffer(self, url_split, post_string) - if page == 'sentoffers': + if page == "sentoffers": return page_offers(self, url_split, post_string, sent=True) - if page == 'bid': + if page == "bid": return page_bid(self, url_split, post_string) - if page == 'receivedbids': + if page == "receivedbids": return page_bids(self, url_split, post_string, received=True) - if page == 'sentbids': + if page == "sentbids": return page_bids(self, url_split, post_string, sent=True) - if page == 'availablebids': + if page == "availablebids": return page_bids(self, url_split, post_string, available=True) - if page == 'watched': + if page == "watched": return self.page_watched(url_split, post_string) - if page == 'smsgaddresses': + if page == "smsgaddresses": return page_smsgaddresses(self, url_split, post_string) - if page == 'identity': + if page == "identity": return page_identity(self, url_split, post_string) - if page == 'tor': + if page == "tor": return page_tor(self, url_split, post_string) - if page == 'automation': + if page == "automation": return page_automation_strategies(self, url_split, post_string) - if page == 'automationstrategy': + if page == "automationstrategy": return page_automation_strategy(self, url_split, post_string) - if page == 'newautomationstrategy': + if page == "newautomationstrategy": return page_automation_strategy_new(self, url_split, post_string) - if page == 'shutdown': + if page == "shutdown": return self.page_shutdown(url_split, post_string) - if page == 'changepassword': + if page == "changepassword": return page_changepassword(self, url_split, post_string) - if page == 'unlock': + if page == "unlock": return page_unlock(self, url_split, post_string) - if page == 'lock': + if page == "lock": return page_lock(self, url_split, post_string) - if page != '': + if page != "": return self.page_404(url_split) return self.page_index(url_split) except LockedCoinError: @@ -563,20 +621,20 @@ class HttpHandler(BaseHTTPRequestHandler): self.wfile.write(response) def do_POST(self): - post_string = self.rfile.read(int(self.headers.get('Content-Length'))) + post_string = self.rfile.read(int(self.headers.get("Content-Length"))) - is_json = True if 'json' in self.headers.get('Content-Type', '') else False + is_json = True if "json" in self.headers.get("Content-Type", "") else False response = self.handle_http(200, self.path, post_string, is_json) self.wfile.write(response) def do_HEAD(self): - self.putHeaders(200, 'text/html') + self.putHeaders(200, "text/html") def do_OPTIONS(self): - self.send_response(200, 'ok') + self.send_response(200, "ok") if self.server.allow_cors: - self.send_header('Access-Control-Allow-Origin', '*') - self.send_header('Access-Control-Allow-Headers', '*') + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Access-Control-Allow-Headers", "*") self.end_headers() @@ -590,7 +648,7 @@ class HttpThread(threading.Thread, HTTPServer): self.port_no = port_no self.allow_cors = allow_cors self.swap_client = swap_client - self.title = 'BasicSwap - ' + __version__ + self.title = "BasicSwap - " + __version__ self.last_form_id = dict() self.session_tokens = dict() self.env = env @@ -605,9 +663,9 @@ class HttpThread(threading.Thread, HTTPServer): # Send fake request conn = http.client.HTTPConnection(self.host_name, self.port_no) conn.connect() - conn.request('GET', '/none') + conn.request("GET", "/none") response = conn.getresponse() - data = response.read() + _ = response.read() conn.close() def serve_forever(self): diff --git a/basicswap/interface/base.py b/basicswap/interface/base.py index 42cd123..bb1ae63 100644 --- a/basicswap/interface/base.py +++ b/basicswap/interface/base.py @@ -14,7 +14,8 @@ from basicswap.chainparams import ( ) from basicswap.util import ( ensure, - i2b, b2i, + i2b, + b2i, make_int, format_amount, TemporaryError, @@ -26,9 +27,7 @@ from basicswap.util.ecc import ( ep, getSecretInt, ) -from coincurve.dleag import ( - verify_secp256k1_point -) +from coincurve.dleag import verify_secp256k1_point from coincurve.keys import ( PublicKey, ) @@ -67,33 +66,33 @@ class CoinInterface: def coin_name(self) -> str: coin_chainparams = chainparams[self.coin_type()] - if 'display_name' in coin_chainparams: - return coin_chainparams['display_name'] - return coin_chainparams['name'].capitalize() + if "display_name" in coin_chainparams: + return coin_chainparams["display_name"] + return coin_chainparams["name"].capitalize() def ticker(self) -> str: - ticker = chainparams[self.coin_type()]['ticker'] - if self._network == 'testnet': - ticker = 't' + ticker - elif self._network == 'regtest': - ticker = 'rt' + ticker + ticker = chainparams[self.coin_type()]["ticker"] + if self._network == "testnet": + ticker = "t" + ticker + elif self._network == "regtest": + ticker = "rt" + ticker return ticker def getExchangeTicker(self, exchange_name: str) -> str: - return chainparams[self.coin_type()]['ticker'] + return chainparams[self.coin_type()]["ticker"] def getExchangeName(self, exchange_name: str) -> str: - return chainparams[self.coin_type()]['name'] + return chainparams[self.coin_type()]["name"] def ticker_mainnet(self) -> str: - ticker = chainparams[self.coin_type()]['ticker'] + ticker = chainparams[self.coin_type()]["ticker"] return ticker def min_amount(self) -> int: - return chainparams[self.coin_type()][self._network]['min_amount'] + return chainparams[self.coin_type()][self._network]["min_amount"] def max_amount(self) -> int: - return chainparams[self.coin_type()][self._network]['max_amount'] + return chainparams[self.coin_type()][self._network]["max_amount"] def setWalletSeedWarning(self, value: bool) -> None: self._unknown_wallet_seed = value @@ -111,7 +110,7 @@ class CoinInterface: return chainparams[self.coin_type()][self._network] def has_segwit(self) -> bool: - return chainparams[self.coin_type()].get('has_segwit', True) + return chainparams[self.coin_type()].get("has_segwit", True) def use_p2shp2wsh(self) -> bool: # p2sh-p2wsh @@ -121,24 +120,26 @@ class CoinInterface: if isinstance(ex, TemporaryError): return True str_error: str = str(ex).lower() - if 'not enough unlocked money' in str_error: + if "not enough unlocked money" in str_error: return True - if 'no unlocked balance' in str_error: + if "no unlocked balance" in str_error: return True - if 'transaction was rejected by daemon' in str_error: + if "transaction was rejected by daemon" in str_error: return True - if 'invalid unlocked_balance' in str_error: + if "invalid unlocked_balance" in str_error: return True - if 'daemon is busy' in str_error: + if "daemon is busy" in str_error: return True - if 'timed out' in str_error: + if "timed out" in str_error: return True - if 'request-sent' in str_error: + if "request-sent" in str_error: return True return False def setConfTarget(self, new_conf_target: int) -> None: - ensure(new_conf_target >= 1 and new_conf_target < 33, 'Invalid conf_target value') + ensure( + new_conf_target >= 1 and new_conf_target < 33, "Invalid conf_target value" + ) self._conf_target = new_conf_target def walletRestoreHeight(self) -> int: @@ -171,30 +172,15 @@ class CoinInterface: return self._altruistic -class AdaptorSigInterface(): +class AdaptorSigInterface: def getScriptLockTxDummyWitness(self, script: bytes): - return [ - b'', - bytes(72), - bytes(72), - bytes(len(script)) - ] + return [b"", bytes(72), bytes(72), bytes(len(script))] def getScriptLockRefundSpendTxDummyWitness(self, script: bytes): - return [ - b'', - bytes(72), - bytes(72), - bytes((1,)), - bytes(len(script)) - ] + return [b"", bytes(72), bytes(72), bytes((1,)), bytes(len(script))] def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes): - return [ - bytes(72), - b'', - bytes(len(script)) - ] + return [bytes(72), b"", bytes(len(script))] class Secp256k1Interface(CoinInterface, AdaptorSigInterface): @@ -213,7 +199,7 @@ class Secp256k1Interface(CoinInterface, AdaptorSigInterface): def verifyKey(self, k: bytes) -> bool: i = b2i(k) - return (i < ep.o and i > 0) + return i < ep.o and i > 0 def verifyPubkey(self, pubkey_bytes: bytes) -> bool: return verify_secp256k1_point(pubkey_bytes) diff --git a/basicswap/interface/bch.py b/basicswap/interface/bch.py index 2d9d67b..37af903 100644 --- a/basicswap/interface/bch.py +++ b/basicswap/interface/bch.py @@ -76,28 +76,34 @@ class BCHInterface(BTCInterface): super(BCHInterface, self).__init__(coin_settings, network, swap_client) # No multiwallet support self.swap_client = swap_client - self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) + self.rpc_wallet = make_rpc_func( + self._rpcport, self._rpcauth, host=self._rpc_host + ) def has_segwit(self) -> bool: # bch does not have segwit, but we return true here to avoid extra checks in basicswap.py return True def getExchangeName(self, exchange_name: str) -> str: - return 'bitcoin-cash' + return "bitcoin-cash" - def getNewAddress(self, use_segwit: bool = False, label: str = 'swap_receive') -> str: + def getNewAddress( + self, use_segwit: bool = False, label: str = "swap_receive" + ) -> str: args = [label] - return self.rpc_wallet('getnewaddress', args) + return self.rpc_wallet("getnewaddress", args) def getUnspentsByAddr(self): unspent_addr = dict() - unspent = self.rpc_wallet('listunspent') + unspent = self.rpc_wallet("listunspent") for u in unspent: - if u.get('spendable', False) is False: + if u.get("spendable", False) is False: continue - if 'address' not in u: + if "address" not in u: continue - unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1) + unspent_addr[u["address"]] = unspent_addr.get( + u["address"], 0 + ) + self.make_int(u["amount"], r=1) return unspent_addr # returns pkh @@ -105,10 +111,10 @@ class BCHInterface(BTCInterface): return bytes(Address.from_string(address).payload) def encodeSegwitAddress(self, script): - raise ValueError('Segwit not supported') + raise ValueError("Segwit not supported") def decodeSegwitAddress(self, addr): - raise ValueError('Segwit not supported') + raise ValueError("Segwit not supported") def getSCLockScriptAddress(self, lock_script: bytes) -> str: lock_tx_dest = self.getScriptDest(lock_script) @@ -116,24 +122,33 @@ class BCHInterface(BTCInterface): if not self.isAddressMine(address, or_watch_only=True): # Expects P2WSH nested in BIP16_P2SH - ro = self.rpc('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True]) - addr_info = self.rpc('validateaddress', [address]) + self.rpc("importaddress", [lock_tx_dest.hex(), "bid lock", False, True]) return address def importWatchOnlyAddress(self, address: str, label: str): - self.rpc_wallet('importaddress', [address, label, False, True]) + self.rpc_wallet("importaddress", [address, label, False, True]) - def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str: - txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) + def createRawFundedTransaction( + self, + addr_to: str, + amount: int, + sub_fee: bool = False, + lock_unspents: bool = True, + ) -> str: + txn = self.rpc( + "createrawtransaction", [[], {addr_to: self.format_amount(amount)}] + ) options = { - 'lockUnspents': lock_unspents, + "lockUnspents": lock_unspents, # 'conf_target': self._conf_target, } if sub_fee: - options['subtractFeeFromOutputs'] = [0,] - return self.rpc_wallet('fundrawtransaction', [txn, options])['hex'] + options["subtractFeeFromOutputs"] = [ + 0, + ] + return self.rpc_wallet("fundrawtransaction", [txn, options])["hex"] def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray: # Return P2PKH @@ -145,55 +160,70 @@ class BCHInterface(BTCInterface): return self.sh_to_address(script_hash) def sh_to_address(self, sh: bytes) -> str: - assert (len(sh) == 20 or len(sh) == 32) + assert len(sh) == 20 or len(sh) == 32 network = self._network.upper() address = None if len(sh) == 20: - address = Address("P2SH20" if network == "MAINNET" else "P2SH20-" + network, sh) + address = Address( + "P2SH20" if network == "MAINNET" else "P2SH20-" + network, sh + ) else: - address = Address("P2SH32" if network == "MAINNET" else "P2SH32-" + network, sh) + address = Address( + "P2SH32" if network == "MAINNET" else "P2SH32-" + network, sh + ) return address.cash_address() def getDestForScriptHash(self, script_hash): - assert (len(script_hash) == 20 or len(script_hash) == 32) + assert len(script_hash) == 20 or len(script_hash) == 32 if len(script_hash) == 20: return CScript([OP_HASH160, script_hash, OP_EQUAL]) else: return CScript([OP_HASH256, script_hash, OP_EQUAL]) def withdrawCoin(self, value: float, addr_to: str, subfee: bool): - params = [addr_to, value, '', '', subfee, 0, False] - return self.rpc_wallet('sendtoaddress', params) + params = [addr_to, value, "", "", subfee, 0, False] + return self.rpc_wallet("sendtoaddress", params) def getBLockSpendTxFee(self, tx, fee_rate: int) -> int: add_bytes = 107 size = len(tx.serialize_with_witness()) + add_bytes pay_fee = round(fee_rate * size / 1000) - self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.') + self._log.info( + f"BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}." + ) return pay_fee def findTxnByHash(self, txid_hex: str): # Only works for wallet txns try: - rv = self.rpc('gettransaction', [txid_hex]) - except Exception as ex: - self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) + rv = self.rpc("gettransaction", [txid_hex]) + except Exception as e: # noqa: F841 + self._log.debug( + "findTxnByHash getrawtransaction failed: {}".format(txid_hex) + ) return None - if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: - block_height = self.getBlockHeader(rv['blockhash'])['height'] - return {'txid': txid_hex, 'amount': 0, 'height': block_height} + if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed: + block_height = self.getBlockHeader(rv["blockhash"])["height"] + return {"txid": txid_hex, "amount": 0, "height": block_height} return None - def getLockTxHeight(self, txid: bytes, dest_address: str, bid_amount: int, rescan_from: int, find_index: bool = False, vout: int = -1): - - ''' - TODO: BCH Watchonly - Replace with importWatchOnlyAddress when it works again - Currently importing the watchonly address only works if rescanblockchain is run on every iteration - ''' + def getLockTxHeight( + self, + txid: bytes, + dest_address: str, + bid_amount: int, + rescan_from: int, + find_index: bool = False, + vout: int = -1, + ): + """ + TODO: BCH Watchonly + Replace with importWatchOnlyAddress when it works again + Currently importing the watchonly address only works if rescanblockchain is run on every iteration + """ if txid is None: - self._log.debug('TODO: getLockTxHeight') + self._log.debug("TODO: getLockTxHeight") return None found_vout = None @@ -201,19 +231,23 @@ class BCHInterface(BTCInterface): if vout is None: test_range = range(2) else: - test_range = (vout, ) + test_range = (vout,) for try_vout in test_range: try: - txout = self.rpc('gettxout', [txid.hex(), try_vout, True]) - addresses = txout['scriptPubKey']['addresses'] + txout = self.rpc("gettxout", [txid.hex(), try_vout, True]) + addresses = txout["scriptPubKey"]["addresses"] if len(addresses) != 1 or addresses[0] != dest_address: continue - if self.make_int(txout['value']) != bid_amount: - self._log.warning('getLockTxHeight found txout {} with incorrect amount {}'.format(txid.hex(), txout['value'])) + if self.make_int(txout["value"]) != bid_amount: + self._log.warning( + "getLockTxHeight found txout {} with incorrect amount {}".format( + txid.hex(), txout["value"] + ) + ) continue found_vout = try_vout break - except Exception as e: + except Exception as e: # noqa: F841 # self._log.warning('gettxout {}'.format(e)) return None @@ -221,26 +255,29 @@ class BCHInterface(BTCInterface): return None block_height: int = 0 - confirmations: int = 0 if 'confirmations' not in txout else txout['confirmations'] + confirmations: int = ( + 0 if "confirmations" not in txout else txout["confirmations"] + ) # TODO: Better way? if confirmations > 0: block_height = self.getChainHeight() - confirmations rv = { - 'txid': txid.hex(), - 'depth': confirmations, - 'index': found_vout, - 'height': block_height} + "txid": txid.hex(), + "depth": confirmations, + "index": found_vout, + "height": block_height, + } return rv def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes, **kwargs) -> CScript: - mining_fee: int = kwargs['mining_fee'] if 'mining_fee' in kwargs else 1000 - out_1: bytes = kwargs['out_1'] - out_2: bytes = kwargs['out_2'] - public_key: bytes = kwargs['public_key'] if 'public_key' in kwargs else Kal - timelock: int = kwargs['timelock'] + mining_fee: int = kwargs["mining_fee"] if "mining_fee" in kwargs else 1000 + out_1: bytes = kwargs["out_1"] + out_2: bytes = kwargs["out_2"] + public_key: bytes = kwargs["public_key"] if "public_key" in kwargs else Kal + timelock: int = kwargs["timelock"] # fmt: off return CScript([ @@ -264,7 +301,7 @@ class BCHInterface(BTCInterface): OP_0, OP_OUTPUTVALUE, OP_SUB, OP_NUMEQUALVERIFY, - # # // Verify that any CashTokens are forwarded to the output. + # // Verify that any CashTokens are forwarded to the output. OP_0, OP_UTXOTOKENCATEGORY, OP_0, OP_OUTPUTTOKENCATEGORY, OP_EQUALVERIFY, @@ -278,7 +315,7 @@ class BCHInterface(BTCInterface): # // If sequence is not used then it is a regular swap TX. OP_0, OP_INPUTSEQUENCENUMBER, OP_NOTIF, - # // bytes aliceOutput + # // bytes aliceOutput # noqa: E131 out_1, # // Verify that the BCH and/or CashTokens are forwarded to Alice's # // output. @@ -324,7 +361,7 @@ class BCHInterface(BTCInterface): def pkh_to_address(self, pkh: bytes) -> str: # pkh is ripemd160(sha256(pk)) - assert (len(pkh) == 20) + assert len(pkh) == 20 network = self._network.upper() address = Address("P2PKH" if network == "MAINNET" else "P2PKH-" + network, pkh) @@ -334,22 +371,28 @@ class BCHInterface(BTCInterface): return self.pkh_to_address(hash160(Kbs)) def addressToLockingBytecode(self, address: str) -> bytes: - return b'\x76\xa9\x14' + bytes(Address.from_string(address).payload) + b'\x88\xac' + return ( + b"\x76\xa9\x14" + bytes(Address.from_string(address).payload) + b"\x88\xac" + ) def getSpendableBalance(self) -> int: - return self.make_int(self.rpc_wallet('getbalance', ["*", 1, False])) + return self.make_int(self.rpc_wallet("getbalance", ["*", 1, False])) def getScriptDest(self, script): return self.scriptToP2SH32LockingBytecode(script) def scriptToP2SH32LockingBytecode(self, script: Union[bytes, str]) -> bytes: - return CScript([ - OP_HASH256, - sha256(sha256(script)), - OP_EQUAL, - ]) + return CScript( + [ + OP_HASH256, + sha256(sha256(script)), + OP_EQUAL, + ] + ) - def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes: + def createSCLockTx( + self, value: int, script: bytearray, vkbv: bytes = None + ) -> bytes: tx = CTransaction() tx.nVersion = self.txVersion() tx.vout.append(self.txoType()(value, self.getScriptDest(script))) @@ -364,13 +407,22 @@ class BCHInterface(BTCInterface): else: return CScript([script]) - def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None, fee_info={}, **kwargs): + def createSCLockSpendTx( + self, + tx_lock_bytes, + script_lock, + pkh_dest, + tx_fee_rate, + vkbv=None, + fee_info={}, + **kwargs, + ): # tx_fee_rate in this context is equal to `mining_fee` contract param - ves = kwargs['ves'] if 'ves' in kwargs else None + ves = kwargs["ves"] if "ves" in kwargs else None tx_lock = self.loadTx(tx_lock_bytes) output_script = self.getScriptDest(script_lock) locked_n = findOutput(tx_lock, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx_lock.vout[locked_n].nValue tx_lock.rehash() @@ -378,67 +430,121 @@ class BCHInterface(BTCInterface): tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n), - scriptSig=self.getScriptScriptSig(script_lock, ves), - nSequence=0)) + tx.vin.append( + CTxIn( + COutPoint(tx_lock_id_int, locked_n), + scriptSig=self.getScriptScriptSig(script_lock, ves), + nSequence=0, + ) + ) - tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest))) + tx.vout.append( + self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)) + ) pay_fee = tx_fee_rate tx.vout[0].nValue = locked_coin - pay_fee size = self.getTxSize(tx) - fee_info['fee_paid'] = pay_fee - fee_info['rate_used'] = tx_fee_rate - fee_info['size'] = size + fee_info["fee_paid"] = pay_fee + fee_info["rate_used"] = tx_fee_rate + fee_info["size"] = size # vsize is the same as size for BCH - fee_info['vsize'] = size + fee_info["vsize"] = size tx.rehash() - self._log.info('createSCLockSpendTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.', - i2h(tx.sha256), tx_fee_rate, size, pay_fee) + self._log.info( + "createSCLockSpendTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.", + i2h(tx.sha256), + tx_fee_rate, + size, + pay_fee, + ) return tx.serialize_without_witness() - def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None, **kwargs): + def createSCLockRefundTx( + self, + tx_lock_bytes, + script_lock, + Kal, + Kaf, + lock1_value, + csv_val, + tx_fee_rate, + vkbv=None, + **kwargs, + ): tx_lock = CTransaction() tx_lock = self.loadTx(tx_lock_bytes) output_script = self.getScriptDest(script_lock) locked_n = findOutput(tx_lock, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx_lock.vout[locked_n].nValue tx_lock.rehash() tx_lock_id_int = tx_lock.sha256 - refund_script = kwargs['refund_lock_tx_script'] + refund_script = kwargs["refund_lock_tx_script"] tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n), - nSequence=kwargs['timelock'] if 'timelock' in kwargs else lock1_value, - scriptSig=self.getScriptScriptSig(script_lock, None))) + tx.vin.append( + CTxIn( + COutPoint(tx_lock_id_int, locked_n), + nSequence=kwargs["timelock"] if "timelock" in kwargs else lock1_value, + scriptSig=self.getScriptScriptSig(script_lock, None), + ) + ) tx.vout.append(self.txoType()(locked_coin, self.getScriptDest(refund_script))) - pay_fee = kwargs['mining_fee'] if 'mining_fee' in kwargs else tx_fee_rate + pay_fee = kwargs["mining_fee"] if "mining_fee" in kwargs else tx_fee_rate tx.vout[0].nValue = locked_coin - pay_fee size = self.getTxSize(tx) vsize = size tx.rehash() - self._log.info('createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) + self._log.info( + "createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.", + i2h(tx.sha256), + tx_fee_rate, + vsize, + pay_fee, + ) return tx.serialize_without_witness(), refund_script, tx.vout[0].nValue - def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv=None, **kwargs): + def createSCLockRefundSpendTx( + self, + tx_lock_refund_bytes, + script_lock_refund, + pkh_refund_to, + tx_fee_rate, + vkbv=None, + **kwargs, + ): # it is not possible to create the refund spend tx without the prior knowledge of the VES which is part of transaction preimage # but it is better and more secure to create a lock spend transaction committing to zero VES than returning static data - kwargs['ves'] = bytes(73) - return self.createSCLockSpendTx(tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv, **kwargs) + kwargs["ves"] = bytes(73) + return self.createSCLockSpendTx( + tx_lock_refund_bytes, + script_lock_refund, + pkh_refund_to, + tx_fee_rate, + vkbv, + **kwargs, + ) - def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None, kbsf=None): + def createSCLockRefundSpendToFTx( + self, + tx_lock_refund_bytes, + script_lock_refund, + pkh_dest, + tx_fee_rate, + vkbv=None, + kbsf=None, + ): # lock refund swipe tx # Sends the coinA locked coin to the follower @@ -446,19 +552,25 @@ class BCHInterface(BTCInterface): output_script = self.getScriptDest(script_lock_refund) locked_n = findOutput(tx_lock_refund, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx_lock_refund.vout[locked_n].nValue - mining_fee, out_1, out_2, public_key, timelock = self.extractScriptLockScriptValues(script_lock_refund) + mining_fee, out_1, out_2, public_key, timelock = ( + self.extractScriptLockScriptValues(script_lock_refund) + ) tx_lock_refund.rehash() tx_lock_refund_hash_int = tx_lock_refund.sha256 tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), - nSequence=timelock, - scriptSig=self.getScriptScriptSig(script_lock_refund, None))) + tx.vin.append( + CTxIn( + COutPoint(tx_lock_refund_hash_int, locked_n), + nSequence=timelock, + scriptSig=self.getScriptScriptSig(script_lock_refund, None), + ) + ) tx.vout.append(self.txoType()(locked_coin, CScript(out_2))) @@ -469,17 +581,37 @@ class BCHInterface(BTCInterface): tx.vout[0].nValue = locked_coin - pay_fee tx.rehash() - self._log.info('createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) + self._log.info( + "createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.", + i2h(tx.sha256), + tx_fee_rate, + vsize, + pay_fee, + ) return tx.serialize_without_witness() - def signTx(self, key_bytes: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes: + def signTx( + self, + key_bytes: bytes, + tx_bytes: bytes, + input_n: int, + prevout_script: bytes, + prevout_value: int, + ) -> bytes: # simply sign the entire tx data, as this is not a preimage signature eck = PrivateKey(key_bytes) return eck.sign(sha256(tx_bytes), hasher=None) - def verifyTxSig(self, tx_bytes: bytes, sig: bytes, K: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bool: + def verifyTxSig( + self, + tx_bytes: bytes, + sig: bytes, + K: bytes, + input_n: int, + prevout_script: bytes, + prevout_value: int, + ) -> bool: # simple ecdsa signature verification return self.verifyDataSig(tx_bytes, sig, K) @@ -498,7 +630,9 @@ class BCHInterface(BTCInterface): signature = None else: unlock_script, _ = decodePushData(script_bytes, nb) - mining_fee, out_1, out_2, public_key, timelock = self.extractScriptLockScriptValues(unlock_script) + mining_fee, out_1, out_2, public_key, timelock = ( + self.extractScriptLockScriptValues(unlock_script) + ) return signature, mining_fee, out_1, out_2, public_key, timelock @@ -510,79 +644,132 @@ class BCHInterface(BTCInterface): script_len = len(script_bytes) # TODO: stricter script_len checks - ensure_op(script_bytes[o] == OP_TXINPUTCOUNT); o += 1 - ensure_op(script_bytes[o] == OP_1); o += 1 - ensure_op(script_bytes[o] == OP_NUMEQUALVERIFY); o += 1 - ensure_op(script_bytes[o] == OP_TXOUTPUTCOUNT); o += 1 - ensure_op(script_bytes[o] == OP_1); o += 1 - ensure_op(script_bytes[o] == OP_NUMEQUALVERIFY); o += 1 - mining_fee, nb = decodeScriptNum(script_bytes, o); o += nb + ensure_op(script_bytes[o] == OP_TXINPUTCOUNT) + o += 1 + ensure_op(script_bytes[o] == OP_1) + o += 1 + ensure_op(script_bytes[o] == OP_NUMEQUALVERIFY) + o += 1 + ensure_op(script_bytes[o] == OP_TXOUTPUTCOUNT) + o += 1 + ensure_op(script_bytes[o] == OP_1) + o += 1 + ensure_op(script_bytes[o] == OP_NUMEQUALVERIFY) + o += 1 + mining_fee, nb = decodeScriptNum(script_bytes, o) + o += nb - ensure_op(script_bytes[o] == OP_0); o += 1 - ensure_op(script_bytes[o] == OP_UTXOVALUE); o += 1 - ensure_op(script_bytes[o] == OP_0); o += 1 - ensure_op(script_bytes[o] == OP_OUTPUTVALUE); o += 1 - ensure_op(script_bytes[o] == OP_SUB); o += 1 - ensure_op(script_bytes[o] == OP_NUMEQUALVERIFY); o += 1 + ensure_op(script_bytes[o] == OP_0) + o += 1 + ensure_op(script_bytes[o] == OP_UTXOVALUE) + o += 1 + ensure_op(script_bytes[o] == OP_0) + o += 1 + ensure_op(script_bytes[o] == OP_OUTPUTVALUE) + o += 1 + ensure_op(script_bytes[o] == OP_SUB) + o += 1 + ensure_op(script_bytes[o] == OP_NUMEQUALVERIFY) + o += 1 - ensure_op(script_bytes[o] == OP_0); o += 1 - ensure_op(script_bytes[o] == OP_UTXOTOKENCATEGORY); o += 1 - ensure_op(script_bytes[o] == OP_0); o += 1 - ensure_op(script_bytes[o] == OP_OUTPUTTOKENCATEGORY); o += 1 + ensure_op(script_bytes[o] == OP_0) + o += 1 + ensure_op(script_bytes[o] == OP_UTXOTOKENCATEGORY) + o += 1 + ensure_op(script_bytes[o] == OP_0) + o += 1 + ensure_op(script_bytes[o] == OP_OUTPUTTOKENCATEGORY) + o += 1 - ensure_op(script_bytes[o] == OP_EQUALVERIFY); o += 1 - ensure_op(script_bytes[o] == OP_0); o += 1 - ensure_op(script_bytes[o] == OP_UTXOTOKENCOMMITMENT); o += 1 - ensure_op(script_bytes[o] == OP_0); o += 1 - ensure_op(script_bytes[o] == OP_OUTPUTTOKENCOMMITMENT); o += 1 - ensure_op(script_bytes[o] == OP_EQUALVERIFY); o += 1 - ensure_op(script_bytes[o] == OP_0); o += 1 - ensure_op(script_bytes[o] == OP_UTXOTOKENAMOUNT); o += 1 - ensure_op(script_bytes[o] == OP_0); o += 1 - ensure_op(script_bytes[o] == OP_OUTPUTTOKENAMOUNT); o += 1 - ensure_op(script_bytes[o] == OP_NUMEQUALVERIFY); o += 1 + ensure_op(script_bytes[o] == OP_EQUALVERIFY) + o += 1 + ensure_op(script_bytes[o] == OP_0) + o += 1 + ensure_op(script_bytes[o] == OP_UTXOTOKENCOMMITMENT) + o += 1 + ensure_op(script_bytes[o] == OP_0) + o += 1 + ensure_op(script_bytes[o] == OP_OUTPUTTOKENCOMMITMENT) + o += 1 + ensure_op(script_bytes[o] == OP_EQUALVERIFY) + o += 1 + ensure_op(script_bytes[o] == OP_0) + o += 1 + ensure_op(script_bytes[o] == OP_UTXOTOKENAMOUNT) + o += 1 + ensure_op(script_bytes[o] == OP_0) + o += 1 + ensure_op(script_bytes[o] == OP_OUTPUTTOKENAMOUNT) + o += 1 + ensure_op(script_bytes[o] == OP_NUMEQUALVERIFY) + o += 1 - ensure_op(script_bytes[o] == OP_0); o += 1 - ensure_op(script_bytes[o] == OP_INPUTSEQUENCENUMBER); o += 1 - ensure_op(script_bytes[o] == OP_NOTIF); o += 1 - out_1, nb = decodePushData(script_bytes, o); o += nb + ensure_op(script_bytes[o] == OP_0) + o += 1 + ensure_op(script_bytes[o] == OP_INPUTSEQUENCENUMBER) + o += 1 + ensure_op(script_bytes[o] == OP_NOTIF) + o += 1 + out_1, nb = decodePushData(script_bytes, o) + o += nb - ensure_op(script_bytes[o] == OP_0); o += 1 - ensure_op(script_bytes[o] == OP_OUTPUTBYTECODE); o += 1 - ensure_op(script_bytes[o] == OP_OVER); o += 1 - ensure_op(script_bytes[o] == OP_EQUALVERIFY); o += 1 - public_key, nb = decodePushData(script_bytes, o); o += nb - ensure_op(script_bytes[o] == OP_CHECKDATASIG); o += 1 + ensure_op(script_bytes[o] == OP_0) + o += 1 + ensure_op(script_bytes[o] == OP_OUTPUTBYTECODE) + o += 1 + ensure_op(script_bytes[o] == OP_OVER) + o += 1 + ensure_op(script_bytes[o] == OP_EQUALVERIFY) + o += 1 + public_key, nb = decodePushData(script_bytes, o) + o += nb + ensure_op(script_bytes[o] == OP_CHECKDATASIG) + o += 1 - ensure_op(script_bytes[o] == OP_ELSE); o += 1 - timelock, nb = decodeScriptNum(script_bytes, o); o += nb - ensure_op(script_bytes[o] == OP_CHECKSEQUENCEVERIFY); o += 1 - ensure_op(script_bytes[o] == OP_DROP); o += 1 + ensure_op(script_bytes[o] == OP_ELSE) + o += 1 + timelock, nb = decodeScriptNum(script_bytes, o) + o += nb + ensure_op(script_bytes[o] == OP_CHECKSEQUENCEVERIFY) + o += 1 + ensure_op(script_bytes[o] == OP_DROP) + o += 1 - out_2, nb = decodePushData(script_bytes, o); o += nb + out_2, nb = decodePushData(script_bytes, o) + o += nb - ensure_op(script_bytes[o] == OP_0); o += 1 - ensure_op(script_bytes[o] == OP_OUTPUTBYTECODE); o += 1 - ensure_op(script_bytes[o] == OP_EQUAL); o += 1 + ensure_op(script_bytes[o] == OP_0) + o += 1 + ensure_op(script_bytes[o] == OP_OUTPUTBYTECODE) + o += 1 + ensure_op(script_bytes[o] == OP_EQUAL) + o += 1 - ensure_op(script_bytes[o] == OP_ENDIF); o += 1 + ensure_op(script_bytes[o] == OP_ENDIF) + o += 1 - ensure(o == script_len, 'Unexpected script length') + ensure(o == script_len, "Unexpected script length") - ensure(mining_fee >= 700 and mining_fee <= 10000, 'Bad mining_fee') - ensure(len(out_1) == 25, 'Bad out_1') - ensure(len(out_2) == 25 or len(out_2) == 35, 'Bad out_2') - ensure(len(public_key) == 33, 'Bad public_key') - ensure(timelock >= 0, 'Bad timelock') + ensure(mining_fee >= 700 and mining_fee <= 10000, "Bad mining_fee") + ensure(len(out_1) == 25, "Bad out_1") + ensure(len(out_2) == 25 or len(out_2) == 35, "Bad out_2") + ensure(len(public_key) == 33, "Bad public_key") + ensure(timelock >= 0, "Bad timelock") return mining_fee, out_1, out_2, public_key, timelock - def verifySCLockTx(self, tx_bytes, script_out, - swap_value, - Kal, Kaf, - feerate, - check_lock_tx_inputs, vkbv=None, - **kwargs): + def verifySCLockTx( + self, + tx_bytes, + script_out, + swap_value, + Kal, + Kaf, + feerate, + check_lock_tx_inputs, + vkbv=None, + **kwargs, + ): # Verify: # @@ -593,38 +780,54 @@ class BCHInterface(BTCInterface): tx = self.loadTx(tx_bytes) txid = self.getTxid(tx) - self._log.info('Verifying lock tx: {}.'.format(b2h(txid))) + self._log.info("Verifying lock tx: {}.".format(b2h(txid))) - ensure(tx.nVersion == self.txVersion(), 'Bad version') - ensure(tx.nLockTime == 0, 'Bad nLockTime') # TODO match txns created by cores + ensure(tx.nVersion == self.txVersion(), "Bad version") + ensure(tx.nLockTime == 0, "Bad nLockTime") # TODO match txns created by cores script_pk = self.getScriptDest(script_out) locked_n = findOutput(tx, script_pk) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx.vout[locked_n].nValue # Check value - ensure(locked_coin == swap_value, 'Bad locked value') + ensure(locked_coin == swap_value, "Bad locked value") # Check script - mining_fee: int = kwargs['mining_fee'] if 'mining_fee' in kwargs else 1000 - out_1: bytes = kwargs['out_1'] - out_2: bytes = kwargs['out_2'] - public_key: bytes = kwargs['public_key'] if 'public_key' in kwargs else Kal - timelock: int = kwargs['timelock'] + mining_fee: int = kwargs["mining_fee"] if "mining_fee" in kwargs else 1000 + out_1: bytes = kwargs["out_1"] + out_2: bytes = kwargs["out_2"] + public_key: bytes = kwargs["public_key"] if "public_key" in kwargs else Kal + timelock: int = kwargs["timelock"] - _mining_fee, _out_1, _out_2, _public_key, _timelock = self.extractScriptLockScriptValues(script_out) - ensure(mining_fee == _mining_fee, 'mining mismatch fee') - ensure(out_1 == _out_1, 'out_1 mismatch') - ensure(out_2 == _out_2, 'out_2 mismatch') - ensure(public_key == _public_key, 'public_key mismatch') - ensure(timelock == _timelock, 'timelock mismatch') + _mining_fee, _out_1, _out_2, _public_key, _timelock = ( + self.extractScriptLockScriptValues(script_out) + ) + ensure(mining_fee == _mining_fee, "mining mismatch fee") + ensure(out_1 == _out_1, "out_1 mismatch") + ensure(out_2 == _out_2, "out_2 mismatch") + ensure(public_key == _public_key, "public_key mismatch") + ensure(timelock == _timelock, "timelock mismatch") return txid, locked_n - def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out, - prevout_id, prevout_n, prevout_seq, prevout_script, - Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv=None, **kwargs): + def verifySCLockRefundTx( + self, + tx_bytes, + lock_tx_bytes, + script_out, + prevout_id, + prevout_n, + prevout_seq, + prevout_script, + Kal, + Kaf, + csv_val_expect, + swap_value, + feerate, + vkbv=None, + **kwargs, + ): # Verify: # Must have only one input with correct prevout and sequence # Must have only one output to the p2wsh of the lock refund script @@ -632,138 +835,181 @@ class BCHInterface(BTCInterface): tx = self.loadTx(tx_bytes) txid = self.getTxid(tx) - self._log.info('Verifying lock refund tx: {}.'.format(b2h(txid))) + self._log.info("Verifying lock refund tx: {}.".format(b2h(txid))) - ensure(tx.nVersion == self.txVersion(), 'Bad version') - ensure(tx.nLockTime == 0, 'nLockTime not 0') - ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') + ensure(tx.nVersion == self.txVersion(), "Bad version") + ensure(tx.nLockTime == 0, "nLockTime not 0") + ensure(len(tx.vin) == 1, "tx doesn't have one input") - ensure(tx.vin[0].nSequence == prevout_seq, 'Bad input nSequence') - ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script, None), 'Input scriptsig mismatch') - ensure(tx.vin[0].prevout.hash == b2i(prevout_id) and tx.vin[0].prevout.n == prevout_n, 'Input prevout mismatch') + ensure(tx.vin[0].nSequence == prevout_seq, "Bad input nSequence") + ensure( + tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script, None), + "Input scriptsig mismatch", + ) + ensure( + tx.vin[0].prevout.hash == b2i(prevout_id) + and tx.vin[0].prevout.n == prevout_n, + "Input prevout mismatch", + ) - ensure(len(tx.vout) == 1, 'tx doesn\'t have one output') + ensure(len(tx.vout) == 1, "tx doesn't have one output") script_pk = self.getScriptDest(script_out) locked_n = findOutput(tx, script_pk) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx.vout[locked_n].nValue # Check script - mining_fee: int = kwargs['mining_fee'] if 'mining_fee' in kwargs else 1000 - out_1: bytes = kwargs['out_1'] - out_2: bytes = kwargs['out_2'] - public_key: bytes = kwargs['public_key'] if 'public_key' in kwargs else Kal - timelock: int = kwargs['timelock'] + mining_fee: int = kwargs["mining_fee"] if "mining_fee" in kwargs else 1000 + out_1: bytes = kwargs["out_1"] + out_2: bytes = kwargs["out_2"] + public_key: bytes = kwargs["public_key"] if "public_key" in kwargs else Kal + timelock: int = kwargs["timelock"] - _mining_fee, _out_1, _out_2, _public_key, _timelock = self.extractScriptLockScriptValues(script_out) - ensure(mining_fee == _mining_fee, 'mining mismatch fee') - ensure(out_1 == _out_1, 'out_1 mismatch') - ensure(out_2 == _out_2, 'out_2 mismatch') - ensure(public_key == _public_key, 'public_key mismatch') - ensure(timelock == _timelock, 'timelock mismatch') + _mining_fee, _out_1, _out_2, _public_key, _timelock = ( + self.extractScriptLockScriptValues(script_out) + ) + ensure(mining_fee == _mining_fee, "mining mismatch fee") + ensure(out_1 == _out_1, "out_1 mismatch") + ensure(out_2 == _out_2, "out_2 mismatch") + ensure(public_key == _public_key, "public_key mismatch") + ensure(timelock == _timelock, "timelock mismatch") fee_paid = locked_coin - mining_fee - assert (fee_paid > 0) + assert fee_paid > 0 size = self.getTxSize(tx) vsize = size - self._log.info('tx amount, vsize, fee: %ld, %ld, %ld', locked_coin, vsize, fee_paid) + self._log.info( + "tx amount, vsize, fee: %ld, %ld, %ld", locked_coin, vsize, fee_paid + ) return txid, locked_coin, locked_n - def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes, - lock_refund_tx_id, prevout_script, - Kal, - prevout_n, prevout_value, feerate, vkbv=None, **kwargs): + def verifySCLockRefundSpendTx( + self, + tx_bytes, + lock_refund_tx_bytes, + lock_refund_tx_id, + prevout_script, + Kal, + prevout_n, + prevout_value, + feerate, + vkbv=None, + **kwargs, + ): # Verify: # Must have only one input with correct prevout (n is always 0) and sequence # Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr tx = self.loadTx(tx_bytes) txid = self.getTxid(tx) - self._log.info('Verifying lock refund spend tx: {}.'.format(b2h(txid))) + self._log.info("Verifying lock refund spend tx: {}.".format(b2h(txid))) - ensure(tx.nVersion == self.txVersion(), 'Bad version') - ensure(tx.nLockTime == 0, 'nLockTime not 0') - ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') + ensure(tx.nVersion == self.txVersion(), "Bad version") + ensure(tx.nLockTime == 0, "nLockTime not 0") + ensure(len(tx.vin) == 1, "tx doesn't have one input") - ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence') - ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script, bytes(73)), 'Input scriptsig mismatch') - ensure(tx.vin[0].prevout.hash == b2i(lock_refund_tx_id) and tx.vin[0].prevout.n == 0, 'Input prevout mismatch') + ensure(tx.vin[0].nSequence == 0, "Bad input nSequence") + ensure( + tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script, bytes(73)), + "Input scriptsig mismatch", + ) + ensure( + tx.vin[0].prevout.hash == b2i(lock_refund_tx_id) + and tx.vin[0].prevout.n == 0, + "Input prevout mismatch", + ) - ensure(len(tx.vout) == 1, 'tx doesn\'t have one output') + ensure(len(tx.vout) == 1, "tx doesn't have one output") # Check script - mining_fee: int = kwargs['mining_fee'] if 'mining_fee' in kwargs else 1000 - out_1: bytes = kwargs['out_1'] - out_2: bytes = kwargs['out_2'] - public_key: bytes = kwargs['public_key'] if 'public_key' in kwargs else Kal - timelock: int = kwargs['timelock'] + mining_fee: int = kwargs["mining_fee"] if "mining_fee" in kwargs else 1000 + out_1: bytes = kwargs["out_1"] + out_2: bytes = kwargs["out_2"] + public_key: bytes = kwargs["public_key"] if "public_key" in kwargs else Kal + timelock: int = kwargs["timelock"] - _mining_fee, _out_1, _out_2, _public_key, _timelock = self.extractScriptLockScriptValues(prevout_script) - ensure(mining_fee == _mining_fee, 'mining mismatch fee') - ensure(out_1 == _out_1, 'out_1 mismatch') - ensure(out_2 == _out_2, 'out_2 mismatch') - ensure(public_key == _public_key, 'public_key mismatch') - ensure(timelock == _timelock, 'timelock mismatch') + _mining_fee, _out_1, _out_2, _public_key, _timelock = ( + self.extractScriptLockScriptValues(prevout_script) + ) + ensure(mining_fee == _mining_fee, "mining mismatch fee") + ensure(out_1 == _out_1, "out_1 mismatch") + ensure(out_2 == _out_2, "out_2 mismatch") + ensure(public_key == _public_key, "public_key mismatch") + ensure(timelock == _timelock, "timelock mismatch") tx_value = tx.vout[0].nValue fee_paid = tx_value - mining_fee - assert (fee_paid > 0) + assert fee_paid > 0 size = self.getTxSize(tx) vsize = size - self._log.info('tx amount, vsize, fee: %ld, %ld, %ld', tx_value, vsize, fee_paid) + self._log.info( + "tx amount, vsize, fee: %ld, %ld, %ld", tx_value, vsize, fee_paid + ) return True - def verifySCLockSpendTx(self, tx_bytes, - lock_tx_bytes, lock_tx_script, - a_pkhash_f, feerate, vkbv=None): + def verifySCLockSpendTx( + self, tx_bytes, lock_tx_bytes, lock_tx_script, a_pkhash_f, feerate, vkbv=None + ): # Verify: # Must have only one input with correct prevout (n is always 0) and sequence # Must have only one output with destination and amount tx = self.loadTx(tx_bytes) txid = self.getTxid(tx) - self._log.info('Verifying lock spend tx: {}.'.format(b2h(txid))) + self._log.info("Verifying lock spend tx: {}.".format(b2h(txid))) - ensure(tx.nVersion == self.txVersion(), 'Bad version') - ensure(tx.nLockTime == 0, 'nLockTime not 0') - ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') + ensure(tx.nVersion == self.txVersion(), "Bad version") + ensure(tx.nLockTime == 0, "nLockTime not 0") + ensure(len(tx.vin) == 1, "tx doesn't have one input") lock_tx = self.loadTx(lock_tx_bytes) - lock_tx_id = self.getTxid(lock_tx) output_script = self.getScriptDest(lock_tx_script) locked_n = findOutput(lock_tx, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = lock_tx.vout[locked_n].nValue - ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence') - ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(lock_tx_script), 'Input scriptsig mismatch') + ensure(tx.vin[0].nSequence == 0, "Bad input nSequence") + ensure( + tx.vin[0].scriptSig == self.getScriptScriptSig(lock_tx_script), + "Input scriptsig mismatch", + ) # allow for this mismatch in BCH, since the lock txid will get changed after signing # ensure(tx.vin[0].prevout.hash == b2i(lock_tx_id) and tx.vin[0].prevout.n == locked_n, 'Input prevout mismatch') - ensure(len(tx.vout) == 1, 'tx doesn\'t have one output') + ensure(len(tx.vout) == 1, "tx doesn't have one output") p2pkh = self.getScriptForPubkeyHash(a_pkhash_f) - ensure(tx.vout[0].scriptPubKey == p2pkh, 'Bad output destination') + ensure(tx.vout[0].scriptPubKey == p2pkh, "Bad output destination") # The value of the lock tx output should already be verified, if the fee is as expected the difference will be the correct amount fee_paid = locked_coin - tx.vout[0].nValue - assert (fee_paid > 0) + assert fee_paid > 0 size = self.getTxSize(tx) vsize = size - self._log.info('tx amount, vsize, fee: %ld, %ld, %ld', tx.vout[0].nValue, vsize, fee_paid) + self._log.info( + "tx amount, vsize, fee: %ld, %ld, %ld", tx.vout[0].nValue, vsize, fee_paid + ) return True - def signTxOtVES(self, key_sign: bytes, pubkey_encrypt: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes: + def signTxOtVES( + self, + key_sign: bytes, + pubkey_encrypt: bytes, + tx_bytes: bytes, + input_n: int, + prevout_script: bytes, + prevout_value: int, + ) -> bytes: _, out_1, _, _, _ = self.extractScriptLockScriptValues(prevout_script) msg = sha256(out_1) @@ -775,7 +1021,16 @@ class BCHInterface(BTCInterface): def recoverEncKey(self, esig, sig, K): return ecdsaotves_rec_enc_key(K, esig, sig) - def verifyTxOtVES(self, tx_bytes: bytes, ct: bytes, Ks: bytes, Ke: bytes, input_n: int, prevout_script: bytes, prevout_value): + def verifyTxOtVES( + self, + tx_bytes: bytes, + ct: bytes, + Ks: bytes, + Ke: bytes, + input_n: int, + prevout_script: bytes, + prevout_value, + ): _, out_1, _, _, _ = self.extractScriptLockScriptValues(prevout_script) msg = sha256(out_1) @@ -783,30 +1038,46 @@ class BCHInterface(BTCInterface): def extractLeaderSig(self, tx_bytes: bytes) -> bytes: tx = self.loadTx(tx_bytes) - signature, _, _, _, _, _ = self.extractScriptLockScriptValuesFromScriptSig(tx.vin[0].scriptSig) + signature, _, _, _, _, _ = self.extractScriptLockScriptValuesFromScriptSig( + tx.vin[0].scriptSig + ) return signature def extractFollowerSig(self, tx_bytes: bytes) -> bytes: tx = self.loadTx(tx_bytes) - signature, _, _, _, _, _ = self.extractScriptLockScriptValuesFromScriptSig(tx.vin[0].scriptSig) + signature, _, _, _, _, _ = self.extractScriptLockScriptValuesFromScriptSig( + tx.vin[0].scriptSig + ) return signature def isSpendingLockTx(self, spend_tx: CTransaction) -> bool: - signature, _, _, _, _, _ = self.extractScriptLockScriptValuesFromScriptSig(spend_tx.vin[0].scriptSig) + signature, _, _, _, _, _ = self.extractScriptLockScriptValuesFromScriptSig( + spend_tx.vin[0].scriptSig + ) return spend_tx.vin[0].nSequence == 0 and signature is not None def isSpendingLockRefundTx(self, spend_tx: CTransaction) -> bool: - signature, _, _, _, _, _ = self.extractScriptLockScriptValuesFromScriptSig(spend_tx.vin[0].scriptSig) + signature, _, _, _, _, _ = self.extractScriptLockScriptValuesFromScriptSig( + spend_tx.vin[0].scriptSig + ) return spend_tx.vin[0].nSequence == 0 and signature is not None def isTxExistsError(self, err_str: str) -> bool: - return 'transaction already in block chain' in err_str + return "transaction already in block chain" in err_str def getRefundOutputScript(self, xmr_swap) -> bytes: - _, out_1, _, _, _ = self.extractScriptLockScriptValues(xmr_swap.a_lock_refund_tx_script) + _, out_1, _, _, _ = self.extractScriptLockScriptValues( + xmr_swap.a_lock_refund_tx_script + ) return out_1 - def createMercyTx(self, refund_swipe_tx_bytes: bytes, refund_swipe_tx_id: bytes, lock_refund_tx_script: bytes, keyshare: bytes) -> str: + def createMercyTx( + self, + refund_swipe_tx_bytes: bytes, + refund_swipe_tx_id: bytes, + lock_refund_tx_script: bytes, + keyshare: bytes, + ) -> str: refund_swipe_tx = self.loadTx(refund_swipe_tx_bytes) refund_output_value = refund_swipe_tx.vout[0].nValue refund_output_script = refund_swipe_tx.vout[0].scriptPubKey @@ -822,11 +1093,15 @@ class BCHInterface(BTCInterface): tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(b2i(refund_swipe_tx_id), 0), - nSequence=0, - scriptSig=CScript(out_1))) + tx.vin.append( + CTxIn( + COutPoint(b2i(refund_swipe_tx_id), 0), + nSequence=0, + scriptSig=CScript(out_1), + ) + ) - tx.vout.append(self.txoType()(0, CScript([OP_RETURN, b'XBSW', keyshare]))) + tx.vout.append(self.txoType()(0, CScript([OP_RETURN, b"XBSW", keyshare]))) tx.vout.append(self.txoType()(dust_limit, CScript(out_1))) tx.vout.append(self.txoType()(outValue, refund_output_script)) @@ -836,8 +1111,13 @@ class BCHInterface(BTCInterface): pay_fee = size tx.rehash() - self._log.info('createMercyTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - i2h(tx.sha256), 1, vsize, pay_fee) + self._log.info( + "createMercyTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.", + i2h(tx.sha256), + 1, + vsize, + pay_fee, + ) txHex = tx.serialize_without_witness() return self.signTxWithWallet(txHex) diff --git a/basicswap/interface/btc.py b/basicswap/interface/btc.py index 3daea8a..2e7e263 100644 --- a/basicswap/interface/btc.py +++ b/basicswap/interface/btc.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020-2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -25,10 +26,14 @@ from basicswap.interface.base import ( ) from basicswap.util import ( ensure, - b2h, i2b, b2i, i2h, + b2h, + i2b, + b2i, + i2h, ) from basicswap.util.ecc import ( - pointToCPK, CPKToPoint, + pointToCPK, + CPKToPoint, ) from basicswap.util.script import ( decodeScriptNum, @@ -55,7 +60,7 @@ from coincurve.ecdsaotves import ( ecdsaotves_enc_sign, ecdsaotves_enc_verify, ecdsaotves_dec_sig, - ecdsaotves_rec_enc_key + ecdsaotves_rec_enc_key, ) from basicswap.contrib.test_framework.messages import ( @@ -67,32 +72,35 @@ from basicswap.contrib.test_framework.messages import ( CTxOut, ) from basicswap.contrib.test_framework.script import ( - CScript, CScriptOp, - OP_IF, OP_ELSE, OP_ENDIF, - OP_0, OP_2, + CScript, + CScriptOp, + OP_IF, + OP_ELSE, + OP_ENDIF, + OP_0, + OP_2, OP_CHECKSIG, OP_CHECKMULTISIG, OP_CHECKSEQUENCEVERIFY, OP_DROP, - OP_HASH160, OP_EQUAL, + OP_HASH160, + OP_EQUAL, OP_RETURN, SIGHASH_ALL, SegwitV0SignatureHash, ) -from basicswap.basicswap_util import ( - TxLockTypes -) +from basicswap.basicswap_util import TxLockTypes from basicswap.chainparams import Coins from basicswap.rpc import make_rpc_func, openrpc SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds -SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22) -SEQUENCE_LOCKTIME_MASK = 0x0000ffff +SEQUENCE_LOCKTIME_TYPE_FLAG = 1 << 22 +SEQUENCE_LOCKTIME_MASK = 0x0000FFFF -def ensure_op(v, err_string='Bad opcode'): +def ensure_op(v, err_string="Bad opcode"): ensure(v, err_string) @@ -116,21 +124,23 @@ def find_vout_for_address_from_txobj(tx_obj, addr: str) -> int: elif "address" in scriptPubKey: if addr == scriptPubKey["address"]: return i - raise RuntimeError("Vout not found for address: txid={}, addr={}".format(tx_obj['txid'], addr)) + raise RuntimeError( + "Vout not found for address: txid={}, addr={}".format(tx_obj["txid"], addr) + ) def extractScriptLockScriptValues(script_bytes: bytes) -> (bytes, bytes): script_len = len(script_bytes) - ensure(script_len == 71, 'Bad script length') + ensure(script_len == 71, "Bad script length") o = 0 ensure_op(script_bytes[o] == OP_2) ensure_op(script_bytes[o + 1] == 33) o += 2 - pk1 = script_bytes[o: o + 33] + pk1 = script_bytes[o : o + 33] o += 33 ensure_op(script_bytes[o] == 33) o += 1 - pk2 = script_bytes[o: o + 33] + pk2 = script_bytes[o : o + 33] o += 33 ensure_op(script_bytes[o] == OP_2) ensure_op(script_bytes[o + 1] == OP_CHECKMULTISIG) @@ -140,13 +150,13 @@ def extractScriptLockScriptValues(script_bytes: bytes) -> (bytes, bytes): def extractScriptLockRefundScriptValues(script_bytes: bytes): script_len = len(script_bytes) - ensure(script_len > 73, 'Bad script length') + ensure(script_len > 73, "Bad script length") ensure_op(script_bytes[0] == OP_IF) ensure_op(script_bytes[1] == OP_2) ensure_op(script_bytes[2] == 33) - pk1 = script_bytes[3: 3 + 33] + pk1 = script_bytes[3 : 3 + 33] ensure_op(script_bytes[36] == 33) - pk2 = script_bytes[37: 37 + 33] + pk2 = script_bytes[37 : 37 + 33] ensure_op(script_bytes[70] == OP_2) ensure_op(script_bytes[71] == OP_CHECKMULTISIG) ensure_op(script_bytes[72] == OP_ELSE) @@ -154,14 +164,14 @@ def extractScriptLockRefundScriptValues(script_bytes: bytes): csv_val, nb = decodeScriptNum(script_bytes, o) o += nb - ensure(script_len == o + 5 + 33, 'Bad script length') # Fails if script too long + ensure(script_len == o + 5 + 33, "Bad script length") # Fails if script too long ensure_op(script_bytes[o] == OP_CHECKSEQUENCEVERIFY) o += 1 ensure_op(script_bytes[o] == OP_DROP) o += 1 ensure_op(script_bytes[o] == 33) o += 1 - pk3 = script_bytes[o: o + 33] + pk3 = script_bytes[o : o + 33] o += 33 ensure_op(script_bytes[o] == OP_CHECKSIG) o += 1 @@ -221,23 +231,25 @@ class BTCInterface(Secp256k1Interface): @staticmethod def getExpectedSequence(lockType: int, lockVal: int) -> int: - ensure(lockVal >= 1, 'Bad lockVal') + ensure(lockVal >= 1, "Bad lockVal") if lockType == TxLockTypes.SEQUENCE_LOCK_BLOCKS: return lockVal if lockType == TxLockTypes.SEQUENCE_LOCK_TIME: secondsLocked = lockVal # Ensure the locked time is never less than lockVal if secondsLocked % (1 << SEQUENCE_LOCKTIME_GRANULARITY) != 0: - secondsLocked += (1 << SEQUENCE_LOCKTIME_GRANULARITY) + secondsLocked += 1 << SEQUENCE_LOCKTIME_GRANULARITY secondsLocked >>= SEQUENCE_LOCKTIME_GRANULARITY return secondsLocked | SEQUENCE_LOCKTIME_TYPE_FLAG - raise ValueError('Unknown lock type') + raise ValueError("Unknown lock type") @staticmethod def decodeSequence(lock_value: int) -> int: # Return the raw value if lock_value & SEQUENCE_LOCKTIME_TYPE_FLAG: - return (lock_value & SEQUENCE_LOCKTIME_MASK) << SEQUENCE_LOCKTIME_GRANULARITY + return ( + lock_value & SEQUENCE_LOCKTIME_MASK + ) << SEQUENCE_LOCKTIME_GRANULARITY return lock_value & SEQUENCE_LOCKTIME_MASK @staticmethod @@ -246,20 +258,22 @@ class BTCInterface(Secp256k1Interface): def __init__(self, coin_settings, network, swap_client=None): super().__init__(network) - self._rpc_host = coin_settings.get('rpchost', '127.0.0.1') - self._rpcport = coin_settings['rpcport'] - self._rpcauth = coin_settings['rpcauth'] + self._rpc_host = coin_settings.get("rpchost", "127.0.0.1") + self._rpcport = coin_settings["rpcport"] + self._rpcauth = coin_settings["rpcauth"] self.rpc = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) - self._rpc_wallet = 'wallet.dat' - self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet) - self.blocks_confirmed = coin_settings['blocks_confirmed'] - self.setConfTarget(coin_settings['conf_target']) - self._use_segwit = coin_settings['use_segwit'] - self._connection_type = coin_settings['connection_type'] + self._rpc_wallet = "wallet.dat" + self.rpc_wallet = make_rpc_func( + self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet + ) + self.blocks_confirmed = coin_settings["blocks_confirmed"] + self.setConfTarget(coin_settings["conf_target"]) + self._use_segwit = coin_settings["use_segwit"] + self._connection_type = coin_settings["connection_type"] self._sc = swap_client self._log = self._sc.log if self._sc and self._sc.log else logging self._expect_seedid_hex = None - self._altruistic = coin_settings.get('altruistic', True) + self._altruistic = coin_settings.get("altruistic", True) def open_rpc(self, wallet=None): return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host) @@ -267,108 +281,123 @@ class BTCInterface(Secp256k1Interface): def json_request(self, rpc_conn, method, params): try: v = rpc_conn.json_request(method, params) - r = json.loads(v.decode('utf-8')) + r = json.loads(v.decode("utf-8")) except Exception as ex: traceback.print_exc() - raise ValueError('RPC Server Error ' + str(ex)) - if 'error' in r and r['error'] is not None: - raise ValueError('RPC error ' + str(r['error'])) - return r['result'] + raise ValueError("RPC Server Error " + str(ex)) + if "error" in r and r["error"] is not None: + raise ValueError("RPC error " + str(r["error"])) + return r["result"] def close_rpc(self, rpc_conn): rpc_conn.close() def checkWallets(self) -> int: - wallets = self.rpc('listwallets') + wallets = self.rpc("listwallets") # Wallet name is "" for some LTC and PART installs on older cores if self._rpc_wallet not in wallets and len(wallets) > 0: - self._log.debug('Changing {} wallet name.'.format(self.ticker())) + self._log.debug("Changing {} wallet name.".format(self.ticker())) for wallet_name in wallets: # Skip over other expected wallets - if wallet_name in ('mweb', ): + if wallet_name in ("mweb",): continue self._rpc_wallet = wallet_name - self._log.info('Switched {} wallet name to {}.'.format(self.ticker(), self._rpc_wallet)) - self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet) + self._log.info( + "Switched {} wallet name to {}.".format( + self.ticker(), self._rpc_wallet + ) + ) + self.rpc_wallet = make_rpc_func( + self._rpcport, + self._rpcauth, + host=self._rpc_host, + wallet=self._rpc_wallet, + ) break return len(wallets) def testDaemonRPC(self, with_wallet=True) -> None: - self.rpc_wallet('getwalletinfo' if with_wallet else 'getblockchaininfo') + self.rpc_wallet("getwalletinfo" if with_wallet else "getblockchaininfo") def getDaemonVersion(self): - return self.rpc('getnetworkinfo')['version'] + return self.rpc("getnetworkinfo")["version"] def getBlockchainInfo(self): - return self.rpc('getblockchaininfo') + return self.rpc("getblockchaininfo") def getChainHeight(self) -> int: - return self.rpc('getblockcount') + return self.rpc("getblockcount") def getMempoolTx(self, txid): - return self.rpc('getrawtransaction', [txid.hex()]) + return self.rpc("getrawtransaction", [txid.hex()]) def getBlockHeaderFromHeight(self, height): - block_hash = self.rpc('getblockhash', [height]) - return self.rpc('getblockheader', [block_hash]) + block_hash = self.rpc("getblockhash", [height]) + return self.rpc("getblockheader", [block_hash]) def getBlockHeader(self, block_hash): - return self.rpc('getblockheader', [block_hash]) + return self.rpc("getblockheader", [block_hash]) def getBlockHeaderAt(self, time: int, block_after=False): - blockchaininfo = self.rpc('getblockchaininfo') - last_block_header = self.rpc('getblockheader', [blockchaininfo['bestblockhash']]) + blockchaininfo = self.rpc("getblockchaininfo") + last_block_header = self.rpc( + "getblockheader", [blockchaininfo["bestblockhash"]] + ) max_tries = 5000 for i in range(max_tries): - prev_block_header = self.rpc('getblockheader', [last_block_header['previousblockhash']]) - if prev_block_header['time'] <= time: + prev_block_header = self.rpc( + "getblockheader", [last_block_header["previousblockhash"]] + ) + if prev_block_header["time"] <= time: return last_block_header if block_after else prev_block_header last_block_header = prev_block_header - raise ValueError(f'Block header not found at time: {time}') + raise ValueError(f"Block header not found at time: {time}") def initialiseWallet(self, key_bytes: bytes) -> None: key_wif = self.encodeKey(key_bytes) - self.rpc_wallet('sethdseed', [True, key_wif]) + self.rpc_wallet("sethdseed", [True, key_wif]) self._have_checked_seed = False def getWalletInfo(self): - rv = self.rpc_wallet('getwalletinfo') - rv['encrypted'] = 'unlocked_until' in rv - rv['locked'] = rv.get('unlocked_until', 1) <= 0 - rv['locked_utxos'] = len(self.rpc_wallet('listlockunspent')) + rv = self.rpc_wallet("getwalletinfo") + rv["encrypted"] = "unlocked_until" in rv + rv["locked"] = rv.get("unlocked_until", 1) <= 0 + rv["locked_utxos"] = len(self.rpc_wallet("listlockunspent")) return rv def getWalletRestoreHeight(self) -> int: - start_time = self.rpc_wallet('getwalletinfo')['keypoololdest'] + start_time = self.rpc_wallet("getwalletinfo")["keypoololdest"] blockchaininfo = self.getBlockchainInfo() - best_block = blockchaininfo['bestblockhash'] + best_block = blockchaininfo["bestblockhash"] - chain_synced = round(blockchaininfo['verificationprogress'], 3) + chain_synced = round(blockchaininfo["verificationprogress"], 3) if chain_synced < 1.0: - raise ValueError('{} chain isn\'t synced.'.format(self.coin_name())) + raise ValueError("{} chain isn't synced.".format(self.coin_name())) - self._log.debug('Finding block at time: {}'.format(start_time)) + self._log.debug("Finding block at time: {}".format(start_time)) rpc_conn = self.open_rpc() try: block_hash = best_block while True: - block_header = self.json_request(rpc_conn, 'getblockheader', [block_hash]) - if block_header['time'] < start_time: - return block_header['height'] - block_hash = block_header['previousblockhash'] + block_header = self.json_request( + rpc_conn, "getblockheader", [block_hash] + ) + if block_header["time"] < start_time: + return block_header["height"] + block_hash = block_header["previousblockhash"] finally: self.close_rpc(rpc_conn) - raise ValueError('{} wallet restore height not found.'.format(self.coin_name())) + raise ValueError("{} wallet restore height not found.".format(self.coin_name())) def getWalletSeedID(self) -> str: - wi = self.rpc_wallet('getwalletinfo') - return 'Not found' if 'hdseedid' not in wi else wi['hdseedid'] + wi = self.rpc_wallet("getwalletinfo") + return "Not found" if "hdseedid" not in wi else wi["hdseedid"] def checkExpectedSeed(self, expect_seedid: str) -> bool: wallet_seed_id = self.getWalletSeedID() @@ -376,94 +405,109 @@ class BTCInterface(Secp256k1Interface): self._have_checked_seed = True return expect_seedid == wallet_seed_id - def getNewAddress(self, use_segwit: bool, label: str = 'swap_receive') -> str: + def getNewAddress(self, use_segwit: bool, label: str = "swap_receive") -> str: args = [label] if use_segwit: - args.append('bech32') - return self.rpc_wallet('getnewaddress', args) + args.append("bech32") + return self.rpc_wallet("getnewaddress", args) def isValidAddress(self, address: str) -> bool: try: - rv = self.rpc_wallet('validateaddress', [address]) - if rv['isvalid'] is True: + rv = self.rpc_wallet("validateaddress", [address]) + if rv["isvalid"] is True: return True - except Exception as ex: - self._log.debug('validateaddress failed: {}'.format(address)) + except Exception as e: # noqa: F841 + self._log.debug("validateaddress failed: {}".format(address)) return False def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool: - addr_info = self.rpc_wallet('getaddressinfo', [address]) + addr_info = self.rpc_wallet("getaddressinfo", [address]) if not or_watch_only: - return addr_info['ismine'] - return addr_info['ismine'] or addr_info['iswatchonly'] + return addr_info["ismine"] + return addr_info["ismine"] or addr_info["iswatchonly"] def checkAddressMine(self, address: str) -> None: - addr_info = self.rpc_wallet('getaddressinfo', [address]) - ensure(addr_info['ismine'], 'ismine is false') + addr_info = self.rpc_wallet("getaddressinfo", [address]) + ensure(addr_info["ismine"], "ismine is false") if self.sc._restrict_unknown_seed_wallets: - ensure(addr_info['hdseedid'] == self._expect_seedid_hex, 'unexpected seedid') + ensure( + addr_info["hdseedid"] == self._expect_seedid_hex, "unexpected seedid" + ) def get_fee_rate(self, conf_target: int = 2) -> (float, str): - chain_client_settings = self._sc.getChainClientSettings(self.coin_type()) # basicswap.json - override_feerate = chain_client_settings.get('override_feerate', None) + chain_client_settings = self._sc.getChainClientSettings( + self.coin_type() + ) # basicswap.json + override_feerate = chain_client_settings.get("override_feerate", None) if override_feerate: - self._log.debug('Fee rate override used for %s: %f', self.coin_name(), override_feerate) - return override_feerate, 'override_feerate' + self._log.debug( + "Fee rate override used for %s: %f", self.coin_name(), override_feerate + ) + return override_feerate, "override_feerate" - min_relay_fee = chain_client_settings.get('min_relay_fee', None) + min_relay_fee = chain_client_settings.get("min_relay_fee", None) def try_get_fee_rate(self, conf_target): try: - fee_rate: float = self.rpc_wallet('estimatesmartfee', [conf_target])['feerate'] - assert (fee_rate > 0.0), 'Negative feerate' - return fee_rate, 'estimatesmartfee' + fee_rate: float = self.rpc_wallet("estimatesmartfee", [conf_target])[ + "feerate" + ] + assert fee_rate > 0.0, "Negative feerate" + return fee_rate, "estimatesmartfee" except Exception: try: - fee_rate: float = self.rpc_wallet('getwalletinfo')['paytxfee'] - assert (fee_rate > 0.0), 'Non positive feerate' - return fee_rate, 'paytxfee' + fee_rate: float = self.rpc_wallet("getwalletinfo")["paytxfee"] + assert fee_rate > 0.0, "Non positive feerate" + return fee_rate, "paytxfee" except Exception: - fee_rate: float = self.rpc('getnetworkinfo')['relayfee'] - return fee_rate, 'relayfee' + fee_rate: float = self.rpc("getnetworkinfo")["relayfee"] + return fee_rate, "relayfee" fee_rate, rate_src = try_get_fee_rate(self, conf_target) if min_relay_fee and min_relay_fee > fee_rate: - self._log.warning('Feerate {} ({}) is below min relay fee {} for {}'.format(self.format_amount(fee_rate, True, 1), rate_src, self.format_amount(min_relay_fee, True, 1), self.coin_name())) - return min_relay_fee, 'min_relay_fee' + self._log.warning( + "Feerate {} ({}) is below min relay fee {} for {}".format( + self.format_amount(fee_rate, True, 1), + rate_src, + self.format_amount(min_relay_fee, True, 1), + self.coin_name(), + ) + ) + return min_relay_fee, "min_relay_fee" return fee_rate, rate_src def isSegwitAddress(self, address: str) -> bool: - return address.startswith(self.chainparams_network()['hrp'] + '1') + return address.startswith(self.chainparams_network()["hrp"] + "1") def decodeAddress(self, address: str) -> bytes: - bech32_prefix = self.chainparams_network()['hrp'] - if len(bech32_prefix) > 0 and address.startswith(bech32_prefix + '1'): + bech32_prefix = self.chainparams_network()["hrp"] + if len(bech32_prefix) > 0 and address.startswith(bech32_prefix + "1"): return bytes(segwit_addr.decode(bech32_prefix, address)[1]) return decodeAddress(address)[1:] def pubkey_to_segwit_address(self, pk: bytes) -> str: - bech32_prefix = self.chainparams_network()['hrp'] + bech32_prefix = self.chainparams_network()["hrp"] version = 0 pkh = hash160(pk) return segwit_addr.encode(bech32_prefix, version, pkh) def pkh_to_address(self, pkh: bytes) -> str: # pkh is ripemd160(sha256(pk)) - assert (len(pkh) == 20) - prefix = self.chainparams_network()['pubkey_address'] + assert len(pkh) == 20 + prefix = self.chainparams_network()["pubkey_address"] data = bytes((prefix,)) + pkh checksum = sha256(sha256(data)) return b58encode(data + checksum[0:4]) def sh_to_address(self, sh: bytes) -> str: - assert (len(sh) == 20) - prefix = self.chainparams_network()['script_address'] + assert len(sh) == 20 + prefix = self.chainparams_network()["script_address"] data = bytes((prefix,)) + sh checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest() return b58encode(data + checksum[0:4]) def encode_p2wsh(self, script: bytes) -> str: - bech32_prefix = self.chainparams_network()['hrp'] + bech32_prefix = self.chainparams_network()["hrp"] version = 0 program = script[2:] # strip version and length return segwit_addr.encode(bech32_prefix, version, program) @@ -472,10 +516,10 @@ class BTCInterface(Secp256k1Interface): return self.encode_p2wsh(script) def encode_p2sh(self, script: bytes) -> str: - return pubkeyToAddress(self.chainparams_network()['script_address'], script) + return pubkeyToAddress(self.chainparams_network()["script_address"], script) def pubkey_to_address(self, pk: bytes) -> str: - assert (len(pk) == 33) + assert len(pk) == 33 return self.pkh_to_address(hash160(pk)) def getAddressHashFromKey(self, key: bytes) -> bytes: @@ -486,17 +530,17 @@ class BTCInterface(Secp256k1Interface): return self.getAddressHashFromKey(seed)[::-1] def encodeKey(self, key_bytes: bytes) -> str: - wif_prefix = self.chainparams_network()['key_prefix'] + wif_prefix = self.chainparams_network()["key_prefix"] return toWIF(wif_prefix, key_bytes) def encodePubkey(self, pk: bytes) -> bytes: return pointToCPK(pk) def encodeSegwitAddress(self, key_hash: bytes) -> str: - return segwit_addr.encode(self.chainparams_network()['hrp'], 0, key_hash) + return segwit_addr.encode(self.chainparams_network()["hrp"], 0, key_hash) def decodeSegwitAddress(self, addr: str) -> bytes: - return bytes(segwit_addr.decode(self.chainparams_network()['hrp'], addr)[1]) + return bytes(segwit_addr.decode(self.chainparams_network()["hrp"], addr)[1]) def decodePubkey(self, pke): return CPKToPoint(pke) @@ -514,7 +558,9 @@ class BTCInterface(Secp256k1Interface): tx.deserialize(BytesIO(tx_bytes)) return tx - def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes: + def createSCLockTx( + self, value: int, script: bytearray, vkbv: bytes = None + ) -> bytes: tx = CTransaction() tx.nVersion = self.txVersion() tx.vout.append(self.txoType()(value, self.getScriptDest(script))) @@ -528,6 +574,7 @@ class BTCInterface(Secp256k1Interface): Kal_enc = Kal if len(Kal) == 33 else self.encodePubkey(Kal) Kaf_enc = Kaf if len(Kaf) == 33 else self.encodePubkey(Kaf) + # fmt: off return CScript([ CScriptOp(OP_IF), 2, Kal_enc, Kaf_enc, 2, CScriptOp(OP_CHECKMULTISIG), @@ -535,14 +582,25 @@ class BTCInterface(Secp256k1Interface): csv_val, CScriptOp(OP_CHECKSEQUENCEVERIFY), CScriptOp(OP_DROP), Kaf_enc, CScriptOp(OP_CHECKSIG), CScriptOp(OP_ENDIF)]) + # fmt: on - def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None): + def createSCLockRefundTx( + self, + tx_lock_bytes, + script_lock, + Kal, + Kaf, + lock1_value, + csv_val, + tx_fee_rate, + vkbv=None, + ): tx_lock = CTransaction() tx_lock = self.loadTx(tx_lock_bytes) output_script = self.getScriptDest(script_lock) locked_n = findOutput(tx_lock, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx_lock.vout[locked_n].nValue tx_lock.rehash() @@ -551,9 +609,13 @@ class BTCInterface(Secp256k1Interface): refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val) tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n), - nSequence=lock1_value, - scriptSig=self.getScriptScriptSig(script_lock))) + tx.vin.append( + CTxIn( + COutPoint(tx_lock_id_int, locked_n), + nSequence=lock1_value, + scriptSig=self.getScriptScriptSig(script_lock), + ) + ) tx.vout.append(self.txoType()(locked_coin, self.getScriptDest(refund_script))) dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock) @@ -563,12 +625,24 @@ class BTCInterface(Secp256k1Interface): tx.vout[0].nValue = locked_coin - pay_fee tx.rehash() - self._log.info('createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) + self._log.info( + "createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.", + i2h(tx.sha256), + tx_fee_rate, + vsize, + pay_fee, + ) return tx.serialize(), refund_script, tx.vout[0].nValue - def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv=None): + def createSCLockRefundSpendTx( + self, + tx_lock_refund_bytes, + script_lock_refund, + pkh_refund_to, + tx_fee_rate, + vkbv=None, + ): # Returns the coinA locked coin to the leader # The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey # If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower @@ -577,7 +651,7 @@ class BTCInterface(Secp256k1Interface): output_script = self.getScriptDest(script_lock_refund) locked_n = findOutput(tx_lock_refund, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx_lock_refund.vout[locked_n].nValue tx_lock_refund.rehash() @@ -585,25 +659,46 @@ class BTCInterface(Secp256k1Interface): tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), - nSequence=0, - scriptSig=self.getScriptScriptSig(script_lock_refund))) + tx.vin.append( + CTxIn( + COutPoint(tx_lock_refund_hash_int, locked_n), + nSequence=0, + scriptSig=self.getScriptScriptSig(script_lock_refund), + ) + ) - tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to))) + tx.vout.append( + self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to)) + ) - dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(script_lock_refund) + dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness( + script_lock_refund + ) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) pay_fee = round(tx_fee_rate * vsize / 1000) tx.vout[0].nValue = locked_coin - pay_fee tx.rehash() - self._log.info('createSCLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) + self._log.info( + "createSCLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.", + i2h(tx.sha256), + tx_fee_rate, + vsize, + pay_fee, + ) return tx.serialize() - def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None, kbsf=None): + def createSCLockRefundSpendToFTx( + self, + tx_lock_refund_bytes, + script_lock_refund, + pkh_dest, + tx_fee_rate, + vkbv=None, + kbsf=None, + ): # lock refund swipe tx # Sends the coinA locked coin to the follower @@ -611,7 +706,7 @@ class BTCInterface(Secp256k1Interface): output_script = self.getScriptDest(script_lock_refund) locked_n = findOutput(tx_lock_refund, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx_lock_refund.vout[locked_n].nValue A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund) @@ -621,35 +716,54 @@ class BTCInterface(Secp256k1Interface): tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), - nSequence=lock2_value, - scriptSig=self.getScriptScriptSig(script_lock_refund))) + tx.vin.append( + CTxIn( + COutPoint(tx_lock_refund_hash_int, locked_n), + nSequence=lock2_value, + scriptSig=self.getScriptScriptSig(script_lock_refund), + ) + ) - tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest))) + tx.vout.append( + self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)) + ) if self.altruistic() and kbsf: # Add mercy_keyshare - tx.vout.append(self.txoType()(0, CScript([OP_RETURN, b'XBSW', kbsf]))) + tx.vout.append(self.txoType()(0, CScript([OP_RETURN, b"XBSW", kbsf]))) else: - self._log.debug('Not attaching mercy output, have kbsf {}.'.format('true' if kbsf else 'false')) + self._log.debug( + "Not attaching mercy output, have kbsf {}.".format( + "true" if kbsf else "false" + ) + ) - dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness(script_lock_refund) + dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness( + script_lock_refund + ) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) pay_fee = round(tx_fee_rate * vsize / 1000) tx.vout[0].nValue = locked_coin - pay_fee tx.rehash() - self._log.info('createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) + self._log.info( + "createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.", + i2h(tx.sha256), + tx_fee_rate, + vsize, + pay_fee, + ) return tx.serialize() - def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None, fee_info={}): + def createSCLockSpendTx( + self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None, fee_info={} + ): tx_lock = self.loadTx(tx_lock_bytes) output_script = self.getScriptDest(script_lock) locked_n = findOutput(tx_lock, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx_lock.vout[locked_n].nValue tx_lock.rehash() @@ -657,10 +771,16 @@ class BTCInterface(Secp256k1Interface): tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n), - scriptSig=self.getScriptScriptSig(script_lock))) + tx.vin.append( + CTxIn( + COutPoint(tx_lock_id_int, locked_n), + scriptSig=self.getScriptScriptSig(script_lock), + ) + ) - tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest))) + tx.vout.append( + self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)) + ) dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) @@ -668,22 +788,33 @@ class BTCInterface(Secp256k1Interface): pay_fee = round(tx_fee_rate * vsize / 1000) tx.vout[0].nValue = locked_coin - pay_fee - fee_info['fee_paid'] = pay_fee - fee_info['rate_used'] = tx_fee_rate - fee_info['witness_bytes'] = witness_bytes - fee_info['vsize'] = vsize + fee_info["fee_paid"] = pay_fee + fee_info["rate_used"] = tx_fee_rate + fee_info["witness_bytes"] = witness_bytes + fee_info["vsize"] = vsize tx.rehash() - self._log.info('createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) + self._log.info( + "createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.", + i2h(tx.sha256), + tx_fee_rate, + vsize, + pay_fee, + ) return tx.serialize() - def verifySCLockTx(self, tx_bytes, script_out, - swap_value, - Kal, Kaf, - feerate, - check_lock_tx_inputs, vkbv=None): + def verifySCLockTx( + self, + tx_bytes, + script_out, + swap_value, + Kal, + Kaf, + feerate, + check_lock_tx_inputs, + vkbv=None, + ): # Verify: # @@ -693,23 +824,23 @@ class BTCInterface(Secp256k1Interface): tx = self.loadTx(tx_bytes) txid = self.getTxid(tx) - self._log.info('Verifying lock tx: {}.'.format(b2h(txid))) + self._log.info("Verifying lock tx: {}.".format(b2h(txid))) - ensure(tx.nVersion == self.txVersion(), 'Bad version') - ensure(tx.nLockTime == 0, 'Bad nLockTime') # TODO match txns created by cores + ensure(tx.nVersion == self.txVersion(), "Bad version") + ensure(tx.nLockTime == 0, "Bad nLockTime") # TODO match txns created by cores script_pk = self.getScriptDest(script_out) locked_n = findOutput(tx, script_pk) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx.vout[locked_n].nValue # Check value - ensure(locked_coin == swap_value, 'Bad locked value') + ensure(locked_coin == swap_value, "Bad locked value") # Check script A, B = extractScriptLockScriptValues(script_out) - ensure(A == Kal, 'Bad script pubkey') - ensure(B == Kaf, 'Bad script pubkey') + ensure(A == Kal, "Bad script pubkey") + ensure(B == Kaf, "Bad script pubkey") if check_lock_tx_inputs: # TODO: Check that inputs are unspent @@ -718,38 +849,62 @@ class BTCInterface(Secp256k1Interface): add_bytes = 0 add_witness_bytes = getCompactSizeLen(len(tx.vin)) for pi in tx.vin: - ptx = self.rpc('getrawtransaction', [i2h(pi.prevout.hash), True]) - prevout = ptx['vout'][pi.prevout.n] - inputs_value += self.make_int(prevout['value']) + ptx = self.rpc("getrawtransaction", [i2h(pi.prevout.hash), True]) + prevout = ptx["vout"][pi.prevout.n] + inputs_value += self.make_int(prevout["value"]) - prevout_type = prevout['scriptPubKey']['type'] - if prevout_type == 'witness_v0_keyhash': + prevout_type = prevout["scriptPubKey"]["type"] + if prevout_type == "witness_v0_keyhash": add_witness_bytes += 107 # sig 72, pk 33 and 2 size bytes add_witness_bytes += getCompactSizeLen(107) else: # Assume P2PKH, TODO more types - add_bytes += 107 # OP_PUSH72 OP_PUSH33 + add_bytes += ( + 107 # OP_PUSH72 OP_PUSH33 + ) outputs_value = 0 for txo in tx.vout: outputs_value += txo.nValue fee_paid = inputs_value - outputs_value - assert (fee_paid > 0) + assert fee_paid > 0 vsize = self.getTxVSize(tx, add_bytes, add_witness_bytes) fee_rate_paid = fee_paid * 1000 // vsize - self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', locked_coin, vsize, fee_rate_paid) + self._log.info( + "tx amount, vsize, feerate: %ld, %ld, %ld", + locked_coin, + vsize, + fee_rate_paid, + ) if not self.compareFeeRates(fee_rate_paid, feerate): - self._log.warning('feerate paid doesn\'t match expected: %ld, %ld', fee_rate_paid, feerate) + self._log.warning( + "feerate paid doesn't match expected: %ld, %ld", + fee_rate_paid, + feerate, + ) # TODO: Display warning to user return txid, locked_n - def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out, - prevout_id, prevout_n, prevout_seq, prevout_script, - Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv=None): + def verifySCLockRefundTx( + self, + tx_bytes, + lock_tx_bytes, + script_out, + prevout_id, + prevout_n, + prevout_seq, + prevout_script, + Kal, + Kaf, + csv_val_expect, + swap_value, + feerate, + vkbv=None, + ): # Verify: # Must have only one input with correct prevout and sequence # Must have only one output to the p2wsh of the lock refund script @@ -757,152 +912,225 @@ class BTCInterface(Secp256k1Interface): tx = self.loadTx(tx_bytes) txid = self.getTxid(tx) - self._log.info('Verifying lock refund tx: {}.'.format(b2h(txid))) + self._log.info("Verifying lock refund tx: {}.".format(b2h(txid))) - ensure(tx.nVersion == self.txVersion(), 'Bad version') - ensure(tx.nLockTime == 0, 'nLockTime not 0') - ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') + ensure(tx.nVersion == self.txVersion(), "Bad version") + ensure(tx.nLockTime == 0, "nLockTime not 0") + ensure(len(tx.vin) == 1, "tx doesn't have one input") - ensure(tx.vin[0].nSequence == prevout_seq, 'Bad input nSequence') - ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script), 'Input scriptsig mismatch') - ensure(tx.vin[0].prevout.hash == b2i(prevout_id) and tx.vin[0].prevout.n == prevout_n, 'Input prevout mismatch') + ensure(tx.vin[0].nSequence == prevout_seq, "Bad input nSequence") + ensure( + tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script), + "Input scriptsig mismatch", + ) + ensure( + tx.vin[0].prevout.hash == b2i(prevout_id) + and tx.vin[0].prevout.n == prevout_n, + "Input prevout mismatch", + ) - ensure(len(tx.vout) == 1, 'tx doesn\'t have one output') + ensure(len(tx.vout) == 1, "tx doesn't have one output") script_pk = self.getScriptDest(script_out) locked_n = findOutput(tx, script_pk) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx.vout[locked_n].nValue # Check script and values A, B, csv_val, C = extractScriptLockRefundScriptValues(script_out) - ensure(A == Kal, 'Bad script pubkey') - ensure(B == Kaf, 'Bad script pubkey') - ensure(csv_val == csv_val_expect, 'Bad script csv value') - ensure(C == Kaf, 'Bad script pubkey') + ensure(A == Kal, "Bad script pubkey") + ensure(B == Kaf, "Bad script pubkey") + ensure(csv_val == csv_val_expect, "Bad script csv value") + ensure(C == Kaf, "Bad script pubkey") fee_paid = swap_value - locked_coin - assert (fee_paid > 0) + assert fee_paid > 0 dummy_witness_stack = self.getScriptLockTxDummyWitness(prevout_script) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) fee_rate_paid = fee_paid * 1000 // vsize - self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', locked_coin, vsize, fee_rate_paid) + self._log.info( + "tx amount, vsize, feerate: %ld, %ld, %ld", + locked_coin, + vsize, + fee_rate_paid, + ) if not self.compareFeeRates(fee_rate_paid, feerate): - raise ValueError('Bad fee rate, expected: {}'.format(feerate)) + raise ValueError("Bad fee rate, expected: {}".format(feerate)) return txid, locked_coin, locked_n - def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes, - lock_refund_tx_id, prevout_script, - Kal, - prevout_n, prevout_value, feerate, vkbv=None): + def verifySCLockRefundSpendTx( + self, + tx_bytes, + lock_refund_tx_bytes, + lock_refund_tx_id, + prevout_script, + Kal, + prevout_n, + prevout_value, + feerate, + vkbv=None, + ): # Verify: # Must have only one input with correct prevout (n is always 0) and sequence # Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr tx = self.loadTx(tx_bytes) txid = self.getTxid(tx) - self._log.info('Verifying lock refund spend tx: {}.'.format(b2h(txid))) + self._log.info("Verifying lock refund spend tx: {}.".format(b2h(txid))) - ensure(tx.nVersion == self.txVersion(), 'Bad version') - ensure(tx.nLockTime == 0, 'nLockTime not 0') - ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') + ensure(tx.nVersion == self.txVersion(), "Bad version") + ensure(tx.nLockTime == 0, "nLockTime not 0") + ensure(len(tx.vin) == 1, "tx doesn't have one input") - ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence') - ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script), 'Input scriptsig mismatch') - ensure(tx.vin[0].prevout.hash == b2i(lock_refund_tx_id) and tx.vin[0].prevout.n == 0, 'Input prevout mismatch') + ensure(tx.vin[0].nSequence == 0, "Bad input nSequence") + ensure( + tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script), + "Input scriptsig mismatch", + ) + ensure( + tx.vin[0].prevout.hash == b2i(lock_refund_tx_id) + and tx.vin[0].prevout.n == 0, + "Input prevout mismatch", + ) - ensure(len(tx.vout) == 1, 'tx doesn\'t have one output') + ensure(len(tx.vout) == 1, "tx doesn't have one output") # Destination doesn't matter to the follower - ''' + """ p2wpkh = CScript([OP_0, hash160(Kal)]) locked_n = findOutput(tx, p2wpkh) ensure(locked_n is not None, 'Output not found in lock refund spend tx') - ''' + """ tx_value = tx.vout[0].nValue fee_paid = prevout_value - tx_value - assert (fee_paid > 0) + assert fee_paid > 0 - dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(prevout_script) + dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness( + prevout_script + ) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) fee_rate_paid = fee_paid * 1000 // vsize - self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', tx_value, vsize, fee_rate_paid) + self._log.info( + "tx amount, vsize, feerate: %ld, %ld, %ld", tx_value, vsize, fee_rate_paid + ) if not self.compareFeeRates(fee_rate_paid, feerate): - raise ValueError('Bad fee rate, expected: {}'.format(feerate)) + raise ValueError("Bad fee rate, expected: {}".format(feerate)) return True - def verifySCLockSpendTx(self, tx_bytes, - lock_tx_bytes, lock_tx_script, - a_pkhash_f, feerate, vkbv=None): + def verifySCLockSpendTx( + self, tx_bytes, lock_tx_bytes, lock_tx_script, a_pkhash_f, feerate, vkbv=None + ): # Verify: # Must have only one input with correct prevout (n is always 0) and sequence # Must have only one output with destination and amount tx = self.loadTx(tx_bytes) txid = self.getTxid(tx) - self._log.info('Verifying lock spend tx: {}.'.format(b2h(txid))) + self._log.info("Verifying lock spend tx: {}.".format(b2h(txid))) - ensure(tx.nVersion == self.txVersion(), 'Bad version') - ensure(tx.nLockTime == 0, 'nLockTime not 0') - ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') + ensure(tx.nVersion == self.txVersion(), "Bad version") + ensure(tx.nLockTime == 0, "nLockTime not 0") + ensure(len(tx.vin) == 1, "tx doesn't have one input") lock_tx = self.loadTx(lock_tx_bytes) lock_tx_id = self.getTxid(lock_tx) output_script = self.getScriptDest(lock_tx_script) locked_n = findOutput(lock_tx, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = lock_tx.vout[locked_n].nValue - ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence') - ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(lock_tx_script), 'Input scriptsig mismatch') - ensure(tx.vin[0].prevout.hash == b2i(lock_tx_id) and tx.vin[0].prevout.n == locked_n, 'Input prevout mismatch') + ensure(tx.vin[0].nSequence == 0, "Bad input nSequence") + ensure( + tx.vin[0].scriptSig == self.getScriptScriptSig(lock_tx_script), + "Input scriptsig mismatch", + ) + ensure( + tx.vin[0].prevout.hash == b2i(lock_tx_id) + and tx.vin[0].prevout.n == locked_n, + "Input prevout mismatch", + ) - ensure(len(tx.vout) == 1, 'tx doesn\'t have one output') + ensure(len(tx.vout) == 1, "tx doesn't have one output") p2wpkh = self.getScriptForPubkeyHash(a_pkhash_f) - ensure(tx.vout[0].scriptPubKey == p2wpkh, 'Bad output destination') + ensure(tx.vout[0].scriptPubKey == p2wpkh, "Bad output destination") # The value of the lock tx output should already be verified, if the fee is as expected the difference will be the correct amount fee_paid = locked_coin - tx.vout[0].nValue - assert (fee_paid > 0) + assert fee_paid > 0 dummy_witness_stack = self.getScriptLockTxDummyWitness(lock_tx_script) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) fee_rate_paid = fee_paid * 1000 // vsize - self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', tx.vout[0].nValue, vsize, fee_rate_paid) + self._log.info( + "tx amount, vsize, feerate: %ld, %ld, %ld", + tx.vout[0].nValue, + vsize, + fee_rate_paid, + ) if not self.compareFeeRates(fee_rate_paid, feerate): - raise ValueError('Bad fee rate, expected: {}'.format(feerate)) + raise ValueError("Bad fee rate, expected: {}".format(feerate)) return True - def signTx(self, key_bytes: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes: + def signTx( + self, + key_bytes: bytes, + tx_bytes: bytes, + input_n: int, + prevout_script: bytes, + prevout_value: int, + ) -> bytes: tx = self.loadTx(tx_bytes) - sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value) + sig_hash = SegwitV0SignatureHash( + prevout_script, tx, input_n, SIGHASH_ALL, prevout_value + ) eck = PrivateKey(key_bytes) return eck.sign(sig_hash, hasher=None) + bytes((SIGHASH_ALL,)) - def signTxOtVES(self, key_sign: bytes, pubkey_encrypt: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes: + def signTxOtVES( + self, + key_sign: bytes, + pubkey_encrypt: bytes, + tx_bytes: bytes, + input_n: int, + prevout_script: bytes, + prevout_value: int, + ) -> bytes: tx = self.loadTx(tx_bytes) - sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value) + sig_hash = SegwitV0SignatureHash( + prevout_script, tx, input_n, SIGHASH_ALL, prevout_value + ) return ecdsaotves_enc_sign(key_sign, pubkey_encrypt, sig_hash) - def verifyTxOtVES(self, tx_bytes: bytes, ct: bytes, Ks: bytes, Ke: bytes, input_n: int, prevout_script: bytes, prevout_value): + def verifyTxOtVES( + self, + tx_bytes: bytes, + ct: bytes, + Ks: bytes, + Ke: bytes, + input_n: int, + prevout_script: bytes, + prevout_value, + ): tx = self.loadTx(tx_bytes) - sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value) + sig_hash = SegwitV0SignatureHash( + prevout_script, tx, input_n, SIGHASH_ALL, prevout_value + ) return ecdsaotves_enc_verify(Ks, Ke, sig_hash, ct) def decryptOtVES(self, k: bytes, esig: bytes) -> bytes: @@ -911,32 +1139,49 @@ class BTCInterface(Secp256k1Interface): def recoverEncKey(self, esig, sig, K): return ecdsaotves_rec_enc_key(K, esig, sig[:-1]) # Strip sighash type - def verifyTxSig(self, tx_bytes: bytes, sig: bytes, K: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bool: + def verifyTxSig( + self, + tx_bytes: bytes, + sig: bytes, + K: bytes, + input_n: int, + prevout_script: bytes, + prevout_value: int, + ) -> bool: tx = self.loadTx(tx_bytes) - sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value) + sig_hash = SegwitV0SignatureHash( + prevout_script, tx, input_n, SIGHASH_ALL, prevout_value + ) pubkey = PublicKey(K) - return pubkey.verify(sig[: -1], sig_hash, hasher=None) # Pop the hashtype byte + return pubkey.verify(sig[:-1], sig_hash, hasher=None) # Pop the hashtype byte def fundTx(self, tx: bytes, feerate) -> bytes: feerate_str = self.format_amount(feerate) # TODO: unlock unspents if bid cancelled options = { - 'lockUnspents': True, - 'feeRate': feerate_str, + "lockUnspents": True, + "feeRate": feerate_str, } - rv = self.rpc_wallet('fundrawtransaction', [tx.hex(), options]) - return bytes.fromhex(rv['hex']) + rv = self.rpc_wallet("fundrawtransaction", [tx.hex(), options]) + return bytes.fromhex(rv["hex"]) def listInputs(self, tx_bytes: bytes): tx = self.loadTx(tx_bytes) - all_locked = self.rpc_wallet('listlockunspent') + all_locked = self.rpc_wallet("listlockunspent") inputs = [] for pi in tx.vin: txid_hex = i2h(pi.prevout.hash) - islocked = any([txid_hex == a['txid'] and pi.prevout.n == a['vout'] for a in all_locked]) - inputs.append({'txid': txid_hex, 'vout': pi.prevout.n, 'islocked': islocked}) + islocked = any( + [ + txid_hex == a["txid"] and pi.prevout.n == a["vout"] + for a in all_locked + ] + ) + inputs.append( + {"txid": txid_hex, "vout": pi.prevout.n, "islocked": islocked} + ) return inputs def unlockInputs(self, tx_bytes): @@ -944,20 +1189,28 @@ class BTCInterface(Secp256k1Interface): inputs = [] for pi in tx.vin: - inputs.append({'txid': i2h(pi.prevout.hash), 'vout': pi.prevout.n}) - self.rpc_wallet('lockunspent', [True, inputs]) + inputs.append({"txid": i2h(pi.prevout.hash), "vout": pi.prevout.n}) + self.rpc_wallet("lockunspent", [True, inputs]) def signTxWithWallet(self, tx: bytes) -> bytes: - rv = self.rpc_wallet('signrawtransactionwithwallet', [tx.hex()]) - return bytes.fromhex(rv['hex']) + rv = self.rpc_wallet("signrawtransactionwithwallet", [tx.hex()]) + return bytes.fromhex(rv["hex"]) def signTxWithKey(self, tx: bytes, key: bytes) -> bytes: key_wif = self.encodeKey(key) - rv = self.rpc('signrawtransactionwithkey', [tx.hex(), [key_wif, ]]) - return bytes.fromhex(rv['hex']) + rv = self.rpc( + "signrawtransactionwithkey", + [ + tx.hex(), + [ + key_wif, + ], + ], + ) + return bytes.fromhex(rv["hex"]) def publishTx(self, tx: bytes): - return self.rpc('sendrawtransaction', [tx.hex()]) + return self.rpc("sendrawtransaction", [tx.hex()]) def encodeTx(self, tx) -> bytes: return tx.serialize() @@ -995,25 +1248,34 @@ class BTCInterface(Secp256k1Interface): def getP2SHP2WSHScriptSig(self, script): script_hash = sha256(script) assert len(script_hash) == 32 - return CScript([CScript([OP_0, script_hash, ]), ]) + return CScript( + [ + CScript( + [ + OP_0, + script_hash, + ] + ), + ] + ) def getPkDest(self, K: bytes) -> bytearray: return self.getScriptForPubkeyHash(self.getPubkeyHash(K)) def scanTxOutset(self, dest): - return self.rpc('scantxoutset', ['start', ['raw({})'.format(dest.hex())]]) + return self.rpc("scantxoutset", ["start", ["raw({})".format(dest.hex())]]) def getTransaction(self, txid: bytes): try: - return bytes.fromhex(self.rpc('getrawtransaction', [txid.hex()])) - except Exception as ex: + return bytes.fromhex(self.rpc("getrawtransaction", [txid.hex()])) + except Exception as e: # noqa: F841 # TODO: filter errors return None def getWalletTransaction(self, txid: bytes): try: - return bytes.fromhex(self.rpc_wallet('gettransaction', [txid.hex()])) - except Exception as ex: + return bytes.fromhex(self.rpc_wallet("gettransaction", [txid.hex()])) + except Exception as e: # noqa: F841 # TODO: filter errors return None @@ -1024,7 +1286,9 @@ class BTCInterface(Secp256k1Interface): tx.wit.vtxinwit[0].scriptWitness.stack = stack return tx.serialize() - def setTxScriptSig(self, tx_bytes: bytes, input_no: int, script_sig: bytes) -> bytes: + def setTxScriptSig( + self, tx_bytes: bytes, input_no: int, script_sig: bytes + ) -> bytes: tx = self.loadTx(tx_bytes) tx.vin[0].scriptSig = script_sig return tx.serialize() @@ -1052,11 +1316,12 @@ class BTCInterface(Secp256k1Interface): def encodeSharedAddress(self, Kbv, Kbs): return self.pubkey_to_segwit_address(Kbs) - def publishBLockTx(self, kbv, Kbs, output_amount, feerate, unlock_time: int = 0) -> bytes: + def publishBLockTx( + self, kbv, Kbs, output_amount, feerate, unlock_time: int = 0 + ) -> bytes: b_lock_tx = self.createBLockTx(Kbs, output_amount) b_lock_tx = self.fundTx(b_lock_tx, feerate) - b_lock_tx_id = self.getTxid(b_lock_tx) b_lock_tx = self.signTxWithWallet(b_lock_tx) return bytes.fromhex(self.publishTx(b_lock_tx)) @@ -1068,11 +1333,17 @@ class BTCInterface(Secp256k1Interface): weight = len_nwit * (wsf - 1) + len_full return (weight + wsf - 1) // wsf - def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender): - dest_address = self.pubkey_to_segwit_address(Kbs) if self.using_segwit() else self.pubkey_to_address(Kbs) + def findTxB( + self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender + ): + dest_address = ( + self.pubkey_to_segwit_address(Kbs) + if self.using_segwit() + else self.pubkey_to_address(Kbs) + ) return self.getLockTxHeight(None, dest_address, cb_swap_value, restore_height) - ''' + """ raw_dest = self.getPkDest(Kbs) rv = self.scanTxOutset(raw_dest) @@ -1084,27 +1355,46 @@ class BTCInterface(Secp256k1Interface): else: return {'txid': utxo['txid'], 'vout': utxo['vout'], 'amount': utxo['amount'], 'height': utxo['height']} return None - ''' + """ def getBLockSpendTxFee(self, tx, fee_rate: int) -> int: witness_bytes = 109 vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) pay_fee = round(fee_rate * vsize / 1000) - self._log.info(f'BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {vsize}, {pay_fee}.') + self._log.info( + f"BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {vsize}, {pay_fee}." + ) return pay_fee - def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, lock_tx_vout=None) -> bytes: - self._log.info('spendBLockTx: {} {}\n'.format(chain_b_lock_txid.hex(), lock_tx_vout)) + def spendBLockTx( + self, + chain_b_lock_txid: bytes, + address_to: str, + kbv: bytes, + kbs: bytes, + cb_swap_value: int, + b_fee: int, + restore_height: int, + lock_tx_vout=None, + ) -> bytes: + self._log.info( + "spendBLockTx: {} {}\n".format(chain_b_lock_txid.hex(), lock_tx_vout) + ) locked_n = lock_tx_vout Kbs = self.getPubkey(kbs) script_pk = self.getPkDest(Kbs) if locked_n is None: - wtx = self.rpc_wallet('gettransaction', [chain_b_lock_txid.hex(), ]) - lock_tx = self.loadTx(bytes.fromhex(wtx['hex'])) + wtx = self.rpc_wallet( + "gettransaction", + [ + chain_b_lock_txid.hex(), + ], + ) + lock_tx = self.loadTx(bytes.fromhex(wtx["hex"])) locked_n = findOutput(lock_tx, script_pk) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") pkh_to = self.decodeAddress(address_to) @@ -1114,10 +1404,16 @@ class BTCInterface(Secp256k1Interface): script_lock = self.getScriptForPubkeyHash(Kbs) chain_b_lock_txid_int = b2i(chain_b_lock_txid) - tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n), - nSequence=0, - scriptSig=self.getScriptScriptSig(script_lock))) - tx.vout.append(self.txoType()(cb_swap_value, self.getScriptForPubkeyHash(pkh_to))) + tx.vin.append( + CTxIn( + COutPoint(chain_b_lock_txid_int, locked_n), + nSequence=0, + scriptSig=self.getScriptScriptSig(script_lock), + ) + ) + tx.vout.append( + self.txoType()(cb_swap_value, self.getScriptForPubkeyHash(pkh_to)) + ) pay_fee = self.getBLockSpendTxFee(tx, b_fee) tx.vout[0].nValue = cb_swap_value - pay_fee @@ -1128,32 +1424,53 @@ class BTCInterface(Secp256k1Interface): return bytes.fromhex(self.publishTx(b_lock_spend_tx)) def importWatchOnlyAddress(self, address: str, label: str): - self.rpc_wallet('importaddress', [address, label, False]) + self.rpc_wallet("importaddress", [address, label, False]) def isWatchOnlyAddress(self, address: str): - addr_info = self.rpc_wallet('getaddressinfo', [address]) - return addr_info['iswatchonly'] + addr_info = self.rpc_wallet("getaddressinfo", [address]) + return addr_info["iswatchonly"] def getSCLockScriptAddress(self, lock_script: bytes) -> str: lock_tx_dest = self.getScriptDest(lock_script) return self.encodeScriptDest(lock_tx_dest) - def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1): + def getLockTxHeight( + self, + txid, + dest_address, + bid_amount, + rescan_from, + find_index: bool = False, + vout: int = -1, + ): # Add watchonly address and rescan if required if not self.isAddressMine(dest_address, or_watch_only=True): - self.importWatchOnlyAddress(dest_address, 'bid') - self._log.info('Imported watch-only addr: {}'.format(dest_address)) - self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from)) - self.rpc_wallet('rescanblockchain', [rescan_from]) + self.importWatchOnlyAddress(dest_address, "bid") + self._log.info("Imported watch-only addr: {}".format(dest_address)) + self._log.info( + "Rescanning {} chain from height: {}".format( + self.coin_name(), rescan_from + ) + ) + self.rpc_wallet("rescanblockchain", [rescan_from]) return_txid = True if txid is None else False if txid is None: - txns = self.rpc_wallet('listunspent', [0, 9999999, [dest_address, ]]) + txns = self.rpc_wallet( + "listunspent", + [ + 0, + 9999999, + [ + dest_address, + ], + ], + ) for tx in txns: - if self.make_int(tx['amount']) == bid_amount: - txid = bytes.fromhex(tx['txid']) + if self.make_int(tx["amount"]) == bid_amount: + txid = bytes.fromhex(tx["txid"]) break if txid is None: @@ -1161,93 +1478,114 @@ class BTCInterface(Secp256k1Interface): try: # set `include_watchonly` explicitly to `True` to get transactions for watchonly addresses also in BCH - tx = self.rpc_wallet('gettransaction', [txid.hex(), True]) + tx = self.rpc_wallet("gettransaction", [txid.hex(), True]) block_height = 0 - if 'blockhash' in tx: - block_header = self.rpc('getblockheader', [tx['blockhash']]) - block_height = block_header['height'] + if "blockhash" in tx: + block_header = self.rpc("getblockheader", [tx["blockhash"]]) + block_height = block_header["height"] rv = { - 'depth': 0 if 'confirmations' not in tx else tx['confirmations'], - 'height': block_height} + "depth": 0 if "confirmations" not in tx else tx["confirmations"], + "height": block_height, + } except Exception as e: - self._log.debug('getLockTxHeight gettransaction failed: %s, %s', txid.hex(), str(e)) + self._log.debug( + "getLockTxHeight gettransaction failed: %s, %s", txid.hex(), str(e) + ) return None if find_index: - tx_obj = self.rpc('decoderawtransaction', [tx['hex']]) - rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address) + tx_obj = self.rpc("decoderawtransaction", [tx["hex"]]) + rv["index"] = find_vout_for_address_from_txobj(tx_obj, dest_address) if return_txid: - rv['txid'] = txid.hex() + rv["txid"] = txid.hex() return rv def getOutput(self, txid, dest_script, expect_value, xmr_swap=None): # TODO: Use getrawtransaction if txindex is active - utxos = self.rpc('scantxoutset', ['start', ['raw({})'.format(dest_script.hex())]]) - if 'height' in utxos: # chain_height not returned by v18 codebase - chain_height = utxos['height'] + utxos = self.rpc( + "scantxoutset", ["start", ["raw({})".format(dest_script.hex())]] + ) + if "height" in utxos: # chain_height not returned by v18 codebase + chain_height = utxos["height"] else: chain_height = self.getChainHeight() rv = [] - for utxo in utxos['unspents']: - if txid and txid.hex() != utxo['txid']: + for utxo in utxos["unspents"]: + if txid and txid.hex() != utxo["txid"]: continue - if expect_value != self.make_int(utxo['amount']): + if expect_value != self.make_int(utxo["amount"]): continue - rv.append({ - 'depth': 0 if 'height' not in utxo else (chain_height - utxo['height']) + 1, - 'height': 0 if 'height' not in utxo else utxo['height'], - 'amount': self.make_int(utxo['amount']), - 'txid': utxo['txid'], - 'vout': utxo['vout']}) + rv.append( + { + "depth": ( + 0 + if "height" not in utxo + else (chain_height - utxo["height"]) + 1 + ), + "height": 0 if "height" not in utxo else utxo["height"], + "amount": self.make_int(utxo["amount"]), + "txid": utxo["txid"], + "vout": utxo["vout"], + } + ) return rv, chain_height def withdrawCoin(self, value: float, addr_to: str, subfee: bool): - params = [addr_to, value, '', '', subfee, True, self._conf_target] - return self.rpc_wallet('sendtoaddress', params) + params = [addr_to, value, "", "", subfee, True, self._conf_target] + return self.rpc_wallet("sendtoaddress", params) def signCompact(self, k, message: str) -> bytes: - message_hash = sha256(bytes(message, 'utf-8')) + message_hash = sha256(bytes(message, "utf-8")) privkey = PrivateKey(k) return privkey.sign_recoverable(message_hash, hasher=None)[:64] def signRecoverable(self, k, message: str) -> bytes: - message_hash = sha256(bytes(message, 'utf-8')) + message_hash = sha256(bytes(message, "utf-8")) privkey = PrivateKey(k) return privkey.sign_recoverable(message_hash, hasher=None) def verifyCompactSig(self, K, message: str, sig) -> None: - message_hash = sha256(bytes(message, 'utf-8')) + message_hash = sha256(bytes(message, "utf-8")) pubkey = PublicKey(K) rv = pubkey.verify_compact(sig, message_hash, hasher=None) - assert (rv is True) + assert rv is True def verifySigAndRecover(self, sig, message: str) -> bytes: - message_hash = sha256(bytes(message, 'utf-8')) + message_hash = sha256(bytes(message, "utf-8")) pubkey = PublicKey.from_signature_and_message(sig, message_hash, hasher=None) return pubkey.format() - def verifyMessage(self, address: str, message: str, signature: str, message_magic: str = None) -> bool: + def verifyMessage( + self, address: str, message: str, signature: str, message_magic: str = None + ) -> bool: if message_magic is None: - message_magic = self.chainparams()['message_magic'] + message_magic = self.chainparams()["message_magic"] - message_bytes = SerialiseNumCompact(len(message_magic)) + bytes(message_magic, 'utf-8') + SerialiseNumCompact(len(message)) + bytes(message, 'utf-8') + message_bytes = ( + SerialiseNumCompact(len(message_magic)) + + bytes(message_magic, "utf-8") + + SerialiseNumCompact(len(message)) + + bytes(message, "utf-8") + ) message_hash = sha256(sha256(message_bytes)) signature_bytes = base64.b64decode(signature) rec_id = (signature_bytes[0] - 27) & 3 signature_bytes = signature_bytes[1:] + bytes((rec_id,)) try: - pubkey = PublicKey.from_signature_and_message(signature_bytes, message_hash, hasher=None) + pubkey = PublicKey.from_signature_and_message( + signature_bytes, message_hash, hasher=None + ) except Exception as e: - self._log.info('verifyMessage failed: ' + str(e)) + self._log.info("verifyMessage failed: " + str(e)) return False address_hash = self.decodeAddress(address) @@ -1256,7 +1594,7 @@ class BTCInterface(Secp256k1Interface): return True if address_hash == pubkey_hash else False def showLockTransfers(self, kbv, Kbs, restore_height): - raise ValueError('Unimplemented') + raise ValueError("Unimplemented") def getWitnessStackSerialisedLength(self, witness_stack): length = getCompactSizeLen(len(witness_stack)) @@ -1269,71 +1607,86 @@ class BTCInterface(Secp256k1Interface): return length def describeTx(self, tx_hex: str): - return self.rpc('decoderawtransaction', [tx_hex]) + return self.rpc("decoderawtransaction", [tx_hex]) def getSpendableBalance(self) -> int: - return self.make_int(self.rpc_wallet('getbalances')['mine']['trusted']) + return self.make_int(self.rpc_wallet("getbalances")["mine"]["trusted"]) def createUTXO(self, value_sats: int): # Create a new address and send value_sats to it spendable_balance = self.getSpendableBalance() if spendable_balance < value_sats: - raise ValueError('Balance too low') + raise ValueError("Balance too low") - address = self.getNewAddress(self._use_segwit, 'create_utxo') - return self.withdrawCoin(self.format_amount(value_sats), address, False), address + address = self.getNewAddress(self._use_segwit, "create_utxo") + return ( + self.withdrawCoin(self.format_amount(value_sats), address, False), + address, + ) - def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str: - txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) + def createRawFundedTransaction( + self, + addr_to: str, + amount: int, + sub_fee: bool = False, + lock_unspents: bool = True, + ) -> str: + txn = self.rpc( + "createrawtransaction", [[], {addr_to: self.format_amount(amount)}] + ) options = { - 'lockUnspents': lock_unspents, - 'conf_target': self._conf_target, + "lockUnspents": lock_unspents, + "conf_target": self._conf_target, } if sub_fee: - options['subtractFeeFromOutputs'] = [0,] - return self.rpc_wallet('fundrawtransaction', [txn, options])['hex'] + options["subtractFeeFromOutputs"] = [ + 0, + ] + return self.rpc_wallet("fundrawtransaction", [txn, options])["hex"] def createRawSignedTransaction(self, addr_to, amount) -> str: txn_funded = self.createRawFundedTransaction(addr_to, amount) - return self.rpc_wallet('signrawtransactionwithwallet', [txn_funded])['hex'] + return self.rpc_wallet("signrawtransactionwithwallet", [txn_funded])["hex"] def getBlockWithTxns(self, block_hash: str): - return self.rpc('getblock', [block_hash, 2]) + return self.rpc("getblock", [block_hash, 2]) def getUnspentsByAddr(self): unspent_addr = dict() - unspent = self.rpc_wallet('listunspent') + unspent = self.rpc_wallet("listunspent") for u in unspent: - if u.get('spendable', False) is False: + if u.get("spendable", False) is False: continue - if 'address' not in u: + if "address" not in u: continue - if 'desc' in u: - desc = u['desc'] + if "desc" in u: + desc = u["desc"] if self.using_segwit: if self.use_p2shp2wsh(): - if not desc.startswith('sh(wpkh'): + if not desc.startswith("sh(wpkh"): continue else: - if not desc.startswith('wpkh'): + if not desc.startswith("wpkh"): continue else: - if not desc.startswith('pkh'): + if not desc.startswith("pkh"): continue - unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1) + unspent_addr[u["address"]] = unspent_addr.get( + u["address"], 0 + ) + self.make_int(u["amount"], r=1) return unspent_addr def getUTXOBalance(self, address: str): - num_blocks = self.rpc('getblockcount') - sum_unspent = 0 - self._log.debug('[rm] scantxoutset start') # scantxoutset is slow - ro = self.rpc('scantxoutset', ['start', ['addr({})'.format(address)]]) # TODO: Use combo(address) where possible - self._log.debug('[rm] scantxoutset end') - for o in ro['unspents']: - sum_unspent += self.make_int(o['amount']) + self._log.debug("[rm] scantxoutset start") # scantxoutset is slow + ro = self.rpc( + "scantxoutset", ["start", ["addr({})".format(address)]] + ) # TODO: Use combo(address) where possible + self._log.debug("[rm] scantxoutset end") + for o in ro["unspents"]: + sum_unspent += self.make_int(o["amount"]) return sum_unspent def getProofOfFunds(self, amount_for, extra_commit_bytes): @@ -1345,17 +1698,25 @@ class BTCInterface(Secp256k1Interface): sign_for_addr = addr break - ensure(sign_for_addr is not None, 'Could not find address with enough funds for proof') + ensure( + sign_for_addr is not None, + "Could not find address with enough funds for proof", + ) - self._log.debug('sign_for_addr %s', sign_for_addr) + self._log.debug("sign_for_addr %s", sign_for_addr) - if self.using_segwit(): # TODO: Use isSegwitAddress when scantxoutset can use combo + if ( + self.using_segwit() + ): # TODO: Use isSegwitAddress when scantxoutset can use combo # 'Address does not refer to key' for non p2pkh pkh = self.decodeAddress(sign_for_addr) sign_for_addr = self.pkh_to_address(pkh) - self._log.debug('sign_for_addr converted %s', sign_for_addr) + self._log.debug("sign_for_addr converted %s", sign_for_addr) - signature = self.rpc_wallet('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + extra_commit_bytes.hex()]) + signature = self.rpc_wallet( + "signmessage", + [sign_for_addr, sign_for_addr + "_swap_proof_" + extra_commit_bytes.hex()], + ) prove_utxos = [] # TODO: Send specific utxos return (sign_for_addr, signature, prove_utxos) @@ -1363,7 +1724,7 @@ class BTCInterface(Secp256k1Interface): def encodeProofUtxos(self, proof_utxos): packed_utxos = bytes() for utxo in proof_utxos: - packed_utxos += utxo[0] + utxo[1].to_bytes(2, 'big') + packed_utxos += utxo[0] + utxo[1].to_bytes(2, "big") return packed_utxos def decodeProofUtxos(self, msg_utxos): @@ -1372,13 +1733,20 @@ class BTCInterface(Secp256k1Interface): num_utxos = len(msg_utxos) // 34 p: int = 0 for i in range(num_utxos): - proof_utxos.append((msg_utxos[p: p + 32], int.from_bytes(msg_utxos[p + 32: p + 34], 'big'))) + proof_utxos.append( + ( + msg_utxos[p : p + 32], + int.from_bytes(msg_utxos[p + 32 : p + 34], "big"), + ) + ) p += 34 return proof_utxos def verifyProofOfFunds(self, address, signature, utxos, extra_commit_bytes): - passed = self.verifyMessage(address, address + '_swap_proof_' + extra_commit_bytes.hex(), signature) - ensure(passed is True, 'Proof of funds signature invalid') + passed = self.verifyMessage( + address, address + "_swap_proof_" + extra_commit_bytes.hex(), signature + ) + ensure(passed is True, "Proof of funds signature invalid") if self.using_segwit(): address = self.encodeSegwitAddress(decodeAddress(address)[1:]) @@ -1386,51 +1754,51 @@ class BTCInterface(Secp256k1Interface): return self.getUTXOBalance(address) def isWalletEncrypted(self) -> bool: - wallet_info = self.rpc_wallet('getwalletinfo') - return 'unlocked_until' in wallet_info + wallet_info = self.rpc_wallet("getwalletinfo") + return "unlocked_until" in wallet_info def isWalletLocked(self) -> bool: - wallet_info = self.rpc_wallet('getwalletinfo') - if 'unlocked_until' in wallet_info and wallet_info['unlocked_until'] <= 0: + wallet_info = self.rpc_wallet("getwalletinfo") + if "unlocked_until" in wallet_info and wallet_info["unlocked_until"] <= 0: return True return False def isWalletEncryptedLocked(self) -> (bool, bool): - wallet_info = self.rpc_wallet('getwalletinfo') - encrypted = 'unlocked_until' in wallet_info - locked = encrypted and wallet_info['unlocked_until'] <= 0 + wallet_info = self.rpc_wallet("getwalletinfo") + encrypted = "unlocked_until" in wallet_info + locked = encrypted and wallet_info["unlocked_until"] <= 0 return encrypted, locked def changeWalletPassword(self, old_password: str, new_password: str): - self._log.info('changeWalletPassword - {}'.format(self.ticker())) - if old_password == '': + self._log.info("changeWalletPassword - {}".format(self.ticker())) + if old_password == "": if self.isWalletEncrypted(): - raise ValueError('Old password must be set') - return self.rpc_wallet('encryptwallet', [new_password]) - self.rpc_wallet('walletpassphrasechange', [old_password, new_password]) + raise ValueError("Old password must be set") + return self.rpc_wallet("encryptwallet", [new_password]) + self.rpc_wallet("walletpassphrasechange", [old_password, new_password]) def unlockWallet(self, password: str): - if password == '': + if password == "": return - self._log.info('unlockWallet - {}'.format(self.ticker())) + self._log.info("unlockWallet - {}".format(self.ticker())) if self.coin_type() == Coins.BTC: # Recreate wallet if none found # Required when encrypting an existing btc wallet, workaround is to delete the btc wallet and recreate - wallets = self.rpc('listwallets') + wallets = self.rpc("listwallets") if len(wallets) < 1: - self._log.info('Creating wallet.dat for {}.'.format(self.coin_name())) + self._log.info("Creating wallet.dat for {}.".format(self.coin_name())) # wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors - self.rpc('createwallet', ['wallet.dat', False, True, '', False, False]) - self.rpc_wallet('encryptwallet', [password]) + self.rpc("createwallet", ["wallet.dat", False, True, "", False, False]) + self.rpc_wallet("encryptwallet", [password]) # Max timeout value, ~3 years - self.rpc_wallet('walletpassphrase', [password, 100000000]) + self.rpc_wallet("walletpassphrase", [password, 100000000]) self._sc.checkWalletSeed(self.coin_type()) def lockWallet(self): - self._log.info('lockWallet - {}'.format(self.ticker())) - self.rpc_wallet('walletlock') + self._log.info("lockWallet - {}".format(self.ticker())) + self.rpc_wallet("walletlock") def get_p2sh_script_pubkey(self, script: bytearray) -> bytearray: script_hash = hash160(script) @@ -1443,31 +1811,48 @@ class BTCInterface(Secp256k1Interface): def findTxnByHash(self, txid_hex: str): # Only works for wallet txns try: - rv = self.rpc_wallet('gettransaction', [txid_hex]) - except Exception as ex: - self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) + rv = self.rpc_wallet("gettransaction", [txid_hex]) + except Exception as e: # noqa: F841 + self._log.debug( + "findTxnByHash getrawtransaction failed: {}".format(txid_hex) + ) return None - if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: - return {'txid': txid_hex, 'amount': 0, 'height': rv['blockheight']} + if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed: + return {"txid": txid_hex, "amount": 0, "height": rv["blockheight"]} return None - def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes = None) -> str: + def createRedeemTxn( + self, prevout, output_addr: str, output_value: int, txn_script: bytes = None + ) -> str: tx = CTransaction() tx.nVersion = self.txVersion() - prev_txid = b2i(bytes.fromhex(prevout['txid'])) - tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']))) + prev_txid = b2i(bytes.fromhex(prevout["txid"])) + tx.vin.append(CTxIn(COutPoint(prev_txid, prevout["vout"]))) pkh = self.decodeAddress(output_addr) script = self.getScriptForPubkeyHash(pkh) tx.vout.append(self.txoType()(output_value, script)) tx.rehash() return tx.serialize().hex() - def createRefundTxn(self, prevout, output_addr: str, output_value: int, locktime: int, sequence: int, txn_script: bytes = None) -> str: + def createRefundTxn( + self, + prevout, + output_addr: str, + output_value: int, + locktime: int, + sequence: int, + txn_script: bytes = None, + ) -> str: tx = CTransaction() tx.nVersion = self.txVersion() tx.nLockTime = locktime - prev_txid = b2i(bytes.fromhex(prevout['txid'])) - tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']), nSequence=sequence,)) + prev_txid = b2i(bytes.fromhex(prevout["txid"])) + tx.vin.append( + CTxIn( + COutPoint(prev_txid, prevout["vout"]), + nSequence=sequence, + ) + ) pkh = self.decodeAddress(output_addr) script = self.getScriptForPubkeyHash(pkh) tx.vout.append(self.txoType()(output_value, script)) @@ -1476,10 +1861,12 @@ class BTCInterface(Secp256k1Interface): def ensureFunds(self, amount: int) -> None: if self.getSpendableBalance() < amount: - raise ValueError('Balance too low') + raise ValueError("Balance too low") def getHTLCSpendTxVSize(self, redeem: bool = True) -> int: - tx_vsize = 5 # Add a few bytes, sequence in script takes variable amount of bytes + tx_vsize = ( + 5 # Add a few bytes, sequence in script takes variable amount of bytes + ) if self.using_segwit(): tx_vsize += 143 if redeem else 134 else: @@ -1487,7 +1874,7 @@ class BTCInterface(Secp256k1Interface): return tx_vsize def find_prevout_info(self, txn_hex: str, txn_script: bytes): - txjs = self.rpc('decoderawtransaction', [txn_hex]) + txjs = self.rpc("decoderawtransaction", [txn_hex]) if self.using_segwit(): p2wsh = self.getScriptDest(txn_script) @@ -1497,34 +1884,33 @@ class BTCInterface(Secp256k1Interface): n = getVoutByAddress(txjs, addr_to) return { - 'txid': txjs['txid'], - 'vout': n, - 'scriptPubKey': txjs['vout'][n]['scriptPubKey']['hex'], - 'redeemScript': txn_script.hex(), - 'amount': txjs['vout'][n]['value'] + "txid": txjs["txid"], + "vout": n, + "scriptPubKey": txjs["vout"][n]["scriptPubKey"]["hex"], + "redeemScript": txn_script.hex(), + "amount": txjs["vout"][n]["value"], } def inspectSwipeTx(self, tx: dict): - mercy_keyshare = None - for vout in tx['vout']: - script_bytes = bytes.fromhex(vout['scriptPubKey']['hex']) + for vout in tx["vout"]: + script_bytes = bytes.fromhex(vout["scriptPubKey"]["hex"]) if len(script_bytes) < 39: continue if script_bytes[0] != OP_RETURN: continue script_bytes[0] - return script_bytes[7: 7 + 32] + return script_bytes[7 : 7 + 32] return None def isTxExistsError(self, err_str: str) -> bool: - return 'Transaction already in block chain' in err_str + return "Transaction already in block chain" in err_str def isTxNonFinalError(self, err_str: str) -> bool: - return 'non-BIP68-final' in err_str or 'non-final' in err_str + return "non-BIP68-final" in err_str or "non-final" in err_str def testBTCInterface(): - print('TODO: testBTCInterface') + print("TODO: testBTCInterface") if __name__ == "__main__": diff --git a/basicswap/interface/dash.py b/basicswap/interface/dash.py index 8549c16..7215763 100644 --- a/basicswap/interface/dash.py +++ b/basicswap/interface/dash.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2022-2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -11,7 +12,10 @@ from basicswap.util.address import decodeAddress from basicswap.contrib.mnemonic import Mnemonic from basicswap.contrib.test_framework.script import ( CScript, - OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG + OP_DUP, + OP_HASH160, + OP_EQUALVERIFY, + OP_CHECKSIG, ) @@ -22,41 +26,49 @@ class DASHInterface(BTCInterface): def __init__(self, coin_settings, network, swap_client=None): super().__init__(coin_settings, network, swap_client) - self._wallet_passphrase = '' + self._wallet_passphrase = "" self._have_checked_seed = False - self._wallet_v20_compatible = False if not swap_client else swap_client.getChainClientSettings(self.coin_type()).get('wallet_v20_compatible', False) + self._wallet_v20_compatible = ( + False + if not swap_client + else swap_client.getChainClientSettings(self.coin_type()).get( + "wallet_v20_compatible", False + ) + ) def decodeAddress(self, address: str) -> bytes: return decodeAddress(address)[1:] def getWalletSeedID(self) -> str: - hdseed: str = self.rpc_wallet('dumphdinfo')['hdseed'] + hdseed: str = self.rpc_wallet("dumphdinfo")["hdseed"] return self.getSeedHash(bytes.fromhex(hdseed)).hex() def entropyToMnemonic(self, key: bytes) -> None: - return Mnemonic('english').to_mnemonic(key) + return Mnemonic("english").to_mnemonic(key) def initialiseWallet(self, key_bytes: bytes) -> None: self._have_checked_seed = False if self._wallet_v20_compatible: - self._log.warning('Generating wallet compatible with v20 seed.') + self._log.warning("Generating wallet compatible with v20 seed.") words = self.entropyToMnemonic(key_bytes) - mnemonic_passphrase = '' - self.rpc_wallet('upgradetohd', [words, mnemonic_passphrase, self._wallet_passphrase]) + mnemonic_passphrase = "" + self.rpc_wallet( + "upgradetohd", [words, mnemonic_passphrase, self._wallet_passphrase] + ) self._have_checked_seed = False - if self._wallet_passphrase != '': + if self._wallet_passphrase != "": self.unlockWallet(self._wallet_passphrase) return key_wif = self.encodeKey(key_bytes) - self.rpc_wallet('sethdseed', [True, key_wif]) + self.rpc_wallet("sethdseed", [True, key_wif]) def checkExpectedSeed(self, expect_seedid: str) -> bool: self._expect_seedid_hex = expect_seedid - rv = self.rpc_wallet('dumphdinfo') - if rv['mnemonic'] != '': - entropy = Mnemonic('english').to_entropy(rv['mnemonic'].split(' ')) + rv = self.rpc_wallet("dumphdinfo") + if rv["mnemonic"] != "": + entropy = Mnemonic("english").to_entropy(rv["mnemonic"].split(" ")) entropy_hash = self.getAddressHashFromKey(entropy)[::-1].hex() have_expected_seed: bool = expect_seedid == entropy_hash else: @@ -65,11 +77,11 @@ class DASHInterface(BTCInterface): return have_expected_seed def withdrawCoin(self, value, addr_to, subfee): - params = [addr_to, value, '', '', subfee, False, False, self._conf_target] - return self.rpc_wallet('sendtoaddress', params) + params = [addr_to, value, "", "", subfee, False, False, self._conf_target] + return self.rpc_wallet("sendtoaddress", params) def getSpendableBalance(self) -> int: - return self.make_int(self.rpc_wallet('getwalletinfo')['balance']) + return self.make_int(self.rpc_wallet("getwalletinfo")["balance"]) def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray: # Return P2PKH @@ -79,19 +91,23 @@ class DASHInterface(BTCInterface): add_bytes = 107 size = len(tx.serialize_with_witness()) + add_bytes pay_fee = round(fee_rate * size / 1000) - self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.') + self._log.info( + f"BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}." + ) return pay_fee def findTxnByHash(self, txid_hex: str): # Only works for wallet txns try: - rv = self.rpc_wallet('gettransaction', [txid_hex]) - except Exception as ex: - self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) + rv = self.rpc_wallet("gettransaction", [txid_hex]) + except Exception as e: # noqa: F841 + self._log.debug( + "findTxnByHash getrawtransaction failed: {}".format(txid_hex) + ) return None - if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: - block_height = self.getBlockHeader(rv['blockhash'])['height'] - return {'txid': txid_hex, 'amount': 0, 'height': block_height} + if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed: + block_height = self.getBlockHeader(rv["blockhash"])["height"] + return {"txid": txid_hex, "amount": 0, "height": block_height} return None @@ -105,8 +121,8 @@ class DASHInterface(BTCInterface): self._sc.checkWalletSeed(self.coin_type()) except Exception as ex: # dumphdinfo can fail if the wallet is not initialised - self._log.debug(f'DASH checkWalletSeed failed: {ex}.') + self._log.debug(f"DASH checkWalletSeed failed: {ex}.") def lockWallet(self): super().lockWallet() - self._wallet_passphrase = '' + self._wallet_passphrase = "" diff --git a/basicswap/interface/dcr/__init__.py b/basicswap/interface/dcr/__init__.py index 5f44f91..77665f8 100644 --- a/basicswap/interface/dcr/__init__.py +++ b/basicswap/interface/dcr/__init__.py @@ -1,4 +1,5 @@ - from .dcr import DCRInterface -__all__ = ['DCRInterface',] +__all__ = [ + "DCRInterface", +] diff --git a/basicswap/interface/dcr/dcr.py b/basicswap/interface/dcr/dcr.py index 3ad5d80..6a8d436 100644 --- a/basicswap/interface/dcr/dcr.py +++ b/basicswap/interface/dcr/dcr.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -12,10 +13,7 @@ import logging import random import traceback -from basicswap.basicswap_util import ( - getVoutByScriptPubKey, - TxLockTypes -) +from basicswap.basicswap_util import getVoutByScriptPubKey, TxLockTypes from basicswap.chainparams import Coins from basicswap.contrib.test_framework.script import ( CScriptNum, @@ -29,7 +27,10 @@ from basicswap.interface.btc import ( ) from basicswap.util import ( ensure, - b2h, b2i, i2b, i2h, + b2h, + b2i, + i2b, + i2h, ) from basicswap.util.address import ( b58decode, @@ -77,64 +78,68 @@ from coincurve.ecdsaotves import ( ecdsaotves_enc_sign, ecdsaotves_enc_verify, ecdsaotves_dec_sig, - ecdsaotves_rec_enc_key + ecdsaotves_rec_enc_key, ) SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds -SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22) -SEQUENCE_LOCKTIME_MASK = 0x0000f +SEQUENCE_LOCKTIME_TYPE_FLAG = 1 << 22 +SEQUENCE_LOCKTIME_MASK = 0x0000F SigHashSerializePrefix: int = 1 SigHashSerializeWitness: int = 3 -def DCRSignatureHash(sign_script: bytes, hash_type: SigHashType, tx: CTransaction, idx: int) -> bytes: +def DCRSignatureHash( + sign_script: bytes, hash_type: SigHashType, tx: CTransaction, idx: int +) -> bytes: masked_hash_type = hash_type & SigHashType.SigHashMask if masked_hash_type != SigHashType.SigHashAll: - raise ValueError('todo') + raise ValueError("todo") # Prefix hash sign_tx_in_idx: int = idx sign_vins = tx.vin if hash_type & SigHashType.SigHashAnyOneCanPay != 0: - sign_vins = [tx.vin[idx],] + sign_vins = [ + tx.vin[idx], + ] sign_tx_in_idx = 0 hash_buffer = bytearray() version: int = tx.version | (SigHashSerializePrefix << 16) - hash_buffer += version.to_bytes(4, 'little') + hash_buffer += version.to_bytes(4, "little") hash_buffer += encode_varint(len(sign_vins)) for txi_n, txi in enumerate(sign_vins): - hash_buffer += txi.prevout.hash.to_bytes(32, 'little') - hash_buffer += txi.prevout.n.to_bytes(4, 'little') - hash_buffer += txi.prevout.tree.to_bytes(1, 'little') + hash_buffer += txi.prevout.hash.to_bytes(32, "little") + hash_buffer += txi.prevout.n.to_bytes(4, "little") + hash_buffer += txi.prevout.tree.to_bytes(1, "little") # In the case of SigHashNone and SigHashSingle, commit to 0 for everything that is not the input being signed instead. - if (masked_hash_type == SigHashType.SigHashNone - or masked_hash_type == SigHashType.SigHashSingle) and \ - sign_tx_in_idx != txi_n: - hash_buffer += (0).to_bytes(4, 'little') + if ( + masked_hash_type == SigHashType.SigHashNone + or masked_hash_type == SigHashType.SigHashSingle + ) and sign_tx_in_idx != txi_n: + hash_buffer += (0).to_bytes(4, "little") else: - hash_buffer += txi.sequence.to_bytes(4, 'little') + hash_buffer += txi.sequence.to_bytes(4, "little") hash_buffer += encode_varint(len(tx.vout)) for txo_n, txo in enumerate(tx.vout): - if masked_hash_type == SigHashType.SigHashSingle and \ - idx != txo_n: - hash_buffer += (-1).to_bytes(8, 'little') - hash_buffer += txo.version.to_bytes(2, 'little') + if masked_hash_type == SigHashType.SigHashSingle and idx != txo_n: + hash_buffer += (-1).to_bytes(8, "little") + hash_buffer += txo.version.to_bytes(2, "little") hash_buffer += encode_varint(0) continue - hash_buffer += txo.value.to_bytes(8, 'little') - hash_buffer += txo.version.to_bytes(2, 'little') + hash_buffer += txo.value.to_bytes(8, "little") + hash_buffer += txo.version.to_bytes(2, "little") hash_buffer += encode_varint(len(txo.script_pubkey)) hash_buffer += txo.script_pubkey - hash_buffer += tx.locktime.to_bytes(4, 'little') - hash_buffer += tx.expiry.to_bytes(4, 'little') + hash_buffer += tx.locktime.to_bytes(4, "little") + hash_buffer += tx.expiry.to_bytes(4, "little") prefix_hash = blake256(hash_buffer) @@ -142,7 +147,7 @@ def DCRSignatureHash(sign_script: bytes, hash_type: SigHashType, tx: CTransactio hash_buffer.clear() version: int = tx.version | (SigHashSerializeWitness << 16) - hash_buffer += version.to_bytes(4, 'little') + hash_buffer += version.to_bytes(4, "little") hash_buffer += encode_varint(len(sign_vins)) for txi_n, txi in enumerate(sign_vins): @@ -155,7 +160,7 @@ def DCRSignatureHash(sign_script: bytes, hash_type: SigHashType, tx: CTransactio witness_hash = blake256(hash_buffer) hash_buffer.clear() - hash_buffer += hash_type.to_bytes(4, 'little') + hash_buffer += hash_type.to_bytes(4, "little") hash_buffer += prefix_hash hash_buffer += witness_hash @@ -168,11 +173,11 @@ def extract_sig_and_pk(sig_script: bytes) -> (bytes, bytes): o: int = 0 num_bytes = sig_script[o] o += 1 - sig = sig_script[o: o + num_bytes] + sig = sig_script[o : o + num_bytes] o += num_bytes num_bytes = sig_script[o] o += 1 - pk = sig_script[o: o + num_bytes] + pk = sig_script[o : o + num_bytes] return sig, pk @@ -216,23 +221,25 @@ class DCRInterface(Secp256k1Interface): @staticmethod def getExpectedSequence(lockType: int, lockVal: int) -> int: - ensure(lockVal >= 1, 'Bad lockVal') + ensure(lockVal >= 1, "Bad lockVal") if lockType == TxLockTypes.SEQUENCE_LOCK_BLOCKS: return lockVal if lockType == TxLockTypes.SEQUENCE_LOCK_TIME: secondsLocked = lockVal # Ensure the locked time is never less than lockVal if secondsLocked % (1 << SEQUENCE_LOCKTIME_GRANULARITY) != 0: - secondsLocked += (1 << SEQUENCE_LOCKTIME_GRANULARITY) + secondsLocked += 1 << SEQUENCE_LOCKTIME_GRANULARITY secondsLocked >>= SEQUENCE_LOCKTIME_GRANULARITY return secondsLocked | SEQUENCE_LOCKTIME_TYPE_FLAG - raise ValueError('Unknown lock type') + raise ValueError("Unknown lock type") @staticmethod def decodeSequence(lock_value: int) -> int: # Return the raw value if lock_value & SEQUENCE_LOCKTIME_TYPE_FLAG: - return (lock_value & SEQUENCE_LOCKTIME_MASK) << SEQUENCE_LOCKTIME_GRANULARITY + return ( + lock_value & SEQUENCE_LOCKTIME_MASK + ) << SEQUENCE_LOCKTIME_GRANULARITY return lock_value & SEQUENCE_LOCKTIME_MASK @staticmethod @@ -245,24 +252,26 @@ class DCRInterface(Secp256k1Interface): def __init__(self, coin_settings, network, swap_client=None): super().__init__(network) - self._rpc_host = coin_settings.get('rpchost', '127.0.0.1') - self._rpcport = coin_settings['rpcport'] - self._rpcauth = coin_settings['rpcauth'] + self._rpc_host = coin_settings.get("rpchost", "127.0.0.1") + self._rpcport = coin_settings["rpcport"] + self._rpcauth = coin_settings["rpcauth"] self._sc = swap_client self._log = self._sc.log if self._sc and self._sc.log else logging self.rpc = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) - if 'walletrpcport' in coin_settings: - self._walletrpcport = coin_settings['walletrpcport'] - self.rpc_wallet = make_rpc_func(self._walletrpcport, self._rpcauth, host=self._rpc_host) + if "walletrpcport" in coin_settings: + self._walletrpcport = coin_settings["walletrpcport"] + self.rpc_wallet = make_rpc_func( + self._walletrpcport, self._rpcauth, host=self._rpc_host + ) else: self._walletrpcport = None self.rpc_wallet = None - self.blocks_confirmed = coin_settings['blocks_confirmed'] - self.setConfTarget(coin_settings['conf_target']) + self.blocks_confirmed = coin_settings["blocks_confirmed"] + self.setConfTarget(coin_settings["conf_target"]) self._use_segwit = True # Decred is natively segwit - self._connection_type = coin_settings['connection_type'] - self._altruistic = coin_settings.get('altruistic', True) + self._connection_type = coin_settings["connection_type"] + self._altruistic = coin_settings.get("altruistic", True) def open_rpc(self): return openrpc(self._rpcport, self._rpcauth, host=self._rpc_host) @@ -270,13 +279,13 @@ class DCRInterface(Secp256k1Interface): def json_request(self, rpc_conn, method, params): try: v = rpc_conn.json_request(method, params) - r = json.loads(v.decode('utf-8')) + r = json.loads(v.decode("utf-8")) except Exception as ex: traceback.print_exc() - raise ValueError('RPC Server Error ' + str(ex)) - if 'error' in r and r['error'] is not None: - raise ValueError('RPC error ' + str(r['error'])) - return r['result'] + raise ValueError("RPC Server Error " + str(ex)) + if "error" in r and r["error"] is not None: + raise ValueError("RPC error " + str(r["error"])) + return r["result"] def close_rpc(self, rpc_conn): rpc_conn.close() @@ -288,16 +297,16 @@ class DCRInterface(Secp256k1Interface): return ripemd160(blake256(pubkey)) def pkh_to_address(self, pkh: bytes) -> str: - prefix = self.chainparams_network()['pubkey_address'] + prefix = self.chainparams_network()["pubkey_address"] - data = prefix.to_bytes(2, 'big') + pkh + data = prefix.to_bytes(2, "big") + pkh checksum = blake256(blake256(data)) return b58encode(data + checksum[0:4]) def sh_to_address(self, sh: bytes) -> str: - assert (len(sh) == 20) - prefix = self.chainparams_network()['script_address'] - data = prefix.to_bytes(2, 'big') + sh + assert len(sh) == 20 + prefix = self.chainparams_network()["script_address"] + data = prefix.to_bytes(2, "big") + sh checksum = blake256(blake256(data)) return b58encode(data + checksum[0:4]) @@ -309,7 +318,7 @@ class DCRInterface(Secp256k1Interface): prefixed_data = addr_data[:-4] checksum = addr_data[-4:] if blake256(blake256(prefixed_data))[:4] != checksum: - raise ValueError('Checksum mismatch') + raise ValueError("Checksum mismatch") return prefixed_data def decodeAddress(self, address: str) -> bytes: @@ -317,12 +326,12 @@ class DCRInterface(Secp256k1Interface): def testDaemonRPC(self, with_wallet=True) -> None: if with_wallet: - self.rpc_wallet('getinfo') + self.rpc_wallet("getinfo") else: - self.rpc('getblockchaininfo') + self.rpc("getblockchaininfo") def getChainHeight(self) -> int: - return self.rpc('getblockcount') + return self.rpc("getblockcount") def initialiseWallet(self, key: bytes) -> None: # Load with --create @@ -332,42 +341,42 @@ class DCRInterface(Secp256k1Interface): return True def isWalletLocked(self) -> bool: - walletislocked = self.rpc_wallet('walletislocked') + walletislocked = self.rpc_wallet("walletislocked") return walletislocked def isWalletEncryptedLocked(self) -> (bool, bool): - walletislocked = self.rpc_wallet('walletislocked') + walletislocked = self.rpc_wallet("walletislocked") return True, walletislocked def changeWalletPassword(self, old_password: str, new_password: str): - self._log.info('changeWalletPassword - {}'.format(self.ticker())) - if old_password == '': + self._log.info("changeWalletPassword - {}".format(self.ticker())) + if old_password == "": # Read initial pwd from settings settings = self._sc.getChainClientSettings(self.coin_type()) - old_password = settings['wallet_pwd'] - self.rpc_wallet('walletpassphrasechange', [old_password, new_password]) + old_password = settings["wallet_pwd"] + self.rpc_wallet("walletpassphrasechange", [old_password, new_password]) # Lock wallet to match other coins - self.rpc_wallet('walletlock') + self.rpc_wallet("walletlock") # Clear initial password - self._sc.editSettings(self.coin_name().lower(), {'wallet_pwd': ''}) + self._sc.editSettings(self.coin_name().lower(), {"wallet_pwd": ""}) def unlockWallet(self, password: str): - if password == '': + if password == "": return - self._log.info('unlockWallet - {}'.format(self.ticker())) + self._log.info("unlockWallet - {}".format(self.ticker())) # Max timeout value, ~3 years - self.rpc_wallet('walletpassphrase', [password, 100000000]) + self.rpc_wallet("walletpassphrase", [password, 100000000]) self._sc.checkWalletSeed(self.coin_type()) def lockWallet(self): - self._log.info('lockWallet - {}'.format(self.ticker())) - self.rpc_wallet('walletlock') + self._log.info("lockWallet - {}".format(self.ticker())) + self.rpc_wallet("walletlock") def getWalletSeedID(self): - masterpubkey = self.rpc_wallet('getmasterpubkey') + masterpubkey = self.rpc_wallet("getmasterpubkey") masterpubkey_data = self.decode_address(masterpubkey)[4:] return hash160(masterpubkey_data).hex() @@ -378,39 +387,42 @@ class DCRInterface(Secp256k1Interface): return rv def getDaemonVersion(self): - return self.rpc('getnetworkinfo')['version'] + return self.rpc("getnetworkinfo")["version"] def getBlockchainInfo(self): - bci = self.rpc('getblockchaininfo') + bci = self.rpc("getblockchaininfo") # Adjust verificationprogress to consider blocks wallet has synced - wallet_blocks = self.rpc_wallet('getinfo')['blocks'] - synced_ind = bci['verificationprogress'] - wallet_synced_ind = wallet_blocks / bci['headers'] + wallet_blocks = self.rpc_wallet("getinfo")["blocks"] + synced_ind = bci["verificationprogress"] + wallet_synced_ind = wallet_blocks / bci["headers"] if wallet_synced_ind < synced_ind: - bci['verificationprogress'] = wallet_synced_ind + bci["verificationprogress"] = wallet_synced_ind return bci def getWalletInfo(self): rv = {} - rv = self.rpc_wallet('getinfo') - wi = self.rpc_wallet('walletinfo') - balances = self.rpc_wallet('getbalance') + rv = self.rpc_wallet("getinfo") + wi = self.rpc_wallet("walletinfo") + balances = self.rpc_wallet("getbalance") - default_account_bal = balances['balances'][0] # 0 always default? - rv['balance'] = default_account_bal['spendable'] - rv['unconfirmed_balance'] = default_account_bal['unconfirmed'] - rv['immature_balance'] = default_account_bal['immaturecoinbaserewards'] + default_account_bal['immaturestakegeneration'] - rv['encrypted'] = True - rv['locked'] = True if wi['unlocked'] is False else False + default_account_bal = balances["balances"][0] # 0 always default? + rv["balance"] = default_account_bal["spendable"] + rv["unconfirmed_balance"] = default_account_bal["unconfirmed"] + rv["immature_balance"] = ( + default_account_bal["immaturecoinbaserewards"] + + default_account_bal["immaturestakegeneration"] + ) + rv["encrypted"] = True + rv["locked"] = True if wi["unlocked"] is False else False return rv def getSpendableBalance(self) -> int: - balances = self.rpc_wallet('getbalance') - default_account_bal = balances['balances'][0] # 0 always default? - return self.make_int(default_account_bal['spendable']) + balances = self.rpc_wallet("getbalance") + default_account_bal = balances["balances"][0] # 0 always default? + return self.make_int(default_account_bal["spendable"]) def getSeedHash(self, seed: bytes, coin_type_id=None) -> bytes: # m / purpose' / coin_type' / account' / change / address_index @@ -419,7 +431,11 @@ class DCRInterface(Secp256k1Interface): ek = ExtKeyPair(self.coin_type()) ek.set_seed(seed) - coin_type = self.chainparams_network()['bip44'] if coin_type_id is None else coin_type_id + coin_type = ( + self.chainparams_network()["bip44"] + if coin_type_id is None + else coin_type_id + ) ek_purpose = ek.derive(44 | (1 << 31)) ek_coin = ek_purpose.derive(coin_type | (1 << 31)) ek_account = ek_coin.derive(0 | (1 << 31)) @@ -432,13 +448,13 @@ class DCRInterface(Secp256k1Interface): key = key[:-4] if blake256(key)[:4] != checksum: - raise ValueError('Checksum mismatch') + raise ValueError("Checksum mismatch") return key[2], key[3:] def encodeKey(self, key_bytes: bytes) -> str: - wif_prefix = self.chainparams_network()['key_prefix'] + wif_prefix = self.chainparams_network()["key_prefix"] key_type = 0 # STEcdsaSecp256k1 - b = wif_prefix.to_bytes(2, 'big') + key_type.to_bytes(1, 'big') + key_bytes + b = wif_prefix.to_bytes(2, "big") + key_type.to_bytes(1, "big") + key_bytes b += blake256(b)[:4] return b58encode(b) @@ -447,14 +463,23 @@ class DCRInterface(Secp256k1Interface): tx.deserialize(tx_bytes) return tx - def signTx(self, key_bytes: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes: + def signTx( + self, + key_bytes: bytes, + tx_bytes: bytes, + input_n: int, + prevout_script: bytes, + prevout_value: int, + ) -> bytes: tx = self.loadTx(tx_bytes) sig_hash = DCRSignatureHash(prevout_script, SigHashType.SigHashAll, tx, input_n) eck = PrivateKey(key_bytes) return eck.sign(sig_hash, hasher=None) + bytes((SigHashType.SigHashAll,)) - def setTxSignatureScript(self, tx_bytes: bytes, script: bytes, txi: int = 0) -> bytes: + def setTxSignatureScript( + self, tx_bytes: bytes, script: bytes, txi: int = 0 + ) -> bytes: tx = self.loadTx(tx_bytes) tx.vin[txi].signature_script = script @@ -468,8 +493,6 @@ class DCRInterface(Secp256k1Interface): push_script_data(script_data, data) tx.vin[txi].signature_script = script_data - test_ser = tx.serialize() - test_tx = self.loadTx(test_ser) return tx.serialize() @@ -479,17 +502,31 @@ class DCRInterface(Secp256k1Interface): def getTxSignature(self, tx_hex: str, prevout_data, key_wif: str) -> str: sig_type, key = self.decodeKey(key_wif) - redeem_script = bytes.fromhex(prevout_data['redeemScript']) - sig = self.signTx(key, bytes.fromhex(tx_hex), 0, redeem_script, self.make_int(prevout_data['amount'])) + redeem_script = bytes.fromhex(prevout_data["redeemScript"]) + sig = self.signTx( + key, + bytes.fromhex(tx_hex), + 0, + redeem_script, + self.make_int(prevout_data["amount"]), + ) return sig.hex() - def verifyTxSig(self, tx_bytes: bytes, sig: bytes, K: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bool: + def verifyTxSig( + self, + tx_bytes: bytes, + sig: bytes, + K: bytes, + input_n: int, + prevout_script: bytes, + prevout_value: int, + ) -> bool: tx = self.loadTx(tx_bytes) sig_hash = DCRSignatureHash(prevout_script, SigHashType.SigHashAll, tx, input_n) pubkey = PublicKey(K) - return pubkey.verify(sig[: -1], sig_hash, hasher=None) # Pop the hashtype byte + return pubkey.verify(sig[:-1], sig_hash, hasher=None) # Pop the hashtype byte def getTxid(self, tx) -> bytes: if isinstance(tx, str): @@ -503,7 +540,12 @@ class DCRInterface(Secp256k1Interface): script_hash = self.pkh(script) assert len(script_hash) == 20 - return bytes((OP_HASH160,)) + bytes((len(script_hash),)) + script_hash + bytes((OP_EQUAL,)) + return ( + bytes((OP_HASH160,)) + + bytes((len(script_hash),)) + + script_hash + + bytes((OP_EQUAL,)) + ) def encodeScriptDest(self, script_dest: bytes) -> str: script_hash = script_dest[2:-1] # Extract hash from script @@ -512,7 +554,14 @@ class DCRInterface(Secp256k1Interface): def getPubkeyHashDest(self, pkh: bytes) -> bytes: # P2PKH assert len(pkh) == 20 - return bytes((OP_DUP,)) + bytes((OP_HASH160,)) + bytes((len(pkh),)) + pkh + bytes((OP_EQUALVERIFY,)) + bytes((OP_CHECKSIG,)) + return ( + bytes((OP_DUP,)) + + bytes((OP_HASH160,)) + + bytes((len(pkh),)) + + pkh + + bytes((OP_EQUALVERIFY,)) + + bytes((OP_CHECKSIG,)) + ) def getPkDest(self, K: bytes) -> bytearray: return self.getPubkeyHashDest(self.pkh(K)) @@ -522,37 +571,50 @@ class DCRInterface(Secp256k1Interface): return self.encodeScriptDest(lock_tx_dest) def get_fee_rate(self, conf_target: int = 2) -> (float, str): - chain_client_settings = self._sc.getChainClientSettings(self.coin_type()) # basicswap.json - override_feerate = chain_client_settings.get('override_feerate', None) + chain_client_settings = self._sc.getChainClientSettings( + self.coin_type() + ) # basicswap.json + override_feerate = chain_client_settings.get("override_feerate", None) if override_feerate: - self._log.debug('Fee rate override used for %s: %f', self.coin_name(), override_feerate) - return override_feerate, 'override_feerate' + self._log.debug( + "Fee rate override used for %s: %f", self.coin_name(), override_feerate + ) + return override_feerate, "override_feerate" - min_relay_fee = chain_client_settings.get('min_relay_fee', None) + min_relay_fee = chain_client_settings.get("min_relay_fee", None) def try_get_fee_rate(self, conf_target): # TODO: How to estimate required fee? try: - fee_rate: float = self.rpc_wallet('walletinfo')['txfee'] - assert (fee_rate > 0.0), 'Non positive feerate' - return fee_rate, 'paytxfee' + fee_rate: float = self.rpc_wallet("walletinfo")["txfee"] + assert fee_rate > 0.0, "Non positive feerate" + return fee_rate, "paytxfee" except Exception: - fee_rate: float = self.rpc('getnetworkinfo')['relayfee'] - return fee_rate, 'relayfee' + fee_rate: float = self.rpc("getnetworkinfo")["relayfee"] + return fee_rate, "relayfee" fee_rate, rate_src = try_get_fee_rate(self, conf_target) if min_relay_fee and min_relay_fee > fee_rate: - self._log.warning('Feerate {} ({}) is below min relay fee {} for {}'.format(self.format_amount(fee_rate, True, 1), rate_src, self.format_amount(min_relay_fee, True, 1), self.coin_name())) - return min_relay_fee, 'min_relay_fee' + self._log.warning( + "Feerate {} ({}) is below min relay fee {} for {}".format( + self.format_amount(fee_rate, True, 1), + rate_src, + self.format_amount(min_relay_fee, True, 1), + self.coin_name(), + ) + ) + return min_relay_fee, "min_relay_fee" return fee_rate, rate_src - def getNewAddress(self, use_segwit: bool = True, label: str = 'swap_receive') -> str: - return self.rpc_wallet('getnewaddress') + def getNewAddress( + self, use_segwit: bool = True, label: str = "swap_receive" + ) -> str: + return self.rpc_wallet("getnewaddress") def getWalletTransaction(self, txid: bytes): try: - return bytes.fromhex(self.rpc_wallet('gettransaction', [txid.hex()])['hex']) - except Exception as ex: + return bytes.fromhex(self.rpc_wallet("gettransaction", [txid.hex()])["hex"]) + except Exception as e: # noqa: F841 # TODO: filter errors return None @@ -560,25 +622,27 @@ class DCRInterface(Secp256k1Interface): # TODO: Lock unspent and use same output/s to fund bid unspents_by_addr = dict() - unspents = self.rpc_wallet('listunspent') + unspents = self.rpc_wallet("listunspent") if unspents is None: unspents = [] for u in unspents: - if u['spendable'] is not True: + if u["spendable"] is not True: continue - if u['address'] not in unspents_by_addr: - unspents_by_addr[u['address']] = {'total': 0, 'utxos': []} - utxo_amount: int = self.make_int(u['amount'], r=1) - unspents_by_addr[u['address']]['total'] += utxo_amount - unspents_by_addr[u['address']]['utxos'].append((utxo_amount, u['txid'], u['vout'], u['tree'])) + if u["address"] not in unspents_by_addr: + unspents_by_addr[u["address"]] = {"total": 0, "utxos": []} + utxo_amount: int = self.make_int(u["amount"], r=1) + unspents_by_addr[u["address"]]["total"] += utxo_amount + unspents_by_addr[u["address"]]["utxos"].append( + (utxo_amount, u["txid"], u["vout"], u["tree"]) + ) max_utxos: int = 4 viable_addrs = [] for addr, data in unspents_by_addr.items(): - if data['total'] >= amount_for: + if data["total"] >= amount_for: # Sort from largest to smallest amount - sorted_utxos = sorted(data['utxos'], key=lambda x: x[0]) + sorted_utxos = sorted(data["utxos"], key=lambda x: x[0]) # Max outputs required to reach amount_for utxos_req: int = 0 @@ -593,13 +657,17 @@ class DCRInterface(Secp256k1Interface): viable_addrs.append(addr) continue - ensure(len(viable_addrs) > 0, 'Could not find address with enough funds for proof') + ensure( + len(viable_addrs) > 0, "Could not find address with enough funds for proof" + ) sign_for_addr: str = random.choice(viable_addrs) - self._log.debug('sign_for_addr %s', sign_for_addr) + self._log.debug("sign_for_addr %s", sign_for_addr) prove_utxos = [] - sorted_utxos = sorted(unspents_by_addr[sign_for_addr]['utxos'], key=lambda x: x[0]) + sorted_utxos = sorted( + unspents_by_addr[sign_for_addr]["utxos"], key=lambda x: x[0] + ) hasher = hashlib.sha256() sum_value: int = 0 @@ -608,30 +676,41 @@ class DCRInterface(Secp256k1Interface): outpoint = (bytes.fromhex(utxo[1]), utxo[2], utxo[3]) prove_utxos.append(outpoint) hasher.update(outpoint[0]) - hasher.update(outpoint[1].to_bytes(2, 'big')) - hasher.update(outpoint[2].to_bytes(1, 'big')) + hasher.update(outpoint[1].to_bytes(2, "big")) + hasher.update(outpoint[2].to_bytes(1, "big")) if sum_value >= amount_for: break utxos_hash = hasher.digest() - signature = self.rpc_wallet('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()]) + signature = self.rpc_wallet( + "signmessage", + [ + sign_for_addr, + sign_for_addr + + "_swap_proof_" + + utxos_hash.hex() + + extra_commit_bytes.hex(), + ], + ) return (sign_for_addr, signature, prove_utxos) def withdrawCoin(self, value: float, addr_to: str, subfee: bool = False) -> str: if subfee: - raise ValueError('TODO') + raise ValueError("TODO") params = [addr_to, float(value)] - return self.rpc_wallet('sendtoaddress', params) + return self.rpc_wallet("sendtoaddress", params) def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool: - addr_info = self.rpc_wallet('validateaddress', [address]) - return addr_info.get('ismine', False) + addr_info = self.rpc_wallet("validateaddress", [address]) + return addr_info.get("ismine", False) def encodeProofUtxos(self, proof_utxos): packed_utxos = bytes() for utxo in proof_utxos: - packed_utxos += utxo[0] + utxo[1].to_bytes(2, 'big') + utxo[2].to_bytes(1, 'big') + packed_utxos += ( + utxo[0] + utxo[1].to_bytes(2, "big") + utxo[2].to_bytes(1, "big") + ) return packed_utxos def decodeProofUtxos(self, msg_utxos): @@ -640,65 +719,86 @@ class DCRInterface(Secp256k1Interface): num_utxos = len(msg_utxos) // 34 p: int = 0 for i in range(num_utxos): - proof_utxos.append((msg_utxos[p: p + 32], int.from_bytes(msg_utxos[p + 32: p + 34], 'big'), msg_utxos[p + 34])) + proof_utxos.append( + ( + msg_utxos[p : p + 32], + int.from_bytes(msg_utxos[p + 32 : p + 34], "big"), + msg_utxos[p + 34], + ) + ) p += 35 return proof_utxos - def verifyProofOfFunds(self, address: str, signature: bytes, utxos, extra_commit_bytes: bytes): + def verifyProofOfFunds( + self, address: str, signature: bytes, utxos, extra_commit_bytes: bytes + ): hasher = hashlib.sha256() sum_value: int = 0 for outpoint in utxos: hasher.update(outpoint[0]) - hasher.update(outpoint[1].to_bytes(2, 'big')) - hasher.update(outpoint[2].to_bytes(1, 'big')) + hasher.update(outpoint[1].to_bytes(2, "big")) + hasher.update(outpoint[2].to_bytes(1, "big")) utxos_hash = hasher.digest() - passed = self.verifyMessage(address, address + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex(), signature) - ensure(passed is True, 'Proof of funds signature invalid') + passed = self.verifyMessage( + address, + address + "_swap_proof_" + utxos_hash.hex() + extra_commit_bytes.hex(), + signature, + ) + ensure(passed is True, "Proof of funds signature invalid") sum_value: int = 0 for outpoint in utxos: - txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1], outpoint[2]]) - sum_value += self.make_int(txout['value']) + txout = self.rpc("gettxout", [outpoint[0].hex(), outpoint[1], outpoint[2]]) + sum_value += self.make_int(txout["value"]) return sum_value def signCompact(self, k, message): - message_hash = blake256(bytes(message, 'utf-8')) + message_hash = blake256(bytes(message, "utf-8")) privkey = PrivateKey(k) return privkey.sign_recoverable(message_hash, hasher=None)[:64] def signRecoverable(self, k, message: str) -> bytes: - message_hash = blake256(bytes(message, 'utf-8')) + message_hash = blake256(bytes(message, "utf-8")) privkey = PrivateKey(k) return privkey.sign_recoverable(message_hash, hasher=None) def verifyCompactSig(self, K, message: str, sig) -> None: - message_hash = blake256(bytes(message, 'utf-8')) + message_hash = blake256(bytes(message, "utf-8")) pubkey = PublicKey(K) rv = pubkey.verify_compact(sig, message_hash, hasher=None) - assert (rv is True) + assert rv is True def verifySigAndRecover(self, sig, message: str) -> bytes: - message_hash = blake256(bytes(message, 'utf-8')) + message_hash = blake256(bytes(message, "utf-8")) pubkey = PublicKey.from_signature_and_message(sig, message_hash, hasher=None) return pubkey.format() - def verifyMessage(self, address: str, message: str, signature: str, message_magic: str = None) -> bool: + def verifyMessage( + self, address: str, message: str, signature: str, message_magic: str = None + ) -> bool: if message_magic is None: - message_magic = self.chainparams()['message_magic'] + message_magic = self.chainparams()["message_magic"] - message_bytes = SerialiseNumCompact(len(message_magic)) + bytes(message_magic, 'utf-8') + SerialiseNumCompact(len(message)) + bytes(message, 'utf-8') + message_bytes = ( + SerialiseNumCompact(len(message_magic)) + + bytes(message_magic, "utf-8") + + SerialiseNumCompact(len(message)) + + bytes(message, "utf-8") + ) message_hash = blake256(message_bytes) signature_bytes = base64.b64decode(signature) rec_id = (signature_bytes[0] - 27) & 3 signature_bytes = signature_bytes[1:] + bytes((rec_id,)) try: - pubkey = PublicKey.from_signature_and_message(signature_bytes, message_hash, hasher=None) + pubkey = PublicKey.from_signature_and_message( + signature_bytes, message_hash, hasher=None + ) except Exception as e: - self._log.info('verifyMessage failed: ' + str(e)) + self._log.info("verifyMessage failed: " + str(e)) return False address_hash = self.decode_address(address)[2:] @@ -707,35 +807,62 @@ class DCRInterface(Secp256k1Interface): return True if address_hash == pubkey_hash else False def signTxWithWallet(self, tx) -> bytes: - return bytes.fromhex(self.rpc_wallet('signrawtransaction', [tx.hex()])['hex']) + return bytes.fromhex(self.rpc_wallet("signrawtransaction", [tx.hex()])["hex"]) def signTxWithKey(self, tx: bytes, key: bytes) -> bytes: key_wif = self.encodeKey(key) - rv = self.rpc_wallet('signrawtransaction', [tx.hex(), [], [key_wif, ]]) - return bytes.fromhex(rv['hex']) + rv = self.rpc_wallet( + "signrawtransaction", + [ + tx.hex(), + [], + [ + key_wif, + ], + ], + ) + return bytes.fromhex(rv["hex"]) - def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str: + def createRawFundedTransaction( + self, + addr_to: str, + amount: int, + sub_fee: bool = False, + lock_unspents: bool = True, + ) -> str: # amount can't be a string, else: Failed to parse request: parameter #2 'amounts' must be type float64 (got string) float_amount = float(self.format_amount(amount)) - txn = self.rpc('createrawtransaction', [[], {addr_to: float_amount}]) + txn = self.rpc("createrawtransaction", [[], {addr_to: float_amount}]) fee_rate, fee_src = self.get_fee_rate(self._conf_target) - self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}') + self._log.debug( + f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}" + ) options = { - 'lockUnspents': lock_unspents, - 'feeRate': fee_rate, + "lockUnspents": lock_unspents, + "feeRate": fee_rate, } if sub_fee: - options['subtractFeeFromOutputs'] = [0,] - return self.rpc_wallet('fundrawtransaction', [txn, 'default', options])['hex'] + options["subtractFeeFromOutputs"] = [ + 0, + ] + return self.rpc_wallet("fundrawtransaction", [txn, "default", options])["hex"] def createRawSignedTransaction(self, addr_to, amount) -> str: txn_funded = self.createRawFundedTransaction(addr_to, amount) - return self.rpc_wallet('signrawtransaction', [txn_funded])['hex'] + return self.rpc_wallet("signrawtransaction", [txn_funded])["hex"] - def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1): + def getLockTxHeight( + self, + txid, + dest_address, + bid_amount, + rescan_from, + find_index: bool = False, + vout: int = -1, + ): if txid is None: - self._log.debug('TODO: getLockTxHeight') + self._log.debug("TODO: getLockTxHeight") return None found_vout = None @@ -743,19 +870,23 @@ class DCRInterface(Secp256k1Interface): if vout is None: test_range = range(2) else: - test_range = (vout, ) + test_range = (vout,) for try_vout in test_range: try: - txout = self.rpc('gettxout', [txid.hex(), try_vout, 0, True]) - addresses = txout['scriptPubKey']['addresses'] + txout = self.rpc("gettxout", [txid.hex(), try_vout, 0, True]) + addresses = txout["scriptPubKey"]["addresses"] if len(addresses) != 1 or addresses[0] != dest_address: continue - if self.make_int(txout['value']) != bid_amount: - self._log.warning('getLockTxHeight found txout {} with incorrect amount {}'.format(txid.hex(), txout['value'])) + if self.make_int(txout["value"]) != bid_amount: + self._log.warning( + "getLockTxHeight found txout {} with incorrect amount {}".format( + txid.hex(), txout["value"] + ) + ) continue found_vout = try_vout break - except Exception as e: + except Exception as e: # noqa: F841 # self._log.warning('gettxout {}'.format(e)) return None @@ -763,54 +894,74 @@ class DCRInterface(Secp256k1Interface): return None block_height: int = 0 - confirmations: int = 0 if 'confirmations' not in txout else txout['confirmations'] + confirmations: int = ( + 0 if "confirmations" not in txout else txout["confirmations"] + ) # TODO: Better way? if confirmations > 0: block_height = self.getChainHeight() - confirmations rv = { - 'txid': txid.hex(), - 'depth': confirmations, - 'index': found_vout, - 'height': block_height} + "txid": txid.hex(), + "depth": confirmations, + "index": found_vout, + "height": block_height, + } return rv def find_prevout_info(self, txn_hex: str, txn_script: bytes): - txjs = self.rpc('decoderawtransaction', [txn_hex]) + txjs = self.rpc("decoderawtransaction", [txn_hex]) n = getVoutByScriptPubKey(txjs, self.getScriptDest(txn_script).hex()) - txo = txjs['vout'][n] + txo = txjs["vout"][n] return { - 'txid': txjs['txid'], - 'vout': n, - 'scriptPubKey': txo['scriptPubKey']['hex'], - 'redeemScript': txn_script.hex(), - 'amount': txo['value'], + "txid": txjs["txid"], + "vout": n, + "scriptPubKey": txo["scriptPubKey"]["hex"], + "redeemScript": txn_script.hex(), + "amount": txo["value"], } def getHTLCSpendTxVSize(self, redeem: bool = True) -> int: - tx_vsize = 5 # Add a few bytes, sequence in script takes variable amount of bytes + tx_vsize = ( + 5 # Add a few bytes, sequence in script takes variable amount of bytes + ) tx_vsize += 348 if redeem else 316 return tx_vsize - def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes = None) -> str: + def createRedeemTxn( + self, prevout, output_addr: str, output_value: int, txn_script: bytes = None + ) -> str: tx = CTransaction() tx.version = self.txVersion() - prev_txid = b2i(bytes.fromhex(prevout['txid'])) - tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout'], 0))) + prev_txid = b2i(bytes.fromhex(prevout["txid"])) + tx.vin.append(CTxIn(COutPoint(prev_txid, prevout["vout"], 0))) pkh = self.decode_address(output_addr)[2:] script = self.getPubkeyHashDest(pkh) tx.vout.append(self.txoType()(output_value, script)) return tx.serialize().hex() - def createRefundTxn(self, prevout, output_addr: str, output_value: int, locktime: int, sequence: int, txn_script: bytes = None) -> str: + def createRefundTxn( + self, + prevout, + output_addr: str, + output_value: int, + locktime: int, + sequence: int, + txn_script: bytes = None, + ) -> str: tx = CTransaction() tx.version = self.txVersion() tx.locktime = locktime - prev_txid = b2i(bytes.fromhex(prevout['txid'])) - tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout'], 0), sequence=sequence,)) + prev_txid = b2i(bytes.fromhex(prevout["txid"])) + tx.vin.append( + CTxIn( + COutPoint(prev_txid, prevout["vout"], 0), + sequence=sequence, + ) + ) pkh = self.decode_address(output_addr)[2:] script = self.getPubkeyHashDest(pkh) tx.vout.append(self.txoType()(output_value, script)) @@ -825,12 +976,14 @@ class DCRInterface(Secp256k1Interface): for i, txi in enumerate(tx.vin): prevout_data = prevouts[i] - redeem_script = bytes.fromhex(prevout_data['redeemScript']) - prevout_value = self.make_int(prevout_data['amount']) + redeem_script = bytes.fromhex(prevout_data["redeemScript"]) + prevout_value = self.make_int(prevout_data["amount"]) sig, pk = extract_sig_and_pk(txi.signature_script) if not sig or not pk: - self._log.warning(f'verifyRawTransaction failed to extract signature for input {i}') + self._log.warning( + f"verifyRawTransaction failed to extract signature for input {i}" + ) continue if self.verifyTxSig(tx_bytes, sig, pk, i, redeem_script, prevout_value): @@ -840,70 +993,80 @@ class DCRInterface(Secp256k1Interface): inputs_valid = True return { - 'inputs_valid': inputs_valid, - 'validscripts': validscripts, + "inputs_valid": inputs_valid, + "validscripts": validscripts, } def getBlockHeaderFromHeight(self, height): - block_hash = self.rpc('getblockhash', [height]) - return self.rpc('getblockheader', [block_hash]) + block_hash = self.rpc("getblockhash", [height]) + return self.rpc("getblockheader", [block_hash]) def getBlockHeaderAt(self, time: int, block_after=False): - blockchaininfo = self.rpc('getblockchaininfo') - last_block_header = self.rpc('getblockheader', [blockchaininfo['bestblockhash']]) + blockchaininfo = self.rpc("getblockchaininfo") + last_block_header = self.rpc( + "getblockheader", [blockchaininfo["bestblockhash"]] + ) max_tries = 15000 for i in range(max_tries): - prev_block_header = self.rpc('getblockheader', [last_block_header['previousblockhash']]) - if prev_block_header['time'] <= time: + prev_block_header = self.rpc( + "getblockheader", [last_block_header["previousblockhash"]] + ) + if prev_block_header["time"] <= time: return last_block_header if block_after else prev_block_header last_block_header = prev_block_header - raise ValueError(f'Block header not found at time: {time}') + raise ValueError(f"Block header not found at time: {time}") def getMempoolTx(self, txid): - raise ValueError('TODO') + raise ValueError("TODO") def getBlockWithTxns(self, block_hash: str): - block = self.rpc('getblock', [block_hash, True, True]) + block = self.rpc("getblock", [block_hash, True, True]) return { - 'hash': block['hash'], - 'previousblockhash': block['previousblockhash'], - 'tx': block['rawtx'], - 'confirmations': block['confirmations'], - 'height': block['height'], - 'time': block['time'], - 'version': block['version'], - 'merkleroot': block['merkleroot'], + "hash": block["hash"], + "previousblockhash": block["previousblockhash"], + "tx": block["rawtx"], + "confirmations": block["confirmations"], + "height": block["height"], + "time": block["time"], + "version": block["version"], + "merkleroot": block["merkleroot"], } def publishTx(self, tx: bytes): - return self.rpc('sendrawtransaction', [tx.hex()]) + return self.rpc("sendrawtransaction", [tx.hex()]) def describeTx(self, tx_hex: str): - return self.rpc('decoderawtransaction', [tx_hex]) + return self.rpc("decoderawtransaction", [tx_hex]) def fundTx(self, tx: bytes, feerate) -> bytes: feerate_str = float(self.format_amount(feerate)) # TODO: unlock unspents if bid cancelled options = { - 'feeRate': feerate_str, + "feeRate": feerate_str, } - rv = self.rpc_wallet('fundrawtransaction', [tx.hex(), 'default', options]) - tx_bytes = bytes.fromhex(rv['hex']) + rv = self.rpc_wallet("fundrawtransaction", [tx.hex(), "default", options]) + tx_bytes = bytes.fromhex(rv["hex"]) tx_obj = self.loadTx(tx_bytes) for txi in tx_obj.vin: - utxos = [{'amount': float(self.format_amount(txi.value_in)), - 'txid': i2h(txi.prevout.hash), - 'vout': txi.prevout.n, - 'tree': txi.prevout.tree}] - rv = self.rpc_wallet('lockunspent', [False, utxos]) + utxos = [ + { + "amount": float(self.format_amount(txi.value_in)), + "txid": i2h(txi.prevout.hash), + "vout": txi.prevout.n, + "tree": txi.prevout.tree, + } + ] + rv = self.rpc_wallet("lockunspent", [False, utxos]) return tx_bytes - def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes: + def createSCLockTx( + self, value: int, script: bytearray, vkbv: bytes = None + ) -> bytes: tx = CTransaction() tx.version = self.txVersion() tx.vout.append(self.txoType()(value, self.getScriptDest(script))) @@ -934,11 +1097,13 @@ class DCRInterface(Secp256k1Interface): return script - def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None, fee_info={}): + def createSCLockSpendTx( + self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None, fee_info={} + ): tx_lock = self.loadTx(tx_lock_bytes) output_script = self.getScriptDest(script_lock) locked_n = findOutput(tx_lock, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx_lock.vout[locked_n].value tx_lock_id_int = b2i(tx_lock.TxHash()) @@ -953,22 +1118,37 @@ class DCRInterface(Secp256k1Interface): pay_fee = round(tx_fee_rate * size / 1000) tx.vout[0].value = locked_coin - pay_fee - fee_info['fee_paid'] = pay_fee - fee_info['rate_used'] = tx_fee_rate - fee_info['size'] = size + fee_info["fee_paid"] = pay_fee + fee_info["rate_used"] = tx_fee_rate + fee_info["size"] = size - self._log.info('createSCLockSpendTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.', - tx.TxHash().hex(), tx_fee_rate, size, pay_fee) + self._log.info( + "createSCLockSpendTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.", + tx.TxHash().hex(), + tx_fee_rate, + size, + pay_fee, + ) return tx.serialize(TxSerializeType.NoWitness) - def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None): + def createSCLockRefundTx( + self, + tx_lock_bytes, + script_lock, + Kal, + Kaf, + lock1_value, + csv_val, + tx_fee_rate, + vkbv=None, + ): tx_lock = CTransaction() tx_lock = self.loadTx(tx_lock_bytes) output_script = self.getScriptDest(script_lock) locked_n = findOutput(tx_lock, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx_lock.vout[locked_n].value tx_lock_id_int = b2i(tx_lock.TxHash()) @@ -976,8 +1156,9 @@ class DCRInterface(Secp256k1Interface): refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val) tx = CTransaction() tx.version = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n, 0), - sequence=lock1_value)) + tx.vin.append( + CTxIn(COutPoint(tx_lock_id_int, locked_n, 0), sequence=lock1_value) + ) tx.vout.append(self.txoType()(locked_coin, self.getScriptDest(refund_script))) dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock) @@ -985,12 +1166,24 @@ class DCRInterface(Secp256k1Interface): pay_fee = round(tx_fee_rate * size / 1000) tx.vout[0].value = locked_coin - pay_fee - self._log.info('createSCLockRefundTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.', - tx.TxHash().hex(), tx_fee_rate, size, pay_fee) + self._log.info( + "createSCLockRefundTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.", + tx.TxHash().hex(), + tx_fee_rate, + size, + pay_fee, + ) return tx.serialize(TxSerializeType.NoWitness), refund_script, tx.vout[0].value - def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv=None): + def createSCLockRefundSpendTx( + self, + tx_lock_refund_bytes, + script_lock_refund, + pkh_refund_to, + tx_fee_rate, + vkbv=None, + ): # Returns the coinA locked coin to the leader # The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey # If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower @@ -999,33 +1192,49 @@ class DCRInterface(Secp256k1Interface): output_script = self.getScriptDest(script_lock_refund) locked_n = findOutput(tx_lock_refund, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx_lock_refund.vout[locked_n].value tx_lock_refund_hash_int = b2i(tx_lock_refund.TxHash()) tx = CTransaction() tx.version = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n, 0), - sequence=0)) + tx.vin.append( + CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n, 0), sequence=0) + ) - tx.vout.append(self.txoType()(locked_coin, self.getPubkeyHashDest(pkh_refund_to))) + tx.vout.append( + self.txoType()(locked_coin, self.getPubkeyHashDest(pkh_refund_to)) + ) - dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(script_lock_refund) + dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness( + script_lock_refund + ) size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack)) pay_fee = round(tx_fee_rate * size / 1000) tx.vout[0].value = locked_coin - pay_fee - self._log.info('createSCLockRefundSpendTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.', - tx.TxHash().hex(), tx_fee_rate, size, pay_fee) + self._log.info( + "createSCLockRefundSpendTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.", + tx.TxHash().hex(), + tx_fee_rate, + size, + pay_fee, + ) return tx.serialize(TxSerializeType.NoWitness) - def verifySCLockTx(self, tx_bytes, script_out, - swap_value, - Kal, Kaf, - feerate, - check_lock_tx_inputs, vkbv=None): + def verifySCLockTx( + self, + tx_bytes, + script_out, + swap_value, + Kal, + Kaf, + feerate, + check_lock_tx_inputs, + vkbv=None, + ): # Verify: # @@ -1035,114 +1244,145 @@ class DCRInterface(Secp256k1Interface): tx = self.loadTx(tx_bytes) txid = self.getTxid(tx) - self._log.info('Verifying lock tx: {}.'.format(b2h(txid))) + self._log.info("Verifying lock tx: {}.".format(b2h(txid))) - ensure(tx.version == self.txVersion(), 'Bad version') - ensure(tx.locktime == 0, 'Bad locktime') - ensure(tx.expiry == 0, 'Bad expiry') + ensure(tx.version == self.txVersion(), "Bad version") + ensure(tx.locktime == 0, "Bad locktime") + ensure(tx.expiry == 0, "Bad expiry") script_pk = self.getScriptDest(script_out) locked_n = findOutput(tx, script_pk) - ensure(locked_n is not None, 'Lock output not found in tx') + ensure(locked_n is not None, "Lock output not found in tx") locked_coin = tx.vout[locked_n].value # Check value - ensure(locked_coin == swap_value, 'Bad locked value') + ensure(locked_coin == swap_value, "Bad locked value") # Check script A, B = extractScriptLockScriptValues(script_out) - ensure(A == Kal, 'Bad script pubkey') - ensure(B == Kaf, 'Bad script pubkey') + ensure(A == Kal, "Bad script pubkey") + ensure(B == Kaf, "Bad script pubkey") if check_lock_tx_inputs: # TODO: Check that inputs are unspent # Verify fee rate inputs_value = 0 - add_bytes = 0 + # add_bytes = 0 add_witness_bytes = 0 for pi in tx.vin: - ptx = self.rpc('getrawtransaction', [i2h(pi.prevout.hash), True]) - prevout = ptx['vout'][pi.prevout.n] - inputs_value += self.make_int(prevout['value']) - self._log.info('prevout: {}.'.format(prevout)) - prevout_type = prevout['scriptPubKey']['type'] + ptx = self.rpc("getrawtransaction", [i2h(pi.prevout.hash), True]) + prevout = ptx["vout"][pi.prevout.n] + inputs_value += self.make_int(prevout["value"]) + self._log.info("prevout: {}.".format(prevout)) - ''' + """ + prevout_type = prevout['scriptPubKey']['type'] if prevout_type == 'witness_v0_keyhash': #add_witness_bytes += 107 # sig 72, pk 33 and 2 size bytes #add_witness_bytes += getCompactSizeLen(107) else: # Assume P2PKH, TODO more types add_bytes += 107 # OP_PUSH72 OP_PUSH33 - ''' + """ outputs_value = 0 for txo in tx.vout: outputs_value += txo.nValue fee_paid = inputs_value - outputs_value - assert (fee_paid > 0) + assert fee_paid > 0 size = len(tx.serialize()) + add_witness_bytes fee_rate_paid = fee_paid * 1000 // size - self._log.info('tx amount, size, feerate: %ld, %ld, %ld', locked_coin, size, fee_rate_paid) + self._log.info( + "tx amount, size, feerate: %ld, %ld, %ld", + locked_coin, + size, + fee_rate_paid, + ) if not self.compareFeeRates(fee_rate_paid, feerate): - self._log.warning('feerate paid doesn\'t match expected: %ld, %ld', fee_rate_paid, feerate) + self._log.warning( + "feerate paid doesn't match expected: %ld, %ld", + fee_rate_paid, + feerate, + ) # TODO: Display warning to user return txid, locked_n - def verifySCLockSpendTx(self, tx_bytes, - lock_tx_bytes, lock_tx_script, - a_pkhash_f, feerate, vkbv=None): + def verifySCLockSpendTx( + self, tx_bytes, lock_tx_bytes, lock_tx_script, a_pkhash_f, feerate, vkbv=None + ): # Verify: # Must have only one input with correct prevout (n is always 0) and sequence # Must have only one output with destination and amount tx = self.loadTx(tx_bytes) txid = self.getTxid(tx) - self._log.info('Verifying lock spend tx: {}.'.format(b2h(txid))) + self._log.info("Verifying lock spend tx: {}.".format(b2h(txid))) - ensure(tx.version == self.txVersion(), 'Bad version') - ensure(tx.locktime == 0, 'Bad locktime') - ensure(tx.expiry == 0, 'Bad expiry') - ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') + ensure(tx.version == self.txVersion(), "Bad version") + ensure(tx.locktime == 0, "Bad locktime") + ensure(tx.expiry == 0, "Bad expiry") + ensure(len(tx.vin) == 1, "tx doesn't have one input") lock_tx = self.loadTx(lock_tx_bytes) lock_tx_id = self.getTxid(lock_tx) output_script = self.getScriptDest(lock_tx_script) locked_n = findOutput(lock_tx, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = lock_tx.vout[locked_n].value - ensure(tx.vin[0].sequence == 0, 'Bad input nSequence') - ensure(len(tx.vin[0].signature_script) == 0, 'Input sig not empty') - ensure(i2b(tx.vin[0].prevout.hash) == lock_tx_id and tx.vin[0].prevout.n == locked_n, 'Input prevout mismatch') + ensure(tx.vin[0].sequence == 0, "Bad input nSequence") + ensure(len(tx.vin[0].signature_script) == 0, "Input sig not empty") + ensure( + i2b(tx.vin[0].prevout.hash) == lock_tx_id + and tx.vin[0].prevout.n == locked_n, + "Input prevout mismatch", + ) - ensure(len(tx.vout) == 1, 'tx doesn\'t have one output') + ensure(len(tx.vout) == 1, "tx doesn't have one output") p2wpkh = self.getPubkeyHashDest(a_pkhash_f) - ensure(tx.vout[0].script_pubkey == p2wpkh, 'Bad output destination') + ensure(tx.vout[0].script_pubkey == p2wpkh, "Bad output destination") # The value of the lock tx output should already be verified, if the fee is as expected the difference will be the correct amount fee_paid = locked_coin - tx.vout[0].value - assert (fee_paid > 0) + assert fee_paid > 0 dummy_witness_stack = self.getScriptLockTxDummyWitness(lock_tx_script) size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack)) fee_rate_paid = fee_paid * 1000 // size - self._log.info('tx amount, size, feerate: %ld, %ld, %ld', tx.vout[0].value, size, fee_rate_paid) + self._log.info( + "tx amount, size, feerate: %ld, %ld, %ld", + tx.vout[0].value, + size, + fee_rate_paid, + ) if not self.compareFeeRates(fee_rate_paid, feerate): - raise ValueError('Bad fee rate, expected: {}'.format(feerate)) + raise ValueError("Bad fee rate, expected: {}".format(feerate)) return True - def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out, - prevout_id, prevout_n, prevout_seq, prevout_script, - Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv=None): + def verifySCLockRefundTx( + self, + tx_bytes, + lock_tx_bytes, + script_out, + prevout_id, + prevout_n, + prevout_seq, + prevout_script, + Kal, + Kaf, + csv_val_expect, + swap_value, + feerate, + vkbv=None, + ): # Verify: # Must have only one input with correct prevout and sequence # Must have only one output to the p2wsh of the lock refund script @@ -1150,90 +1390,122 @@ class DCRInterface(Secp256k1Interface): tx = self.loadTx(tx_bytes) txid = self.getTxid(tx) - self._log.info('Verifying lock refund tx: {}.'.format(b2h(txid))) + self._log.info("Verifying lock refund tx: {}.".format(b2h(txid))) - ensure(tx.version == self.txVersion(), 'Bad version') - ensure(tx.locktime == 0, 'locktime not 0') - ensure(tx.expiry == 0, 'Bad expiry') - ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') + ensure(tx.version == self.txVersion(), "Bad version") + ensure(tx.locktime == 0, "locktime not 0") + ensure(tx.expiry == 0, "Bad expiry") + ensure(len(tx.vin) == 1, "tx doesn't have one input") - ensure(tx.vin[0].sequence == prevout_seq, 'Bad input sequence') - ensure(i2b(tx.vin[0].prevout.hash) == prevout_id and tx.vin[0].prevout.n == prevout_n and tx.vin[0].prevout.tree == 0, 'Input prevout mismatch') - ensure(len(tx.vin[0].signature_script) == 0, 'Input sig not empty') + ensure(tx.vin[0].sequence == prevout_seq, "Bad input sequence") + ensure( + i2b(tx.vin[0].prevout.hash) == prevout_id + and tx.vin[0].prevout.n == prevout_n + and tx.vin[0].prevout.tree == 0, + "Input prevout mismatch", + ) + ensure(len(tx.vin[0].signature_script) == 0, "Input sig not empty") - ensure(len(tx.vout) == 1, 'tx doesn\'t have one output') + ensure(len(tx.vout) == 1, "tx doesn't have one output") script_pk = self.getScriptDest(script_out) locked_n = findOutput(tx, script_pk) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx.vout[locked_n].value # Check script and values A, B, csv_val, C = extractScriptLockRefundScriptValues(script_out) - ensure(A == Kal, 'Bad script pubkey') - ensure(B == Kaf, 'Bad script pubkey') - ensure(csv_val == csv_val_expect, 'Bad script csv value') - ensure(C == Kaf, 'Bad script pubkey') + ensure(A == Kal, "Bad script pubkey") + ensure(B == Kaf, "Bad script pubkey") + ensure(csv_val == csv_val_expect, "Bad script csv value") + ensure(C == Kaf, "Bad script pubkey") fee_paid = swap_value - locked_coin - assert (fee_paid > 0) + assert fee_paid > 0 dummy_witness_stack = self.getScriptLockTxDummyWitness(prevout_script) size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack)) fee_rate_paid = fee_paid * 1000 // size - self._log.info('tx amount, size, feerate: %ld, %ld, %ld', locked_coin, size, fee_rate_paid) + self._log.info( + "tx amount, size, feerate: %ld, %ld, %ld", locked_coin, size, fee_rate_paid + ) if not self.compareFeeRates(fee_rate_paid, feerate): - raise ValueError('Bad fee rate, expected: {}'.format(feerate)) + raise ValueError("Bad fee rate, expected: {}".format(feerate)) return txid, locked_coin, locked_n - def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes, - lock_refund_tx_id, prevout_script, - Kal, - prevout_n, prevout_value, feerate, vkbv=None): + def verifySCLockRefundSpendTx( + self, + tx_bytes, + lock_refund_tx_bytes, + lock_refund_tx_id, + prevout_script, + Kal, + prevout_n, + prevout_value, + feerate, + vkbv=None, + ): # Verify: # Must have only one input with correct prevout (n is always 0) and sequence # Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr tx = self.loadTx(tx_bytes) txid = self.getTxid(tx) - self._log.info('Verifying lock refund spend tx: {}.'.format(b2h(txid))) + self._log.info("Verifying lock refund spend tx: {}.".format(b2h(txid))) - ensure(tx.version == self.txVersion(), 'Bad version') - ensure(tx.locktime == 0, 'locktime not 0') - ensure(tx.expiry == 0, 'Bad expiry') - ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') + ensure(tx.version == self.txVersion(), "Bad version") + ensure(tx.locktime == 0, "locktime not 0") + ensure(tx.expiry == 0, "Bad expiry") + ensure(len(tx.vin) == 1, "tx doesn't have one input") - ensure(tx.vin[0].sequence == 0, 'Bad input sequence') - ensure(len(tx.vin[0].signature_script) == 0, 'Input sig not empty') - ensure(i2b(tx.vin[0].prevout.hash) == lock_refund_tx_id and tx.vin[0].prevout.n == 0 and tx.vin[0].prevout.tree == 0, 'Input prevout mismatch') + ensure(tx.vin[0].sequence == 0, "Bad input sequence") + ensure(len(tx.vin[0].signature_script) == 0, "Input sig not empty") + ensure( + i2b(tx.vin[0].prevout.hash) == lock_refund_tx_id + and tx.vin[0].prevout.n == 0 + and tx.vin[0].prevout.tree == 0, + "Input prevout mismatch", + ) - ensure(len(tx.vout) == 1, 'tx doesn\'t have one output') + ensure(len(tx.vout) == 1, "tx doesn't have one output") # Destination doesn't matter to the follower - ''' + """ p2wpkh = CScript([OP_0, hash160(Kal)]) locked_n = findOutput(tx, p2wpkh) ensure(locked_n is not None, 'Output not found in lock refund spend tx') - ''' + """ tx_value = tx.vout[0].value fee_paid = prevout_value - tx_value - assert (fee_paid > 0) + assert fee_paid > 0 - dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(prevout_script) + dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness( + prevout_script + ) size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack)) fee_rate_paid = fee_paid * 1000 // size - self._log.info('tx amount, size, feerate: %ld, %ld, %ld', tx_value, size, fee_rate_paid) + self._log.info( + "tx amount, size, feerate: %ld, %ld, %ld", tx_value, size, fee_rate_paid + ) if not self.compareFeeRates(fee_rate_paid, feerate): - raise ValueError('Bad fee rate, expected: {}'.format(feerate)) + raise ValueError("Bad fee rate, expected: {}".format(feerate)) return True - def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None, kbsf=None): + def createSCLockRefundSpendToFTx( + self, + tx_lock_refund_bytes, + script_lock_refund, + pkh_dest, + tx_fee_rate, + vkbv=None, + kbsf=None, + ): # lock refund swipe tx # Sends the coinA locked coin to the follower @@ -1241,7 +1513,7 @@ class DCRInterface(Secp256k1Interface): output_script = self.getScriptDest(script_lock_refund) locked_n = findOutput(tx_lock_refund, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_amount = tx_lock_refund.vout[locked_n].value A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund) @@ -1250,28 +1522,56 @@ class DCRInterface(Secp256k1Interface): tx = CTransaction() tx.version = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n, 0), - sequence=lock2_value,)) + tx.vin.append( + CTxIn( + COutPoint(tx_lock_refund_hash_int, locked_n, 0), + sequence=lock2_value, + ) + ) tx.vout.append(self.txoType()(locked_amount, self.getPubkeyHashDest(pkh_dest))) - dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness(script_lock_refund) + dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness( + script_lock_refund + ) size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack)) pay_fee = round(tx_fee_rate * size / 1000) tx.vout[0].value = locked_amount - pay_fee - self._log.info('createSCLockRefundSpendToFTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.', - tx.TxHash().hex(), tx_fee_rate, size, pay_fee) + self._log.info( + "createSCLockRefundSpendToFTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.", + tx.TxHash().hex(), + tx_fee_rate, + size, + pay_fee, + ) return tx.serialize(TxSerializeType.NoWitness) - def signTxOtVES(self, key_sign: bytes, pubkey_encrypt: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes: + def signTxOtVES( + self, + key_sign: bytes, + pubkey_encrypt: bytes, + tx_bytes: bytes, + input_n: int, + prevout_script: bytes, + prevout_value: int, + ) -> bytes: tx = self.loadTx(tx_bytes) sig_hash = DCRSignatureHash(prevout_script, SigHashType.SigHashAll, tx, input_n) return ecdsaotves_enc_sign(key_sign, pubkey_encrypt, sig_hash) - def verifyTxOtVES(self, tx_bytes: bytes, ct: bytes, Ks: bytes, Ke: bytes, input_n: int, prevout_script: bytes, prevout_value): + def verifyTxOtVES( + self, + tx_bytes: bytes, + ct: bytes, + Ks: bytes, + Ke: bytes, + input_n: int, + prevout_script: bytes, + prevout_value, + ): tx = self.loadTx(tx_bytes) sig_hash = DCRSignatureHash(prevout_script, SigHashType.SigHashAll, tx, input_n) return ecdsaotves_enc_verify(Ks, Ke, sig_hash, ct) @@ -1289,25 +1589,16 @@ class DCRInterface(Secp256k1Interface): return findOutput(tx, script_pk) def getScriptLockTxDummyWitness(self, script: bytes): - return [ - bytes(72), - bytes(72), - bytes(len(script)) - ] + return [bytes(72), bytes(72), bytes(len(script))] def getScriptLockRefundSpendTxDummyWitness(self, script: bytes): - return [ - bytes(72), - bytes(72), - bytes((1,)), - bytes(len(script)) - ] + return [bytes(72), bytes(72), bytes((1,)), bytes(len(script))] def extractLeaderSig(self, tx_bytes: bytes) -> bytes: tx = self.loadTx(tx_bytes) sig_len = tx.vin[0].signature_script[0] - return tx.vin[0].signature_script[1: 1 + sig_len] + return tx.vin[0].signature_script[1 : 1 + sig_len] def extractFollowerSig(self, tx_bytes: bytes) -> bytes: tx = self.loadTx(tx_bytes) @@ -1316,17 +1607,24 @@ class DCRInterface(Secp256k1Interface): ofs = 1 + sig_len sig_len = tx.vin[0].signature_script[ofs] ofs += 1 - return tx.vin[0].signature_script[ofs: ofs + sig_len] + return tx.vin[0].signature_script[ofs : ofs + sig_len] def listInputs(self, tx_bytes: bytes): tx = self.loadTx(tx_bytes) - all_locked = self.rpc_wallet('listlockunspent') + all_locked = self.rpc_wallet("listlockunspent") inputs = [] for txi in tx.vin: txid_hex = i2h(txi.prevout.hash) - islocked = any([txid_hex == a['txid'] and txi.prevout.n == a['vout'] for a in all_locked]) - inputs.append({'txid': txid_hex, 'vout': txi.prevout.n, 'islocked': islocked}) + islocked = any( + [ + txid_hex == a["txid"] and txi.prevout.n == a["vout"] + for a in all_locked + ] + ) + inputs.append( + {"txid": txid_hex, "vout": txi.prevout.n, "islocked": islocked} + ) return inputs def unlockInputs(self, tx_bytes): @@ -1334,44 +1632,64 @@ class DCRInterface(Secp256k1Interface): inputs = [] for txi in tx.vin: - inputs.append({'amount': float(self.format_amount(txi.value_in)), 'txid': i2h(txi.prevout.hash), 'vout': txi.prevout.n, 'tree': txi.prevout.tree}) - self.rpc_wallet('lockunspent', [True, inputs]) + inputs.append( + { + "amount": float(self.format_amount(txi.value_in)), + "txid": i2h(txi.prevout.hash), + "vout": txi.prevout.n, + "tree": txi.prevout.tree, + } + ) + self.rpc_wallet("lockunspent", [True, inputs]) def getWalletRestoreHeight(self) -> int: - start_time = self.rpc_wallet('getinfo')['keypoololdest'] + start_time = self.rpc_wallet("getinfo")["keypoololdest"] blockchaininfo = self.getBlockchainInfo() - best_block = blockchaininfo['bestblockhash'] + best_block = blockchaininfo["bestblockhash"] - chain_synced = round(blockchaininfo['verificationprogress'], 3) + chain_synced = round(blockchaininfo["verificationprogress"], 3) if chain_synced < 1.0: - raise ValueError('{} chain isn\'t synced.'.format(self.coin_name())) + raise ValueError("{} chain isn't synced.".format(self.coin_name())) if start_time == 0: - self._log.debug('Using genesis block for restore height as keypoololdest is 0.') + self._log.debug( + "Using genesis block for restore height as keypoololdest is 0." + ) return 0 - self._log.info('Finding block at time: {} for restore height.'.format(start_time)) + self._log.info( + "Finding block at time: {} for restore height.".format(start_time) + ) blocks_searched: int = 0 rpc_conn = self.open_rpc() try: block_hash = best_block while True: - block_header = self.json_request(rpc_conn, 'getblockheader', [block_hash]) - if block_header['time'] < start_time: - return block_header['height'] + block_header = self.json_request( + rpc_conn, "getblockheader", [block_hash] + ) + if block_header["time"] < start_time: + return block_header["height"] # genesis block - if block_header['previousblockhash'] == '0000000000000000000000000000000000000000000000000000000000000000': - return block_header['height'] + if ( + block_header["previousblockhash"] + == "0000000000000000000000000000000000000000000000000000000000000000" + ): + return block_header["height"] - block_hash = block_header['previousblockhash'] + block_hash = block_header["previousblockhash"] blocks_searched += 1 if blocks_searched % 10000 == 0: - self._log.debug('Still finding restore height, block at height {} has time {}.'.format(block_header['height'], block_header['time'])) + self._log.debug( + "Still finding restore height, block at height {} has time {}.".format( + block_header["height"], block_header["time"] + ) + ) finally: self.close_rpc(rpc_conn) - raise ValueError('{} wallet restore height not found.'.format(self.coin_name())) + raise ValueError("{} wallet restore height not found.".format(self.coin_name())) def createBLockTx(self, Kbs, output_amount, vkbv=None) -> bytes: tx = CTransaction() @@ -1380,11 +1698,12 @@ class DCRInterface(Secp256k1Interface): tx.vout.append(self.txoType()(output_amount, script_pk)) return tx.serialize() - def publishBLockTx(self, kbv, Kbs, output_amount, feerate, unlock_time: int = 0) -> bytes: + def publishBLockTx( + self, kbv, Kbs, output_amount, feerate, unlock_time: int = 0 + ) -> bytes: b_lock_tx = self.createBLockTx(Kbs, output_amount) b_lock_tx = self.fundTx(b_lock_tx, feerate) - b_lock_tx_id = self.getTxid(b_lock_tx) b_lock_tx = self.signTxWithWallet(b_lock_tx) return bytes.fromhex(self.publishTx(b_lock_tx)) @@ -1393,24 +1712,43 @@ class DCRInterface(Secp256k1Interface): witness_bytes = 115 size = len(tx.serialize()) + witness_bytes pay_fee = round(fee_rate * size / 1000) - self._log.info(f'BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {size}, {pay_fee}.') + self._log.info( + f"BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {size}, {pay_fee}." + ) return pay_fee - def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, lock_tx_vout=None) -> bytes: - self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex()) + def spendBLockTx( + self, + chain_b_lock_txid: bytes, + address_to: str, + kbv: bytes, + kbs: bytes, + cb_swap_value: int, + b_fee: int, + restore_height: int, + lock_tx_vout=None, + ) -> bytes: + self._log.info("spendBLockTx %s:\n", chain_b_lock_txid.hex()) locked_n = lock_tx_vout Kbs = self.getPubkey(kbs) script_pk = self.getPkDest(Kbs) if locked_n is None: - self._log.debug(f'Unknown lock vout, searching tx: {chain_b_lock_txid.hex()}') + self._log.debug( + f"Unknown lock vout, searching tx: {chain_b_lock_txid.hex()}" + ) # When refunding a lock tx, it should be in the wallet as a sent tx - wtx = self.rpc_wallet('gettransaction', [chain_b_lock_txid.hex(), ]) - lock_tx = self.loadTx(bytes.fromhex(wtx['hex'])) + wtx = self.rpc_wallet( + "gettransaction", + [ + chain_b_lock_txid.hex(), + ], + ) + lock_tx = self.loadTx(bytes.fromhex(wtx["hex"])) locked_n = findOutput(lock_tx, script_pk) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") pkh_to = self.decodeAddress(address_to) tx = CTransaction() @@ -1418,8 +1756,7 @@ class DCRInterface(Secp256k1Interface): chain_b_lock_txid_int = b2i(chain_b_lock_txid) - tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n, 0), - sequence=0)) + tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n, 0), sequence=0)) tx.vout.append(self.txoType()(cb_swap_value, self.getPubkeyHashDest(pkh_to))) pay_fee = self.getBLockSpendTxFee(tx, b_fee) @@ -1432,22 +1769,27 @@ class DCRInterface(Secp256k1Interface): def findTxnByHash(self, txid_hex: str): try: - txout = self.rpc('gettxout', [txid_hex, 0, 0, True]) - except Exception as e: + txout = self.rpc("gettxout", [txid_hex, 0, 0, True]) + except Exception as e: # noqa: F841 # self._log.warning('gettxout {}'.format(e)) return None - confirmations: int = 0 if 'confirmations' not in txout else txout['confirmations'] + confirmations: int = ( + 0 if "confirmations" not in txout else txout["confirmations"] + ) if confirmations >= self.blocks_confirmed: block_height = self.getChainHeight() - confirmations # TODO: Better way? - return {'txid': txid_hex, 'amount': 0, 'height': block_height} + return {"txid": txid_hex, "amount": 0, "height": block_height} return None def encodeSharedAddress(self, Kbv, Kbs): return self.pkh_to_address(self.pkh(Kbs)) def isTxExistsError(self, err_str: str) -> bool: - return 'transaction already exists' in err_str or 'already have transaction' in err_str + return ( + "transaction already exists" in err_str + or "already have transaction" in err_str + ) def isTxNonFinalError(self, err_str: str) -> bool: - return 'locks on inputs not met' in err_str + return "locks on inputs not met" in err_str diff --git a/basicswap/interface/dcr/messages.py b/basicswap/interface/dcr/messages.py index 00e7f67..c0a2ff6 100644 --- a/basicswap/interface/dcr/messages.py +++ b/basicswap/interface/dcr/messages.py @@ -23,7 +23,7 @@ class SigHashType(IntEnum): SigHashSingle = 0x3 SigHashAnyOneCanPay = 0x80 - SigHashMask = 0x1f + SigHashMask = 0x1F class SignatureType(IntEnum): @@ -33,7 +33,7 @@ class SignatureType(IntEnum): class COutPoint: - __slots__ = ('hash', 'n', 'tree') + __slots__ = ("hash", "n", "tree") def __init__(self, hash=0, n=0, tree=0): self.hash = hash @@ -41,24 +41,30 @@ class COutPoint: self.tree = tree def get_hash(self) -> bytes: - return self.hash.to_bytes(32, 'big') + return self.hash.to_bytes(32, "big") class CTxIn: - __slots__ = ('prevout', 'sequence', - 'value_in', 'block_height', 'block_index', 'signature_script') # Witness + __slots__ = ( + "prevout", + "sequence", + "value_in", + "block_height", + "block_index", + "signature_script", + ) # Witness def __init__(self, prevout=COutPoint(), sequence=0): self.prevout = prevout self.sequence = sequence self.value_in = -1 self.block_height = 0 - self.block_index = 0xffffffff + self.block_index = 0xFFFFFFFF self.signature_script = bytes() class CTxOut: - __slots__ = ('value', 'version', 'script_pubkey') + __slots__ = ("value", "version", "script_pubkey") def __init__(self, value=0, script_pubkey=bytes()): self.value = value @@ -67,7 +73,7 @@ class CTxOut: class CTransaction: - __slots__ = ('hash', 'version', 'vin', 'vout', 'locktime', 'expiry') + __slots__ = ("hash", "version", "vin", "vout", "locktime", "expiry") def __init__(self, tx=None): if tx is None: @@ -85,8 +91,8 @@ class CTransaction: def deserialize(self, data: bytes) -> None: - version = int.from_bytes(data[:4], 'little') - self.version = version & 0xffff + version = int.from_bytes(data[:4], "little") + self.version = version & 0xFFFF ser_type: int = version >> 16 o = 4 @@ -97,13 +103,13 @@ class CTransaction: for i in range(num_txin): txi = CTxIn() txi.prevout = COutPoint() - txi.prevout.hash = int.from_bytes(data[o:o + 32], 'little') + txi.prevout.hash = int.from_bytes(data[o : o + 32], "little") o += 32 - txi.prevout.n = int.from_bytes(data[o:o + 4], 'little') + txi.prevout.n = int.from_bytes(data[o : o + 4], "little") o += 4 txi.prevout.tree = data[o] o += 1 - txi.sequence = int.from_bytes(data[o:o + 4], 'little') + txi.sequence = int.from_bytes(data[o : o + 4], "little") o += 4 self.vin.append(txi) @@ -112,19 +118,19 @@ class CTransaction: for i in range(num_txout): txo = CTxOut() - txo.value = int.from_bytes(data[o:o + 8], 'little') + txo.value = int.from_bytes(data[o : o + 8], "little") o += 8 - txo.version = int.from_bytes(data[o:o + 2], 'little') + txo.version = int.from_bytes(data[o : o + 2], "little") o += 2 script_bytes, nb = decode_compactsize(data, o) o += nb - txo.script_pubkey = data[o:o + script_bytes] + txo.script_pubkey = data[o : o + script_bytes] o += script_bytes self.vout.append(txo) - self.locktime = int.from_bytes(data[o:o + 4], 'little') + self.locktime = int.from_bytes(data[o : o + 4], "little") o += 4 - self.expiry = int.from_bytes(data[o:o + 4], 'little') + self.expiry = int.from_bytes(data[o : o + 4], "little") o += 4 if ser_type == TxSerializeType.NoWitness: @@ -137,51 +143,53 @@ class CTransaction: self.vin = [CTxIn() for _ in range(num_wit_scripts)] else: if num_wit_scripts != len(self.vin): - raise ValueError('non equal witness and prefix txin quantities') + raise ValueError("non equal witness and prefix txin quantities") for i in range(num_wit_scripts): txi = self.vin[i] - txi.value_in = int.from_bytes(data[o:o + 8], 'little') + txi.value_in = int.from_bytes(data[o : o + 8], "little") o += 8 - txi.block_height = int.from_bytes(data[o:o + 4], 'little') + txi.block_height = int.from_bytes(data[o : o + 4], "little") o += 4 - txi.block_index = int.from_bytes(data[o:o + 4], 'little') + txi.block_index = int.from_bytes(data[o : o + 4], "little") o += 4 script_bytes, nb = decode_compactsize(data, o) o += nb - txi.signature_script = data[o:o + script_bytes] + txi.signature_script = data[o : o + script_bytes] o += script_bytes def serialize(self, ser_type=TxSerializeType.Full) -> bytes: data = bytes() - version = (self.version & 0xffff) | (ser_type << 16) - data += version.to_bytes(4, 'little') + version = (self.version & 0xFFFF) | (ser_type << 16) + data += version.to_bytes(4, "little") if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.NoWitness: data += encode_compactsize(len(self.vin)) for txi in self.vin: - data += txi.prevout.hash.to_bytes(32, 'little') - data += txi.prevout.n.to_bytes(4, 'little') - data += txi.prevout.tree.to_bytes(1, 'little') - data += txi.sequence.to_bytes(4, 'little') + data += txi.prevout.hash.to_bytes(32, "little") + data += txi.prevout.n.to_bytes(4, "little") + data += txi.prevout.tree.to_bytes(1, "little") + data += txi.sequence.to_bytes(4, "little") data += encode_compactsize(len(self.vout)) for txo in self.vout: - data += txo.value.to_bytes(8, 'little') - data += txo.version.to_bytes(2, 'little') + data += txo.value.to_bytes(8, "little") + data += txo.version.to_bytes(2, "little") data += encode_compactsize(len(txo.script_pubkey)) data += txo.script_pubkey - data += self.locktime.to_bytes(4, 'little') - data += self.expiry.to_bytes(4, 'little') + data += self.locktime.to_bytes(4, "little") + data += self.expiry.to_bytes(4, "little") if ser_type == TxSerializeType.Full or ser_type == TxSerializeType.OnlyWitness: data += encode_compactsize(len(self.vin)) for txi in self.vin: - tc_value_in = txi.value_in & 0xffffffffffffffff # Convert negative values - data += tc_value_in.to_bytes(8, 'little') - data += txi.block_height.to_bytes(4, 'little') - data += txi.block_index.to_bytes(4, 'little') + tc_value_in = ( + txi.value_in & 0xFFFFFFFFFFFFFFFF + ) # Convert negative values + data += tc_value_in.to_bytes(8, "little") + data += txi.block_height.to_bytes(4, "little") + data += txi.block_index.to_bytes(4, "little") data += encode_compactsize(len(txi.signature_script)) data += txi.signature_script @@ -191,10 +199,10 @@ class CTransaction: return blake256(self.serialize(TxSerializeType.NoWitness))[::-1] def TxHashWitness(self) -> bytes: - raise ValueError('todo') + raise ValueError("todo") def TxHashFull(self) -> bytes: - raise ValueError('todo') + raise ValueError("todo") def findOutput(tx, script_pk: bytes): diff --git a/basicswap/interface/dcr/rpc.py b/basicswap/interface/dcr/rpc.py index b536e11..ac357e4 100644 --- a/basicswap/interface/dcr/rpc.py +++ b/basicswap/interface/dcr/rpc.py @@ -9,34 +9,34 @@ import traceback from basicswap.rpc import Jsonrpc -def callrpc(rpc_port, auth, method, params=[], host='127.0.0.1'): +def callrpc(rpc_port, auth, method, params=[], host="127.0.0.1"): try: - url = 'http://{}@{}:{}/'.format(auth, host, rpc_port) + url = "http://{}@{}:{}/".format(auth, host, rpc_port) x = Jsonrpc(url) x.__handler = None v = x.json_request(method, params) x.close() - r = json.loads(v.decode('utf-8')) + r = json.loads(v.decode("utf-8")) except Exception as ex: traceback.print_exc() - raise ValueError('RPC server error ' + str(ex) + ', method: ' + method) + raise ValueError("RPC server error " + str(ex) + ", method: " + method) - if 'error' in r and r['error'] is not None: - raise ValueError('RPC error ' + str(r['error'])) + if "error" in r and r["error"] is not None: + raise ValueError("RPC error " + str(r["error"])) - return r['result'] + return r["result"] -def openrpc(rpc_port, auth, host='127.0.0.1'): +def openrpc(rpc_port, auth, host="127.0.0.1"): try: - url = 'http://{}@{}:{}/'.format(auth, host, rpc_port) + url = "http://{}@{}:{}/".format(auth, host, rpc_port) return Jsonrpc(url) except Exception as ex: traceback.print_exc() - raise ValueError('RPC error ' + str(ex)) + raise ValueError("RPC error " + str(ex)) -def make_rpc_func(port, auth, host='127.0.0.1'): +def make_rpc_func(port, auth, host="127.0.0.1"): port = port auth = auth host = host @@ -44,4 +44,5 @@ def make_rpc_func(port, auth, host='127.0.0.1'): def rpc_func(method, params=None): nonlocal port, auth, host return callrpc(port, auth, method, params, host) + return rpc_func diff --git a/basicswap/interface/dcr/script.py b/basicswap/interface/dcr/script.py index 3c0762f..310e03f 100644 --- a/basicswap/interface/dcr/script.py +++ b/basicswap/interface/dcr/script.py @@ -7,7 +7,7 @@ OP_0 = 0x00 OP_DATA_1 = 0x01 -OP_1NEGATE = 0x4f +OP_1NEGATE = 0x4F OP_1 = 0x51 OP_IF = 0x63 OP_ELSE = 0x67 @@ -16,13 +16,13 @@ OP_DROP = 0x75 OP_DUP = 0x76 OP_EQUAL = 0x87 OP_EQUALVERIFY = 0x88 -OP_PUSHDATA1 = 0x4c -OP_PUSHDATA2 = 0x4d -OP_PUSHDATA4 = 0x4e -OP_HASH160 = 0xa9 -OP_CHECKSIG = 0xac -OP_CHECKMULTISIG = 0xae -OP_CHECKSEQUENCEVERIFY = 0xb2 +OP_PUSHDATA1 = 0x4C +OP_PUSHDATA2 = 0x4D +OP_PUSHDATA4 = 0x4E +OP_HASH160 = 0xA9 +OP_CHECKSIG = 0xAC +OP_CHECKMULTISIG = 0xAE +OP_CHECKSEQUENCEVERIFY = 0xB2 def push_script_data(data_array: bytearray, data: bytes) -> None: @@ -39,12 +39,12 @@ def push_script_data(data_array: bytearray, data: bytes) -> None: return if len_data < OP_PUSHDATA1: - data_array += len_data.to_bytes(1, 'little') - elif len_data <= 0xff: + data_array += len_data.to_bytes(1, "little") + elif len_data <= 0xFF: data_array += bytes((OP_PUSHDATA1, len_data)) - elif len_data <= 0xffff: - data_array += bytes((OP_PUSHDATA2,)) + len_data.to_bytes(2, 'little') + elif len_data <= 0xFFFF: + data_array += bytes((OP_PUSHDATA2,)) + len_data.to_bytes(2, "little") else: - data_array += bytes((OP_PUSHDATA4,)) + len_data.to_bytes(4, 'little') + data_array += bytes((OP_PUSHDATA4,)) + len_data.to_bytes(4, "little") data_array += data diff --git a/basicswap/interface/dcr/util.py b/basicswap/interface/dcr/util.py index b32743a..cb8bbb8 100644 --- a/basicswap/interface/dcr/util.py +++ b/basicswap/interface/dcr/util.py @@ -10,46 +10,48 @@ import subprocess def createDCRWallet(args, hex_seed, logging, delay_event): - logging.info('Creating DCR wallet') + logging.info("Creating DCR wallet") (pipe_r, pipe_w) = os.pipe() # subprocess.PIPE is buffered, blocks when read - if os.name == 'nt': - str_args = ' '.join(args) - p = subprocess.Popen(str_args, shell=True, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w) + if os.name == "nt": + str_args = " ".join(args) + p = subprocess.Popen( + str_args, shell=True, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w + ) else: p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=pipe_w, stderr=pipe_w) def readOutput(): - buf = os.read(pipe_r, 1024).decode('utf-8') + buf = os.read(pipe_r, 1024).decode("utf-8") response = None - if 'Opened wallet' in buf: + if "Opened wallet" in buf: pass - elif 'Use the existing configured private passphrase' in buf: - response = b'y\n' - elif 'Do you want to add an additional layer of encryption' in buf: - response = b'n\n' - elif 'Do you have an existing wallet seed' in buf: - response = b'y\n' - elif 'Enter existing wallet seed' in buf: - response = (hex_seed + '\n').encode('utf-8') - elif 'Seed input successful' in buf: + elif "Use the existing configured private passphrase" in buf: + response = b"y\n" + elif "Do you want to add an additional layer of encryption" in buf: + response = b"n\n" + elif "Do you have an existing wallet seed" in buf: + response = b"y\n" + elif "Enter existing wallet seed" in buf: + response = (hex_seed + "\n").encode("utf-8") + elif "Seed input successful" in buf: pass - elif 'Upgrading database from version' in buf: + elif "Upgrading database from version" in buf: pass - elif 'Ticket commitments db upgrade done' in buf: + elif "Ticket commitments db upgrade done" in buf: pass - elif 'The wallet has been created successfully' in buf: + elif "The wallet has been created successfully" in buf: pass else: - raise ValueError(f'Unexpected output: {buf}') + raise ValueError(f"Unexpected output: {buf}") if response is not None: p.stdin.write(response) p.stdin.flush() try: while p.poll() is None: - if os.name == 'nt': + if os.name == "nt": readOutput() delay_event.wait(0.1) continue @@ -57,7 +59,7 @@ def createDCRWallet(args, hex_seed, logging, delay_event): readOutput() delay_event.wait(0.1) except Exception as e: - logging.error(f'dcrwallet --create failed: {e}') + logging.error(f"dcrwallet --create failed: {e}") finally: if p.poll() is None: p.terminate() diff --git a/basicswap/interface/firo.py b/basicswap/interface/firo.py index 6dad465..033592c 100644 --- a/basicswap/interface/firo.py +++ b/basicswap/interface/firo.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2022-2023 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -40,10 +41,12 @@ class FIROInterface(BTCInterface): def __init__(self, coin_settings, network, swap_client=None): super(FIROInterface, self).__init__(coin_settings, network, swap_client) # No multiwallet support - self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) + self.rpc_wallet = make_rpc_func( + self._rpcport, self._rpcauth, host=self._rpc_host + ) def getExchangeName(self, exchange_name: str) -> str: - return 'zcoin' + return "zcoin" def initialiseWallet(self, key): # load with -hdseed= parameter @@ -52,8 +55,8 @@ class FIROInterface(BTCInterface): def checkWallets(self) -> int: return 1 - def getNewAddress(self, use_segwit, label='swap_receive'): - return self.rpc('getnewaddress', [label]) + def getNewAddress(self, use_segwit, label="swap_receive"): + return self.rpc("getnewaddress", [label]) # addr_plain = self.rpc('getnewaddress', [label]) # return self.rpc('addwitnessaddress', [addr_plain]) @@ -61,20 +64,20 @@ class FIROInterface(BTCInterface): return decodeAddress(address)[1:] def encodeSegwitAddress(self, script): - raise ValueError('TODO') + raise ValueError("TODO") def decodeSegwitAddress(self, addr): - raise ValueError('TODO') + raise ValueError("TODO") def isWatchOnlyAddress(self, address): - addr_info = self.rpc('validateaddress', [address]) - return addr_info['iswatchonly'] + addr_info = self.rpc("validateaddress", [address]) + return addr_info["iswatchonly"] def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool: - addr_info = self.rpc('validateaddress', [address]) + addr_info = self.rpc("validateaddress", [address]) if not or_watch_only: - return addr_info['ismine'] - return addr_info['ismine'] or addr_info['iswatchonly'] + return addr_info["ismine"] + return addr_info["ismine"] or addr_info["iswatchonly"] def getSCLockScriptAddress(self, lock_script: bytes) -> str: lock_tx_dest = self.getScriptDest(lock_script) @@ -82,58 +85,83 @@ class FIROInterface(BTCInterface): if not self.isAddressMine(address, or_watch_only=True): # Expects P2WSH nested in BIP16_P2SH - ro = self.rpc('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True]) - addr_info = self.rpc('validateaddress', [address]) + self.rpc("importaddress", [lock_tx_dest.hex(), "bid lock", False, True]) return address - def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1): + def getLockTxHeight( + self, + txid, + dest_address, + bid_amount, + rescan_from, + find_index: bool = False, + vout: int = -1, + ): # Add watchonly address and rescan if required if not self.isAddressMine(dest_address, or_watch_only=True): - self.importWatchOnlyAddress(dest_address, 'bid') - self._log.info('Imported watch-only addr: {}'.format(dest_address)) - self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from)) + self.importWatchOnlyAddress(dest_address, "bid") + self._log.info("Imported watch-only addr: {}".format(dest_address)) + self._log.info( + "Rescanning {} chain from height: {}".format( + self.coin_name(), rescan_from + ) + ) self.rescanBlockchainForAddress(rescan_from, dest_address) return_txid = True if txid is None else False if txid is None: - txns = self.rpc('listunspent', [0, 9999999, [dest_address, ]]) + txns = self.rpc( + "listunspent", + [ + 0, + 9999999, + [ + dest_address, + ], + ], + ) for tx in txns: - if self.make_int(tx['amount']) == bid_amount: - txid = bytes.fromhex(tx['txid']) + if self.make_int(tx["amount"]) == bid_amount: + txid = bytes.fromhex(tx["txid"]) break if txid is None: return None try: - tx = self.rpc('gettransaction', [txid.hex()]) + tx = self.rpc("gettransaction", [txid.hex()]) block_height = 0 - if 'blockhash' in tx: - block_header = self.rpc('getblockheader', [tx['blockhash']]) - block_height = block_header['height'] + if "blockhash" in tx: + block_header = self.rpc("getblockheader", [tx["blockhash"]]) + block_height = block_header["height"] rv = { - 'depth': 0 if 'confirmations' not in tx else tx['confirmations'], - 'height': block_height} + "depth": 0 if "confirmations" not in tx else tx["confirmations"], + "height": block_height, + } except Exception as e: - self._log.debug('getLockTxHeight gettransaction failed: %s, %s', txid.hex(), str(e)) + self._log.debug( + "getLockTxHeight gettransaction failed: %s, %s", txid.hex(), str(e) + ) return None if find_index: - tx_obj = self.rpc('decoderawtransaction', [tx['hex']]) - rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address) + tx_obj = self.rpc("decoderawtransaction", [tx["hex"]]) + rv["index"] = find_vout_for_address_from_txobj(tx_obj, dest_address) if return_txid: - rv['txid'] = txid.hex() + rv["txid"] = txid.hex() return rv - def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes: + def createSCLockTx( + self, value: int, script: bytearray, vkbv: bytes = None + ) -> bytes: tx = CTransaction() tx.nVersion = self.txVersion() tx.vout.append(self.txoType()(value, self.getScriptDest(script))) @@ -144,24 +172,36 @@ class FIROInterface(BTCInterface): return self.fundTx(tx_bytes, feerate) def signTxWithWallet(self, tx): - rv = self.rpc('signrawtransaction', [tx.hex()]) - return bytes.fromhex(rv['hex']) + rv = self.rpc("signrawtransaction", [tx.hex()]) + return bytes.fromhex(rv["hex"]) - def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str: - txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) + def createRawFundedTransaction( + self, + addr_to: str, + amount: int, + sub_fee: bool = False, + lock_unspents: bool = True, + ) -> str: + txn = self.rpc( + "createrawtransaction", [[], {addr_to: self.format_amount(amount)}] + ) fee_rate, fee_src = self.get_fee_rate(self._conf_target) - self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}') + self._log.debug( + f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}" + ) options = { - 'lockUnspents': lock_unspents, - 'feeRate': fee_rate, + "lockUnspents": lock_unspents, + "feeRate": fee_rate, } if sub_fee: - options['subtractFeeFromOutputs'] = [0,] - return self.rpc('fundrawtransaction', [txn, options])['hex'] + options["subtractFeeFromOutputs"] = [ + 0, + ] + return self.rpc("fundrawtransaction", [txn, options])["hex"] def createRawSignedTransaction(self, addr_to, amount) -> str: txn_funded = self.createRawFundedTransaction(addr_to, amount) - return self.rpc('signrawtransaction', [txn_funded])['hex'] + return self.rpc("signrawtransaction", [txn_funded])["hex"] def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray: # Return P2PKH @@ -188,60 +228,75 @@ class FIROInterface(BTCInterface): return CScript([OP_HASH160, script_hash, OP_EQUAL]) def withdrawCoin(self, value, addr_to, subfee): - params = [addr_to, value, '', '', subfee] - return self.rpc('sendtoaddress', params) + params = [addr_to, value, "", "", subfee] + return self.rpc("sendtoaddress", params) def getWalletSeedID(self): - return self.rpc('getwalletinfo')['hdmasterkeyid'] + return self.rpc("getwalletinfo")["hdmasterkeyid"] def getSpendableBalance(self) -> int: - return self.make_int(self.rpc('getwalletinfo')['balance']) + return self.make_int(self.rpc("getwalletinfo")["balance"]) def getBLockSpendTxFee(self, tx, fee_rate: int) -> int: add_bytes = 107 size = len(tx.serialize_with_witness()) + add_bytes pay_fee = round(fee_rate * size / 1000) - self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.') + self._log.info( + f"BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}." + ) return pay_fee def signTxWithKey(self, tx: bytes, key: bytes) -> bytes: key_wif = self.encodeKey(key) - rv = self.rpc('signrawtransaction', [tx.hex(), [], [key_wif, ]]) - return bytes.fromhex(rv['hex']) + rv = self.rpc( + "signrawtransaction", + [ + tx.hex(), + [], + [ + key_wif, + ], + ], + ) + return bytes.fromhex(rv["hex"]) def findTxnByHash(self, txid_hex: str): # Only works for wallet txns try: - rv = self.rpc('gettransaction', [txid_hex]) - except Exception as ex: - self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) + rv = self.rpc("gettransaction", [txid_hex]) + except Exception as e: # noqa: F841 + self._log.debug( + "findTxnByHash getrawtransaction failed: {}".format(txid_hex) + ) return None - if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: - block_height = self.getBlockHeader(rv['blockhash'])['height'] - return {'txid': txid_hex, 'amount': 0, 'height': block_height} + if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed: + block_height = self.getBlockHeader(rv["blockhash"])["height"] + return {"txid": txid_hex, "amount": 0, "height": block_height} return None def getProofOfFunds(self, amount_for, extra_commit_bytes): # TODO: Lock unspent and use same output/s to fund bid unspents_by_addr = dict() - unspents = self.rpc('listunspent') + unspents = self.rpc("listunspent") for u in unspents: - if u['spendable'] is not True: + if u["spendable"] is not True: continue - if u['address'] not in unspents_by_addr: - unspents_by_addr[u['address']] = {'total': 0, 'utxos': []} - utxo_amount: int = self.make_int(u['amount'], r=1) - unspents_by_addr[u['address']]['total'] += utxo_amount - unspents_by_addr[u['address']]['utxos'].append((utxo_amount, u['txid'], u['vout'])) + if u["address"] not in unspents_by_addr: + unspents_by_addr[u["address"]] = {"total": 0, "utxos": []} + utxo_amount: int = self.make_int(u["amount"], r=1) + unspents_by_addr[u["address"]]["total"] += utxo_amount + unspents_by_addr[u["address"]]["utxos"].append( + (utxo_amount, u["txid"], u["vout"]) + ) max_utxos: int = 4 viable_addrs = [] for addr, data in unspents_by_addr.items(): - if data['total'] >= amount_for: + if data["total"] >= amount_for: # Sort from largest to smallest amount - sorted_utxos = sorted(data['utxos'], key=lambda x: x[0]) + sorted_utxos = sorted(data["utxos"], key=lambda x: x[0]) # Max outputs required to reach amount_for utxos_req: int = 0 @@ -256,13 +311,17 @@ class FIROInterface(BTCInterface): viable_addrs.append(addr) continue - ensure(len(viable_addrs) > 0, 'Could not find address with enough funds for proof') + ensure( + len(viable_addrs) > 0, "Could not find address with enough funds for proof" + ) sign_for_addr: str = random.choice(viable_addrs) - self._log.debug('sign_for_addr %s', sign_for_addr) + self._log.debug("sign_for_addr %s", sign_for_addr) prove_utxos = [] - sorted_utxos = sorted(unspents_by_addr[sign_for_addr]['utxos'], key=lambda x: x[0]) + sorted_utxos = sorted( + unspents_by_addr[sign_for_addr]["utxos"], key=lambda x: x[0] + ) hasher = hashlib.sha256() @@ -272,18 +331,29 @@ class FIROInterface(BTCInterface): outpoint = (bytes.fromhex(utxo[1]), utxo[2]) prove_utxos.append(outpoint) hasher.update(outpoint[0]) - hasher.update(outpoint[1].to_bytes(2, 'big')) + hasher.update(outpoint[1].to_bytes(2, "big")) if sum_value >= amount_for: break utxos_hash = hasher.digest() - if self.using_segwit(): # TODO: Use isSegwitAddress when scantxoutset can use combo + if ( + self.using_segwit() + ): # TODO: Use isSegwitAddress when scantxoutset can use combo # 'Address does not refer to key' for non p2pkh pkh = self.decodeAddress(sign_for_addr) sign_for_addr = self.pkh_to_address(pkh) - self._log.debug('sign_for_addr converted %s', sign_for_addr) + self._log.debug("sign_for_addr converted %s", sign_for_addr) - signature = self.rpc('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()]) + signature = self.rpc( + "signmessage", + [ + sign_for_addr, + sign_for_addr + + "_swap_proof_" + + utxos_hash.hex() + + extra_commit_bytes.hex(), + ], + ) return (sign_for_addr, signature, prove_utxos) @@ -292,19 +362,23 @@ class FIROInterface(BTCInterface): sum_value: int = 0 for outpoint in utxos: hasher.update(outpoint[0]) - hasher.update(outpoint[1].to_bytes(2, 'big')) + hasher.update(outpoint[1].to_bytes(2, "big")) utxos_hash = hasher.digest() - passed = self.verifyMessage(address, address + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex(), signature) - ensure(passed is True, 'Proof of funds signature invalid') + passed = self.verifyMessage( + address, + address + "_swap_proof_" + utxos_hash.hex() + extra_commit_bytes.hex(), + signature, + ) + ensure(passed is True, "Proof of funds signature invalid") if self.using_segwit(): address = self.encodeSegwitAddress(decodeAddress(address)[1:]) sum_value: int = 0 for outpoint in utxos: - txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1]]) - sum_value += self.make_int(txout['value']) + txout = self.rpc("gettxout", [outpoint[0].hex(), outpoint[1]]) + sum_value += self.make_int(txout["value"]) return sum_value @@ -314,15 +388,15 @@ class FIROInterface(BTCInterface): chain_blocks: int = self.getChainHeight() current_height: int = chain_blocks - block_hash = self.rpc('getblockhash', [current_height]) + block_hash = self.rpc("getblockhash", [current_height]) script_hash: bytes = self.decodeAddress(addr_find) find_scriptPubKey = self.getDestForScriptHash(script_hash) while current_height > height_start: - block_hash = self.rpc('getblockhash', [current_height]) + block_hash = self.rpc("getblockhash", [current_height]) - block = self.rpc('getblock', [block_hash, False]) + block = self.rpc("getblock", [block_hash, False]) decoded_block = CBlock() decoded_block = FromHex(decoded_block, block) for tx in decoded_block.vtx: @@ -330,38 +404,46 @@ class FIROInterface(BTCInterface): if txo.scriptPubKey == find_scriptPubKey: tx.rehash() txid = i2b(tx.sha256) - self._log.info('Found output to addr: {} in tx {} in block {}'.format(addr_find, txid.hex(), block_hash)) - self._log.info('rescanblockchain hack invalidateblock {}'.format(block_hash)) - self.rpc('invalidateblock', [block_hash]) - self.rpc('reconsiderblock', [block_hash]) + self._log.info( + "Found output to addr: {} in tx {} in block {}".format( + addr_find, txid.hex(), block_hash + ) + ) + self._log.info( + "rescanblockchain hack invalidateblock {}".format( + block_hash + ) + ) + self.rpc("invalidateblock", [block_hash]) + self.rpc("reconsiderblock", [block_hash]) return current_height -= 1 def getBlockWithTxns(self, block_hash: str): # TODO: Bypass decoderawtransaction and getblockheader - block = self.rpc('getblock', [block_hash, False]) - block_header = self.rpc('getblockheader', [block_hash]) + block = self.rpc("getblock", [block_hash, False]) + block_header = self.rpc("getblockheader", [block_hash]) decoded_block = CBlock() decoded_block = FromHex(decoded_block, block) tx_rv = [] for tx in decoded_block.vtx: tx_hex = tx.serialize_with_witness().hex() - tx_dec = self.rpc('decoderawtransaction', [tx_hex]) - if 'hex' not in tx_dec: - tx_dec['hex'] = tx_hex + tx_dec = self.rpc("decoderawtransaction", [tx_hex]) + if "hex" not in tx_dec: + tx_dec["hex"] = tx_hex tx_rv.append(tx_dec) block_rv = { - 'hash': block_hash, - 'previousblockhash': block_header['previousblockhash'], - 'tx': tx_rv, - 'confirmations': block_header['confirmations'], - 'height': block_header['height'], - 'time': block_header['time'], - 'version': block_header['version'], - 'merkleroot': block_header['merkleroot'], + "hash": block_hash, + "previousblockhash": block_header["previousblockhash"], + "tx": tx_rv, + "confirmations": block_header["confirmations"], + "height": block_header["height"], + "time": block_header["time"], + "version": block_header["version"], + "merkleroot": block_header["merkleroot"], } return block_rv diff --git a/basicswap/interface/ltc.py b/basicswap/interface/ltc.py index 19f5bf3..a6c3ba2 100644 --- a/basicswap/interface/ltc.py +++ b/basicswap/interface/ltc.py @@ -17,63 +17,73 @@ class LTCInterface(BTCInterface): def __init__(self, coin_settings, network, swap_client=None): super(LTCInterface, self).__init__(coin_settings, network, swap_client) - self._rpc_wallet_mweb = 'mweb' - self.rpc_wallet_mweb = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet_mweb) + self._rpc_wallet_mweb = "mweb" + self.rpc_wallet_mweb = make_rpc_func( + self._rpcport, + self._rpcauth, + host=self._rpc_host, + wallet=self._rpc_wallet_mweb, + ) - def getNewMwebAddress(self, use_segwit=False, label='swap_receive') -> str: - return self.rpc_wallet_mweb('getnewaddress', [label, 'mweb']) + def getNewMwebAddress(self, use_segwit=False, label="swap_receive") -> str: + return self.rpc_wallet_mweb("getnewaddress", [label, "mweb"]) - def getNewStealthAddress(self, label=''): + def getNewStealthAddress(self, label=""): return self.getNewMwebAddress(False, label) def withdrawCoin(self, value, type_from: str, addr_to: str, subfee: bool) -> str: - params = [addr_to, value, '', '', subfee, True, self._conf_target] - if type_from == 'mweb': - return self.rpc_wallet_mweb('sendtoaddress', params) - return self.rpc_wallet('sendtoaddress', params) + params = [addr_to, value, "", "", subfee, True, self._conf_target] + if type_from == "mweb": + return self.rpc_wallet_mweb("sendtoaddress", params) + return self.rpc_wallet("sendtoaddress", params) def createUTXO(self, value_sats: int): # Create a new address and send value_sats to it spendable_balance = self.getSpendableBalance() if spendable_balance < value_sats: - raise ValueError('Balance too low') + raise ValueError("Balance too low") - address = self.getNewAddress(self._use_segwit, 'create_utxo') - return self.withdrawCoin(self.format_amount(value_sats), 'plain', address, False), address + address = self.getNewAddress(self._use_segwit, "create_utxo") + return ( + self.withdrawCoin(self.format_amount(value_sats), "plain", address, False), + address, + ) def getWalletInfo(self): rv = super(LTCInterface, self).getWalletInfo() - mweb_info = self.rpc_wallet_mweb('getwalletinfo') - rv['mweb_balance'] = mweb_info['balance'] - rv['mweb_unconfirmed'] = mweb_info['unconfirmed_balance'] - rv['mweb_immature'] = mweb_info['immature_balance'] + mweb_info = self.rpc_wallet_mweb("getwalletinfo") + rv["mweb_balance"] = mweb_info["balance"] + rv["mweb_unconfirmed"] = mweb_info["unconfirmed_balance"] + rv["mweb_immature"] = mweb_info["immature_balance"] return rv def getUnspentsByAddr(self): unspent_addr = dict() - unspent = self.rpc_wallet('listunspent') + unspent = self.rpc_wallet("listunspent") for u in unspent: - if u.get('spendable', False) is False: + if u.get("spendable", False) is False: continue - if u.get('solvable', False) is False: # Filter out mweb outputs + if u.get("solvable", False) is False: # Filter out mweb outputs continue - if 'address' not in u: + if "address" not in u: continue - if 'desc' in u: - desc = u['desc'] + if "desc" in u: + desc = u["desc"] if self.using_segwit: if self.use_p2shp2wsh(): - if not desc.startswith('sh(wpkh'): + if not desc.startswith("sh(wpkh"): continue else: - if not desc.startswith('wpkh'): + if not desc.startswith("wpkh"): continue else: - if not desc.startswith('pkh'): + if not desc.startswith("pkh"): continue - unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1) + unspent_addr[u["address"]] = unspent_addr.get( + u["address"], 0 + ) + self.make_int(u["amount"], r=1) return unspent_addr @@ -84,8 +94,10 @@ class LTCInterfaceMWEB(LTCInterface): def __init__(self, coin_settings, network, swap_client=None): super(LTCInterfaceMWEB, self).__init__(coin_settings, network, swap_client) - self._rpc_wallet = 'mweb' - self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet) + self._rpc_wallet = "mweb" + self.rpc_wallet = make_rpc_func( + self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet + ) def chainparams(self): return chainparams[Coins.LTC] @@ -95,54 +107,54 @@ class LTCInterfaceMWEB(LTCInterface): def coin_name(self) -> str: coin_chainparams = chainparams[Coins.LTC] - return coin_chainparams['name'].capitalize() + ' MWEB' + return coin_chainparams["name"].capitalize() + " MWEB" def ticker(self) -> str: - ticker = chainparams[Coins.LTC]['ticker'] - if self._network == 'testnet': - ticker = 't' + ticker - elif self._network == 'regtest': - ticker = 'rt' + ticker - return ticker + '_MWEB' + ticker = chainparams[Coins.LTC]["ticker"] + if self._network == "testnet": + ticker = "t" + ticker + elif self._network == "regtest": + ticker = "rt" + ticker + return ticker + "_MWEB" - def getNewAddress(self, use_segwit=False, label='swap_receive') -> str: + def getNewAddress(self, use_segwit=False, label="swap_receive") -> str: return self.getNewMwebAddress() def has_mweb_wallet(self) -> bool: - return 'mweb' in self.rpc('listwallets') + return "mweb" in self.rpc("listwallets") def init_wallet(self, password=None): # If system is encrypted mweb wallet will be created at first unlock - self._log.info('init_wallet - {}'.format(self.ticker())) + self._log.info("init_wallet - {}".format(self.ticker())) - self._log.info('Creating mweb wallet for {}.'.format(self.coin_name())) + self._log.info("Creating mweb wallet for {}.".format(self.coin_name())) # wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors, load_on_startup - self.rpc('createwallet', ['mweb', False, True, password, False, False, True]) + self.rpc("createwallet", ["mweb", False, True, password, False, False, True]) if password is not None: # Max timeout value, ~3 years - self.rpc_wallet('walletpassphrase', [password, 100000000]) + self.rpc_wallet("walletpassphrase", [password, 100000000]) - if self.getWalletSeedID() == 'Not found': + if self.getWalletSeedID() == "Not found": self._sc.initialiseWallet(self.coin_type()) # Workaround to trigger mweb_spk_man->LoadMWEBKeychain() - self.rpc('unloadwallet', ['mweb']) - self.rpc('loadwallet', ['mweb']) + self.rpc("unloadwallet", ["mweb"]) + self.rpc("loadwallet", ["mweb"]) if password is not None: - self.rpc_wallet('walletpassphrase', [password, 100000000]) - self.rpc_wallet('keypoolrefill') + self.rpc_wallet("walletpassphrase", [password, 100000000]) + self.rpc_wallet("keypoolrefill") def unlockWallet(self, password: str): - if password == '': + if password == "": return - self._log.info('unlockWallet - {}'.format(self.ticker())) + self._log.info("unlockWallet - {}".format(self.ticker())) if not self.has_mweb_wallet(): self.init_wallet(password) else: # Max timeout value, ~3 years - self.rpc_wallet('walletpassphrase', [password, 100000000]) + self.rpc_wallet("walletpassphrase", [password, 100000000]) self._sc.checkWalletSeed(self.coin_type()) diff --git a/basicswap/interface/nav.py b/basicswap/interface/nav.py index 511436e..4f131e5 100644 --- a/basicswap/interface/nav.py +++ b/basicswap/interface/nav.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2023 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -38,7 +39,9 @@ from basicswap.util.address import ( encodeAddress, ) from basicswap.util import ( - b2i, i2b, i2h, + b2i, + i2b, + i2h, ensure, ) from basicswap.basicswap_util import ( @@ -49,7 +52,10 @@ from basicswap.interface.contrib.nav_test_framework.script import ( CScript, OP_0, OP_EQUAL, - OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, + OP_DUP, + OP_HASH160, + OP_EQUALVERIFY, + OP_CHECKSIG, SIGHASH_ALL, SegwitVersion1SignatureHash, ) @@ -71,7 +77,9 @@ class NAVInterface(BTCInterface): def __init__(self, coin_settings, network, swap_client=None): super(NAVInterface, self).__init__(coin_settings, network, swap_client) # No multiwallet support - self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) + self.rpc_wallet = make_rpc_func( + self._rpcport, self._rpcauth, host=self._rpc_host + ) def use_p2shp2wsh(self) -> bool: # p2sh-p2wsh @@ -85,31 +93,31 @@ class NAVInterface(BTCInterface): return 1 def getWalletSeedID(self): - return self.rpc('getwalletinfo')['hdmasterkeyid'] + return self.rpc("getwalletinfo")["hdmasterkeyid"] def withdrawCoin(self, value, addr_to: str, subfee: bool): - strdzeel = '' - params = [addr_to, value, '', '', strdzeel, subfee] - return self.rpc('sendtoaddress', params) + strdzeel = "" + params = [addr_to, value, "", "", strdzeel, subfee] + return self.rpc("sendtoaddress", params) def getSpendableBalance(self) -> int: - return self.make_int(self.rpc('getwalletinfo')['balance']) + return self.make_int(self.rpc("getwalletinfo")["balance"]) def signTxWithWallet(self, tx: bytes) -> bytes: - rv = self.rpc('signrawtransaction', [tx.hex()]) + rv = self.rpc("signrawtransaction", [tx.hex()]) - return bytes.fromhex(rv['hex']) + return bytes.fromhex(rv["hex"]) def checkExpectedSeed(self, key_hash: str): try: - rv = self.rpc('dumpmnemonic') - entropy = Mnemonic('english').to_entropy(rv.split(' ')) + rv = self.rpc("dumpmnemonic") + entropy = Mnemonic("english").to_entropy(rv.split(" ")) entropy_hash = self.getAddressHashFromKey(entropy)[::-1].hex() self._have_checked_seed = True return entropy_hash == key_hash except Exception as e: - self._log.warning('checkExpectedSeed failed: {}'.format(str(e))) + self._log.warning("checkExpectedSeed failed: {}".format(str(e))) return False def getScriptForP2PKH(self, pkh: bytes) -> bytearray: @@ -134,13 +142,22 @@ class NAVInterface(BTCInterface): script = CScript([OP_0, pkh]) script_hash = hash160(script) assert len(script_hash) == 20 - return encodeAddress(bytes((self.chainparams_network()['script_address'],)) + script_hash) + return encodeAddress( + bytes((self.chainparams_network()["script_address"],)) + script_hash + ) def encodeSegwitAddressScript(self, script: bytes) -> str: - if len(script) == 23 and script[0] == OP_HASH160 and script[1] == 20 and script[22] == OP_EQUAL: + if ( + len(script) == 23 + and script[0] == OP_HASH160 + and script[1] == 20 + and script[22] == OP_EQUAL + ): script_hash = script[2:22] - return encodeAddress(bytes((self.chainparams_network()['script_address'],)) + script_hash) - raise ValueError('Unknown Script') + return encodeAddress( + bytes((self.chainparams_network()["script_address"],)) + script_hash + ) + raise ValueError("Unknown Script") def loadTx(self, tx_bytes: bytes) -> CTransaction: # Load tx from bytes to internal representation @@ -148,9 +165,18 @@ class NAVInterface(BTCInterface): tx.deserialize(BytesIO(tx_bytes)) return tx - def signTx(self, key_bytes: bytes, tx_bytes: bytes, input_n: int, prevout_script, prevout_value: int): + def signTx( + self, + key_bytes: bytes, + tx_bytes: bytes, + input_n: int, + prevout_script, + prevout_value: int, + ): tx = self.loadTx(tx_bytes) - sig_hash = SegwitVersion1SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value) + sig_hash = SegwitVersion1SignatureHash( + prevout_script, tx, input_n, SIGHASH_ALL, prevout_value + ) eck = PrivateKey(key_bytes) return eck.sign(sig_hash, hasher=None) + bytes((SIGHASH_ALL,)) @@ -165,23 +191,25 @@ class NAVInterface(BTCInterface): # TODO: Lock unspent and use same output/s to fund bid unspents_by_addr = dict() - unspents = self.rpc('listunspent') + unspents = self.rpc("listunspent") for u in unspents: - if u['spendable'] is not True: + if u["spendable"] is not True: continue - if u['address'] not in unspents_by_addr: - unspents_by_addr[u['address']] = {'total': 0, 'utxos': []} - utxo_amount: int = self.make_int(u['amount'], r=1) - unspents_by_addr[u['address']]['total'] += utxo_amount - unspents_by_addr[u['address']]['utxos'].append((utxo_amount, u['txid'], u['vout'])) + if u["address"] not in unspents_by_addr: + unspents_by_addr[u["address"]] = {"total": 0, "utxos": []} + utxo_amount: int = self.make_int(u["amount"], r=1) + unspents_by_addr[u["address"]]["total"] += utxo_amount + unspents_by_addr[u["address"]]["utxos"].append( + (utxo_amount, u["txid"], u["vout"]) + ) max_utxos: int = 4 viable_addrs = [] for addr, data in unspents_by_addr.items(): - if data['total'] >= amount_for: + if data["total"] >= amount_for: # Sort from largest to smallest amount - sorted_utxos = sorted(data['utxos'], key=lambda x: x[0]) + sorted_utxos = sorted(data["utxos"], key=lambda x: x[0]) # Max outputs required to reach amount_for utxos_req: int = 0 @@ -196,13 +224,17 @@ class NAVInterface(BTCInterface): viable_addrs.append(addr) continue - ensure(len(viable_addrs) > 0, 'Could not find address with enough funds for proof') + ensure( + len(viable_addrs) > 0, "Could not find address with enough funds for proof" + ) sign_for_addr: str = random.choice(viable_addrs) - self._log.debug('sign_for_addr %s', sign_for_addr) + self._log.debug("sign_for_addr %s", sign_for_addr) prove_utxos = [] - sorted_utxos = sorted(unspents_by_addr[sign_for_addr]['utxos'], key=lambda x: x[0]) + sorted_utxos = sorted( + unspents_by_addr[sign_for_addr]["utxos"], key=lambda x: x[0] + ) hasher = hashlib.sha256() @@ -212,20 +244,36 @@ class NAVInterface(BTCInterface): outpoint = (bytes.fromhex(utxo[1]), utxo[2]) prove_utxos.append(outpoint) hasher.update(outpoint[0]) - hasher.update(outpoint[1].to_bytes(2, 'big')) + hasher.update(outpoint[1].to_bytes(2, "big")) if sum_value >= amount_for: break utxos_hash = hasher.digest() - if self.using_segwit(): # TODO: Use isSegwitAddress when scantxoutset can use combo + if ( + self.using_segwit() + ): # TODO: Use isSegwitAddress when scantxoutset can use combo # 'Address does not refer to key' for non p2pkh - addr_info = self.rpc('validateaddress', [addr, ]) - if 'isscript' in addr_info and addr_info['isscript'] and 'hex' in addr_info: - pkh = bytes.fromhex(addr_info['hex'])[2:] + addr_info = self.rpc( + "validateaddress", + [ + addr, + ], + ) + if "isscript" in addr_info and addr_info["isscript"] and "hex" in addr_info: + pkh = bytes.fromhex(addr_info["hex"])[2:] sign_for_addr = self.pkh_to_address(pkh) - self._log.debug('sign_for_addr converted %s', sign_for_addr) + self._log.debug("sign_for_addr converted %s", sign_for_addr) - signature = self.rpc('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex()]) + signature = self.rpc( + "signmessage", + [ + sign_for_addr, + sign_for_addr + + "_swap_proof_" + + utxos_hash.hex() + + extra_commit_bytes.hex(), + ], + ) return (sign_for_addr, signature, prove_utxos) @@ -234,48 +282,64 @@ class NAVInterface(BTCInterface): sum_value: int = 0 for outpoint in utxos: hasher.update(outpoint[0]) - hasher.update(outpoint[1].to_bytes(2, 'big')) + hasher.update(outpoint[1].to_bytes(2, "big")) utxos_hash = hasher.digest() - passed = self.verifyMessage(address, address + '_swap_proof_' + utxos_hash.hex() + extra_commit_bytes.hex(), signature) - ensure(passed is True, 'Proof of funds signature invalid') + passed = self.verifyMessage( + address, + address + "_swap_proof_" + utxos_hash.hex() + extra_commit_bytes.hex(), + signature, + ) + ensure(passed is True, "Proof of funds signature invalid") if self.using_segwit(): address = self.encodeSegwitAddress(self.decodeAddress(address)[1:]) sum_value: int = 0 for outpoint in utxos: - txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1]]) - sum_value += self.make_int(txout['value']) + txout = self.rpc("gettxout", [outpoint[0].hex(), outpoint[1]]) + sum_value += self.make_int(txout["value"]) return sum_value - def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str: - txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) + def createRawFundedTransaction( + self, + addr_to: str, + amount: int, + sub_fee: bool = False, + lock_unspents: bool = True, + ) -> str: + txn = self.rpc( + "createrawtransaction", [[], {addr_to: self.format_amount(amount)}] + ) fee_rate, fee_src = self.get_fee_rate(self._conf_target) - self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}') + self._log.debug( + f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}" + ) if sub_fee: - raise ValueError('Navcoin fundrawtransaction is missing the subtractFeeFromOutputs parameter') + raise ValueError( + "Navcoin fundrawtransaction is missing the subtractFeeFromOutputs parameter" + ) # options['subtractFeeFromOutputs'] = [0,] fee_rate = self.make_int(fee_rate, r=1) return self.fundTx(txn, fee_rate, lock_unspents).hex() def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool: - addr_info = self.rpc('validateaddress', [address]) + addr_info = self.rpc("validateaddress", [address]) if not or_watch_only: - return addr_info['ismine'] - return addr_info['ismine'] or addr_info['iswatchonly'] + return addr_info["ismine"] + return addr_info["ismine"] or addr_info["iswatchonly"] def createRawSignedTransaction(self, addr_to, amount) -> str: txn_funded = self.createRawFundedTransaction(addr_to, amount) - return self.rpc('signrawtransaction', [txn_funded])['hex'] + return self.rpc("signrawtransaction", [txn_funded])["hex"] def getBlockchainInfo(self): - rv = self.rpc('getblockchaininfo') - synced = round(rv['verificationprogress'], 3) + rv = self.rpc("getblockchaininfo") + synced = round(rv["verificationprogress"], 3) if synced >= 0.997: - rv['verificationprogress'] = 1.0 + rv["verificationprogress"] = 1.0 return rv def encodeScriptDest(self, script_dest: bytes) -> str: @@ -283,47 +347,75 @@ class NAVInterface(BTCInterface): return self.sh_to_address(script_hash) def encode_p2wsh(self, script: bytes) -> str: - return pubkeyToAddress(self.chainparams_network()['script_address'], script) + return pubkeyToAddress(self.chainparams_network()["script_address"], script) def find_prevout_info(self, txn_hex: str, txn_script: bytes): - txjs = self.rpc('decoderawtransaction', [txn_hex]) + txjs = self.rpc("decoderawtransaction", [txn_hex]) n = getVoutByScriptPubKey(txjs, self.getScriptDest(txn_script).hex()) return { - 'txid': txjs['txid'], - 'vout': n, - 'scriptPubKey': txjs['vout'][n]['scriptPubKey']['hex'], - 'redeemScript': txn_script.hex(), - 'amount': txjs['vout'][n]['value'] + "txid": txjs["txid"], + "vout": n, + "scriptPubKey": txjs["vout"][n]["scriptPubKey"]["hex"], + "redeemScript": txn_script.hex(), + "amount": txjs["vout"][n]["value"], } - def getNewAddress(self, use_segwit: bool, label: str = 'swap_receive') -> str: - address: str = self.rpc('getnewaddress', [label,]) + def getNewAddress(self, use_segwit: bool, label: str = "swap_receive") -> str: + address: str = self.rpc( + "getnewaddress", + [ + label, + ], + ) if use_segwit: - return self.rpc('addwitnessaddress', [address,]) + return self.rpc( + "addwitnessaddress", + [ + address, + ], + ) return address - def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes) -> str: + def createRedeemTxn( + self, prevout, output_addr: str, output_value: int, txn_script: bytes + ) -> str: tx = CTransaction() tx.nVersion = self.txVersion() - prev_txid = b2i(bytes.fromhex(prevout['txid'])) + prev_txid = b2i(bytes.fromhex(prevout["txid"])) - tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']), - scriptSig=self.getScriptScriptSig(txn_script))) + tx.vin.append( + CTxIn( + COutPoint(prev_txid, prevout["vout"]), + scriptSig=self.getScriptScriptSig(txn_script), + ) + ) pkh = self.decodeAddress(output_addr) script = self.getScriptForPubkeyHash(pkh) tx.vout.append(self.txoType()(output_value, script)) tx.rehash() return tx.serialize().hex() - def createRefundTxn(self, prevout, output_addr: str, output_value: int, locktime: int, sequence: int, txn_script: bytes) -> str: + def createRefundTxn( + self, + prevout, + output_addr: str, + output_value: int, + locktime: int, + sequence: int, + txn_script: bytes, + ) -> str: tx = CTransaction() tx.nVersion = self.txVersion() tx.nLockTime = locktime - prev_txid = b2i(bytes.fromhex(prevout['txid'])) - tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']), - nSequence=sequence, - scriptSig=self.getScriptScriptSig(txn_script))) + prev_txid = b2i(bytes.fromhex(prevout["txid"])) + tx.vin.append( + CTxIn( + COutPoint(prev_txid, prevout["vout"]), + nSequence=sequence, + scriptSig=self.getScriptScriptSig(txn_script), + ) + ) pkh = self.decodeAddress(output_addr) script = self.getScriptForPubkeyHash(pkh) tx.vout.append(self.txoType()(output_value, script)) @@ -332,22 +424,38 @@ class NAVInterface(BTCInterface): def getTxSignature(self, tx_hex: str, prevout_data, key_wif: str) -> str: key = decodeWif(key_wif) - redeem_script = bytes.fromhex(prevout_data['redeemScript']) - sig = self.signTx(key, bytes.fromhex(tx_hex), 0, redeem_script, self.make_int(prevout_data['amount'])) + redeem_script = bytes.fromhex(prevout_data["redeemScript"]) + sig = self.signTx( + key, + bytes.fromhex(tx_hex), + 0, + redeem_script, + self.make_int(prevout_data["amount"]), + ) return sig.hex() - def verifyTxSig(self, tx_bytes: bytes, sig: bytes, K: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bool: + def verifyTxSig( + self, + tx_bytes: bytes, + sig: bytes, + K: bytes, + input_n: int, + prevout_script: bytes, + prevout_value: int, + ) -> bool: tx = self.loadTx(tx_bytes) - sig_hash = SegwitVersion1SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value) + sig_hash = SegwitVersion1SignatureHash( + prevout_script, tx, input_n, SIGHASH_ALL, prevout_value + ) pubkey = PublicKey(K) - return pubkey.verify(sig[: -1], sig_hash, hasher=None) # Pop the hashtype byte + return pubkey.verify(sig[:-1], sig_hash, hasher=None) # Pop the hashtype byte def verifyRawTransaction(self, tx_hex: str, prevouts): # Only checks signature # verifyrawtransaction - self._log.warning('NAV verifyRawTransaction only checks signature') + self._log.warning("NAV verifyRawTransaction only checks signature") inputs_valid: bool = False validscripts: int = 0 @@ -359,22 +467,26 @@ class NAVInterface(BTCInterface): input_n: int = 0 prevout_data = prevouts[input_n] - redeem_script = bytes.fromhex(prevout_data['redeemScript']) - prevout_value = self.make_int(prevout_data['amount']) + redeem_script = bytes.fromhex(prevout_data["redeemScript"]) + prevout_value = self.make_int(prevout_data["amount"]) - if self.verifyTxSig(tx_bytes, signature, pubkey, input_n, redeem_script, prevout_value): + if self.verifyTxSig( + tx_bytes, signature, pubkey, input_n, redeem_script, prevout_value + ): validscripts += 1 # TODO: validate inputs inputs_valid = True return { - 'inputs_valid': inputs_valid, - 'validscripts': validscripts, + "inputs_valid": inputs_valid, + "validscripts": validscripts, } def getHTLCSpendTxVSize(self, redeem: bool = True) -> int: - tx_vsize = 5 # Add a few bytes, sequence in script takes variable amount of bytes + tx_vsize = ( + 5 # Add a few bytes, sequence in script takes variable amount of bytes + ) tx_vsize += 184 if redeem else 187 return tx_vsize @@ -393,15 +505,15 @@ class NAVInterface(BTCInterface): chain_blocks: int = self.getChainHeight() current_height: int = chain_blocks - block_hash = self.rpc('getblockhash', [current_height]) + block_hash = self.rpc("getblockhash", [current_height]) script_hash: bytes = self.decodeAddress(addr_find) find_scriptPubKey = self.getDestForScriptHash(script_hash) while current_height > height_start: - block_hash = self.rpc('getblockhash', [current_height]) + block_hash = self.rpc("getblockhash", [current_height]) - block = self.rpc('getblock', [block_hash, False]) + block = self.rpc("getblock", [block_hash, False]) decoded_block = CBlock() decoded_block = FromHex(decoded_block, block) for tx in decoded_block.vtx: @@ -409,84 +521,116 @@ class NAVInterface(BTCInterface): if txo.scriptPubKey == find_scriptPubKey: tx.rehash() txid = i2b(tx.sha256) - self._log.info('Found output to addr: {} in tx {} in block {}'.format(addr_find, txid.hex(), block_hash)) - self._log.info('rescanblockchain hack invalidateblock {}'.format(block_hash)) - self.rpc('invalidateblock', [block_hash]) - self.rpc('reconsiderblock', [block_hash]) + self._log.info( + "Found output to addr: {} in tx {} in block {}".format( + addr_find, txid.hex(), block_hash + ) + ) + self._log.info( + "rescanblockchain hack invalidateblock {}".format( + block_hash + ) + ) + self.rpc("invalidateblock", [block_hash]) + self.rpc("reconsiderblock", [block_hash]) return current_height -= 1 - def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1): + def getLockTxHeight( + self, + txid, + dest_address, + bid_amount, + rescan_from, + find_index: bool = False, + vout: int = -1, + ): # Add watchonly address and rescan if required if not self.isAddressMine(dest_address, or_watch_only=True): - self.importWatchOnlyAddress(dest_address, 'bid') - self._log.info('Imported watch-only addr: {}'.format(dest_address)) - self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from)) + self.importWatchOnlyAddress(dest_address, "bid") + self._log.info("Imported watch-only addr: {}".format(dest_address)) + self._log.info( + "Rescanning {} chain from height: {}".format( + self.coin_name(), rescan_from + ) + ) self.rescanBlockchainForAddress(rescan_from, dest_address) return_txid = True if txid is None else False if txid is None: - txns = self.rpc('listunspent', [0, 9999999, [dest_address, ]]) + txns = self.rpc( + "listunspent", + [ + 0, + 9999999, + [ + dest_address, + ], + ], + ) for tx in txns: - if self.make_int(tx['amount']) == bid_amount: - txid = bytes.fromhex(tx['txid']) + if self.make_int(tx["amount"]) == bid_amount: + txid = bytes.fromhex(tx["txid"]) break if txid is None: return None try: - tx = self.rpc('gettransaction', [txid.hex()]) + tx = self.rpc("gettransaction", [txid.hex()]) block_height = 0 - if 'blockhash' in tx: - block_header = self.rpc('getblockheader', [tx['blockhash']]) - block_height = block_header['height'] + if "blockhash" in tx: + block_header = self.rpc("getblockheader", [tx["blockhash"]]) + block_height = block_header["height"] rv = { - 'depth': 0 if 'confirmations' not in tx else tx['confirmations'], - 'height': block_height} + "depth": 0 if "confirmations" not in tx else tx["confirmations"], + "height": block_height, + } except Exception as e: - self._log.debug('getLockTxHeight gettransaction failed: %s, %s', txid.hex(), str(e)) + self._log.debug( + "getLockTxHeight gettransaction failed: %s, %s", txid.hex(), str(e) + ) return None if find_index: - tx_obj = self.rpc('decoderawtransaction', [tx['hex']]) - rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address) + tx_obj = self.rpc("decoderawtransaction", [tx["hex"]]) + rv["index"] = find_vout_for_address_from_txobj(tx_obj, dest_address) if return_txid: - rv['txid'] = txid.hex() + rv["txid"] = txid.hex() return rv def getBlockWithTxns(self, block_hash): # TODO: Bypass decoderawtransaction and getblockheader - block = self.rpc('getblock', [block_hash, False]) - block_header = self.rpc('getblockheader', [block_hash]) + block = self.rpc("getblock", [block_hash, False]) + block_header = self.rpc("getblockheader", [block_hash]) decoded_block = CBlock() decoded_block = FromHex(decoded_block, block) tx_rv = [] for tx in decoded_block.vtx: tx_hex = tx.serialize_with_witness().hex() - tx_dec = self.rpc('decoderawtransaction', [tx_hex]) - if 'hex' not in tx_dec: - tx_dec['hex'] = tx_hex + tx_dec = self.rpc("decoderawtransaction", [tx_hex]) + if "hex" not in tx_dec: + tx_dec["hex"] = tx_hex tx_rv.append(tx_dec) block_rv = { - 'hash': block_hash, - 'previousblockhash': block_header['previousblockhash'], - 'tx': tx_rv, - 'confirmations': block_header['confirmations'], - 'height': block_header['height'], - 'time': block_header['time'], - 'version': block_header['version'], - 'merkleroot': block_header['merkleroot'], + "hash": block_hash, + "previousblockhash": block_header["previousblockhash"], + "tx": tx_rv, + "confirmations": block_header["confirmations"], + "height": block_header["height"], + "time": block_header["time"], + "version": block_header["version"], + "merkleroot": block_header["merkleroot"], } return block_rv @@ -513,15 +657,30 @@ class NAVInterface(BTCInterface): tx.vout.append(self.txoType()(output_amount, script_pk)) return tx.serialize() - def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, lock_tx_vout=None) -> bytes: - self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex()) - wtx = self.rpc('gettransaction', [chain_b_lock_txid.hex(), ]) - lock_tx = self.loadTx(bytes.fromhex(wtx['hex'])) + def spendBLockTx( + self, + chain_b_lock_txid: bytes, + address_to: str, + kbv: bytes, + kbs: bytes, + cb_swap_value: int, + b_fee: int, + restore_height: int, + lock_tx_vout=None, + ) -> bytes: + self._log.info("spendBLockTx %s:\n", chain_b_lock_txid.hex()) + wtx = self.rpc( + "gettransaction", + [ + chain_b_lock_txid.hex(), + ], + ) + lock_tx = self.loadTx(bytes.fromhex(wtx["hex"])) Kbs = self.getPubkey(kbs) script_pk = self.getPkDest(Kbs) locked_n = findOutput(lock_tx, script_pk) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") pkh_to = self.decodeAddress(address_to) tx = CTransaction() @@ -531,10 +690,16 @@ class NAVInterface(BTCInterface): script_sig = self.getInputScriptForPubkeyHash(self.getPubkeyHash(Kbs)) - tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n), - nSequence=0, - scriptSig=script_sig)) - tx.vout.append(self.txoType()(cb_swap_value, self.getScriptForPubkeyHash(pkh_to))) + tx.vin.append( + CTxIn( + COutPoint(chain_b_lock_txid_int, locked_n), + nSequence=0, + scriptSig=script_sig, + ) + ) + tx.vout.append( + self.txoType()(cb_swap_value, self.getScriptForPubkeyHash(pkh_to)) + ) pay_fee = self.getBLockSpendTxFee(tx, b_fee) tx.vout[0].nValue = cb_swap_value - pay_fee @@ -560,16 +725,20 @@ class NAVInterface(BTCInterface): def findTxnByHash(self, txid_hex: str): # Only works for wallet txns try: - rv = self.rpc('gettransaction', [txid_hex]) - except Exception as ex: - self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) + rv = self.rpc("gettransaction", [txid_hex]) + except Exception as e: # noqa: F841 + self._log.debug( + "findTxnByHash getrawtransaction failed: {}".format(txid_hex) + ) return None - if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: - block_height = self.getBlockHeader(rv['blockhash'])['height'] - return {'txid': txid_hex, 'amount': 0, 'height': block_height} + if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed: + block_height = self.getBlockHeader(rv["blockhash"])["height"] + return {"txid": txid_hex, "amount": 0, "height": block_height} return None - def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes: + def createSCLockTx( + self, value: int, script: bytearray, vkbv: bytes = None + ) -> bytes: tx = CTransaction() tx.nVersion = self.txVersion() tx.vout.append(self.txoType()(value, self.getScriptDest(script))) @@ -580,20 +749,20 @@ class NAVInterface(BTCInterface): feerate_str = self.format_amount(feerate) # TODO: unlock unspents if bid cancelled options = { - 'lockUnspents': lock_unspents, - 'feeRate': feerate_str, + "lockUnspents": lock_unspents, + "feeRate": feerate_str, } - rv = self.rpc('fundrawtransaction', [tx_hex, options]) + rv = self.rpc("fundrawtransaction", [tx_hex, options]) # Sign transaction then strip witness data to fill scriptsig - rv = self.rpc('signrawtransaction', [rv['hex']]) + rv = self.rpc("signrawtransaction", [rv["hex"]]) - tx_signed = self.loadTx(bytes.fromhex(rv['hex'])) + tx_signed = self.loadTx(bytes.fromhex(rv["hex"])) if len(tx_signed.vin) != len(tx_signed.wit.vtxinwit): - raise ValueError('txn has non segwit input') + raise ValueError("txn has non segwit input") for witness_data in tx_signed.wit.vtxinwit: if len(witness_data.scriptWitness.stack) < 2: - raise ValueError('txn has non segwit input') + raise ValueError("txn has non segwit input") return tx_signed.serialize_without_witness() @@ -601,13 +770,23 @@ class NAVInterface(BTCInterface): tx_funded = self.fundTx(tx_bytes.hex(), feerate) return tx_funded - def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None): + def createSCLockRefundTx( + self, + tx_lock_bytes, + script_lock, + Kal, + Kaf, + lock1_value, + csv_val, + tx_fee_rate, + vkbv=None, + ): tx_lock = CTransaction() tx_lock = self.loadTx(tx_lock_bytes) output_script = self.getScriptDest(script_lock) locked_n = findOutput(tx_lock, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx_lock.vout[locked_n].nValue tx_lock.rehash() @@ -616,9 +795,13 @@ class NAVInterface(BTCInterface): refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val) tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n), - nSequence=lock1_value, - scriptSig=self.getScriptScriptSig(script_lock))) + tx.vin.append( + CTxIn( + COutPoint(tx_lock_id_int, locked_n), + nSequence=lock1_value, + scriptSig=self.getScriptScriptSig(script_lock), + ) + ) tx.vout.append(self.txoType()(locked_coin, self.getScriptDest(refund_script))) dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock) @@ -628,12 +811,24 @@ class NAVInterface(BTCInterface): tx.vout[0].nValue = locked_coin - pay_fee tx.rehash() - self._log.info('createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) + self._log.info( + "createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.", + i2h(tx.sha256), + tx_fee_rate, + vsize, + pay_fee, + ) return tx.serialize(), refund_script, tx.vout[0].nValue - def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv=None): + def createSCLockRefundSpendTx( + self, + tx_lock_refund_bytes, + script_lock_refund, + pkh_refund_to, + tx_fee_rate, + vkbv=None, + ): # Returns the coinA locked coin to the leader # The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey # If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower @@ -642,7 +837,7 @@ class NAVInterface(BTCInterface): output_script = self.getScriptDest(script_lock_refund) locked_n = findOutput(tx_lock_refund, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx_lock_refund.vout[locked_n].nValue tx_lock_refund.rehash() @@ -650,25 +845,46 @@ class NAVInterface(BTCInterface): tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), - nSequence=0, - scriptSig=self.getScriptScriptSig(script_lock_refund))) + tx.vin.append( + CTxIn( + COutPoint(tx_lock_refund_hash_int, locked_n), + nSequence=0, + scriptSig=self.getScriptScriptSig(script_lock_refund), + ) + ) - tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to))) + tx.vout.append( + self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to)) + ) - dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(script_lock_refund) + dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness( + script_lock_refund + ) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) pay_fee = round(tx_fee_rate * vsize / 1000) tx.vout[0].nValue = locked_coin - pay_fee tx.rehash() - self._log.info('createSCLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) + self._log.info( + "createSCLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.", + i2h(tx.sha256), + tx_fee_rate, + vsize, + pay_fee, + ) return tx.serialize() - def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None, kbsf=None): + def createSCLockRefundSpendToFTx( + self, + tx_lock_refund_bytes, + script_lock_refund, + pkh_dest, + tx_fee_rate, + vkbv=None, + kbsf=None, + ): # lock refund swipe tx # Sends the coinA locked coin to the follower @@ -676,7 +892,7 @@ class NAVInterface(BTCInterface): output_script = self.getScriptDest(script_lock_refund) locked_n = findOutput(tx_lock_refund, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx_lock_refund.vout[locked_n].nValue A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund) @@ -686,29 +902,44 @@ class NAVInterface(BTCInterface): tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), - nSequence=lock2_value, - scriptSig=self.getScriptScriptSig(script_lock_refund))) + tx.vin.append( + CTxIn( + COutPoint(tx_lock_refund_hash_int, locked_n), + nSequence=lock2_value, + scriptSig=self.getScriptScriptSig(script_lock_refund), + ) + ) - tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest))) + tx.vout.append( + self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)) + ) - dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness(script_lock_refund) + dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness( + script_lock_refund + ) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) pay_fee = round(tx_fee_rate * vsize / 1000) tx.vout[0].nValue = locked_coin - pay_fee tx.rehash() - self._log.info('createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) + self._log.info( + "createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.", + i2h(tx.sha256), + tx_fee_rate, + vsize, + pay_fee, + ) return tx.serialize() - def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None, fee_info={}): + def createSCLockSpendTx( + self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None, fee_info={} + ): tx_lock = self.loadTx(tx_lock_bytes) output_script = self.getScriptDest(script_lock) locked_n = findOutput(tx_lock, output_script) - ensure(locked_n is not None, 'Output not found in tx') + ensure(locked_n is not None, "Output not found in tx") locked_coin = tx_lock.vout[locked_n].nValue tx_lock.rehash() @@ -716,10 +947,16 @@ class NAVInterface(BTCInterface): tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n), - scriptSig=self.getScriptScriptSig(script_lock))) + tx.vin.append( + CTxIn( + COutPoint(tx_lock_id_int, locked_n), + scriptSig=self.getScriptScriptSig(script_lock), + ) + ) - tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest))) + tx.vout.append( + self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest)) + ) dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) @@ -727,13 +964,18 @@ class NAVInterface(BTCInterface): pay_fee = round(tx_fee_rate * vsize / 1000) tx.vout[0].nValue = locked_coin - pay_fee - fee_info['fee_paid'] = pay_fee - fee_info['rate_used'] = tx_fee_rate - fee_info['witness_bytes'] = witness_bytes - fee_info['vsize'] = vsize + fee_info["fee_paid"] = pay_fee + fee_info["rate_used"] = tx_fee_rate + fee_info["witness_bytes"] = witness_bytes + fee_info["vsize"] = vsize tx.rehash() - self._log.info('createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) + self._log.info( + "createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.", + i2h(tx.sha256), + tx_fee_rate, + vsize, + pay_fee, + ) return tx.serialize() diff --git a/basicswap/interface/nmc.py b/basicswap/interface/nmc.py index b71e85b..cf9bae7 100644 --- a/basicswap/interface/nmc.py +++ b/basicswap/interface/nmc.py @@ -14,26 +14,38 @@ class NMCInterface(BTCInterface): def coin_type(): return Coins.NMC - def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False, vout: int = -1): - self._log.debug('[rm] scantxoutset start') # scantxoutset is slow - ro = self.rpc('scantxoutset', ['start', ['addr({})'.format(dest_address)]]) # TODO: Use combo(address) where possible - self._log.debug('[rm] scantxoutset end') + def getLockTxHeight( + self, + txid, + dest_address, + bid_amount, + rescan_from, + find_index: bool = False, + vout: int = -1, + ): + self._log.debug("[rm] scantxoutset start") # scantxoutset is slow + ro = self.rpc( + "scantxoutset", ["start", ["addr({})".format(dest_address)]] + ) # TODO: Use combo(address) where possible + self._log.debug("[rm] scantxoutset end") return_txid = True if txid is None else False - for o in ro['unspents']: - if txid and o['txid'] != txid.hex(): + for o in ro["unspents"]: + if txid and o["txid"] != txid.hex(): continue # Verify amount - if self.make_int(o['amount']) != int(bid_amount): - self._log.warning('Found output to lock tx address of incorrect value: %s, %s', str(o['amount']), o['txid']) + if self.make_int(o["amount"]) != int(bid_amount): + self._log.warning( + "Found output to lock tx address of incorrect value: %s, %s", + str(o["amount"]), + o["txid"], + ) continue - rv = { - 'depth': 0, - 'height': o['height']} - if o['height'] > 0: - rv['depth'] = ro['height'] - o['height'] + rv = {"depth": 0, "height": o["height"]} + if o["height"] > 0: + rv["depth"] = ro["height"] - o["height"] if find_index: - rv['index'] = o['vout'] + rv["index"] = o["vout"] if return_txid: - rv['txid'] = o['txid'] + rv["txid"] = o["txid"] return rv diff --git a/basicswap/interface/part.py b/basicswap/interface/part.py index 60b7495..8129343 100644 --- a/basicswap/interface/part.py +++ b/basicswap/interface/part.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020-2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -14,7 +15,10 @@ from basicswap.contrib.test_framework.messages import ( from basicswap.contrib.test_framework.script import ( CScript, OP_0, - OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG, + OP_DUP, + OP_HASH160, + OP_EQUALVERIFY, + OP_CHECKSIG, ) from basicswap.util import ( ensure, @@ -60,7 +64,7 @@ class PARTInterface(BTCInterface): @staticmethod def txVersion() -> int: - return 0xa0 + return 0xA0 @staticmethod def xmr_swap_a_lock_spend_tx_vsize() -> int: @@ -76,49 +80,60 @@ class PARTInterface(BTCInterface): def __init__(self, coin_settings, network, swap_client=None): super().__init__(coin_settings, network, swap_client) - self.setAnonTxRingSize(int(coin_settings.get('anon_tx_ring_size', 12))) + self.setAnonTxRingSize(int(coin_settings.get("anon_tx_ring_size", 12))) def use_tx_vsize(self) -> bool: return True def setAnonTxRingSize(self, value): - ensure(value >= 3 and value < 33, 'Invalid anon_tx_ring_size value') + ensure(value >= 3 and value < 33, "Invalid anon_tx_ring_size value") self._anon_tx_ring_size = value def knownWalletSeed(self): # TODO: Double check return True - def getNewAddress(self, use_segwit, label='swap_receive') -> str: - return self.rpc_wallet('getnewaddress', [label]) + def getNewAddress(self, use_segwit, label="swap_receive") -> str: + return self.rpc_wallet("getnewaddress", [label]) - def getNewStealthAddress(self, label='swap_stealth') -> str: - return self.rpc_wallet('getnewstealthaddress', [label]) + def getNewStealthAddress(self, label="swap_stealth") -> str: + return self.rpc_wallet("getnewstealthaddress", [label]) def haveSpentIndex(self): version = self.getDaemonVersion() - index_info = self.rpc('getinsightinfo' if int(str(version)[:2]) > 19 else 'getindexinfo') - return index_info['spentindex'] + index_info = self.rpc( + "getinsightinfo" if int(str(version)[:2]) > 19 else "getindexinfo" + ) + return index_info["spentindex"] def initialiseWallet(self, key: bytes) -> None: - raise ValueError('TODO') + raise ValueError("TODO") def withdrawCoin(self, value, addr_to, subfee): - params = [addr_to, value, '', '', subfee, '', True, self._conf_target] - return self.rpc_wallet('sendtoaddress', params) + params = [addr_to, value, "", "", subfee, "", True, self._conf_target] + return self.rpc_wallet("sendtoaddress", params) def sendTypeTo(self, type_from, type_to, value, addr_to, subfee): - params = [type_from, type_to, - [{'address': addr_to, 'amount': value, 'subfee': subfee}, ], - '', '', self._anon_tx_ring_size, 1, False, - {'conf_target': self._conf_target}] - return self.rpc_wallet('sendtypeto', params) + params = [ + type_from, + type_to, + [ + {"address": addr_to, "amount": value, "subfee": subfee}, + ], + "", + "", + self._anon_tx_ring_size, + 1, + False, + {"conf_target": self._conf_target}, + ] + return self.rpc_wallet("sendtypeto", params) def getScriptForPubkeyHash(self, pkh: bytes) -> CScript: return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]) def formatStealthAddress(self, scan_pubkey, spend_pubkey) -> str: - prefix_byte = chainparams[self.coin_type()][self._network]['stealth_key_prefix'] + prefix_byte = chainparams[self.coin_type()][self._network]["stealth_key_prefix"] return encodeStealthAddress(prefix_byte, scan_pubkey, spend_pubkey) @@ -129,34 +144,37 @@ class PARTInterface(BTCInterface): return length def getWalletRestoreHeight(self) -> int: - start_time = self.rpc_wallet('getwalletinfo')['keypoololdest'] + start_time = self.rpc_wallet("getwalletinfo")["keypoololdest"] blockchaininfo = self.getBlockchainInfo() - best_block = blockchaininfo['bestblockhash'] - chain_synced = round(blockchaininfo['verificationprogress'], 3) + chain_synced = round(blockchaininfo["verificationprogress"], 3) if chain_synced < 1.0: - raise ValueError('{} chain isn\'t synced.'.format(self.coin_name())) + raise ValueError("{} chain isn't synced.".format(self.coin_name())) - self._log.debug('Finding block at time: {}'.format(start_time)) - block_hash = self.rpc('getblockhashafter', [start_time]) - block_header = self.rpc('getblockheader', [block_hash]) - return block_header['height'] + self._log.debug("Finding block at time: {}".format(start_time)) + block_hash = self.rpc("getblockhashafter", [start_time]) + block_header = self.rpc("getblockheader", [block_hash]) + return block_header["height"] def getHTLCSpendTxVSize(self, redeem: bool = True) -> int: - tx_vsize = 5 # Add a few bytes, sequence in script takes variable amount of bytes + tx_vsize = ( + 5 # Add a few bytes, sequence in script takes variable amount of bytes + ) tx_vsize += 204 if redeem else 187 return tx_vsize def getUnspentsByAddr(self): unspent_addr = dict() - unspent = self.rpc_wallet('listunspent') + unspent = self.rpc_wallet("listunspent") for u in unspent: - if u['spendable'] is not True: + if u["spendable"] is not True: continue - if 'address' not in u: + if "address" not in u: continue - unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1) + unspent_addr[u["address"]] = unspent_addr.get( + u["address"], 0 + ) + self.make_int(u["amount"], r=1) return unspent_addr @@ -174,32 +192,41 @@ class PARTInterfaceBlind(PARTInterface): return 980 def coin_name(self) -> str: - return super().coin_name() + ' Blind' + return super().coin_name() + " Blind" def getScriptLockTxNonce(self, data): - return hashlib.sha256(data + bytes('locktx', 'utf-8')).digest() + return hashlib.sha256(data + bytes("locktx", "utf-8")).digest() def getScriptLockRefundTxNonce(self, data): - return hashlib.sha256(data + bytes('lockrefundtx', 'utf-8')).digest() + return hashlib.sha256(data + bytes("lockrefundtx", "utf-8")).digest() def findOutputByNonce(self, tx_obj, nonce): blinded_info = None output_n = None - for txo in tx_obj['vout']: - if txo['type'] != 'blind': + for txo in tx_obj["vout"]: + if txo["type"] != "blind": continue try: - blinded_info = self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()]) - output_n = txo['n'] + blinded_info = self.rpc( + "rewindrangeproof", + [txo["rangeproof"], txo["valueCommitment"], nonce.hex()], + ) + output_n = txo["n"] - self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()]) + self.rpc( + "rewindrangeproof", + [txo["rangeproof"], txo["valueCommitment"], nonce.hex()], + ) break except Exception as e: - self._log.debug('Searching for locked output: {}'.format(str(e))) + self._log.debug("Searching for locked output: {}".format(str(e))) continue # Should not be possible for commitment not to match - v = self.rpc('verifycommitment', [txo['valueCommitment'], blinded_info['blind'], blinded_info['amount']]) - ensure(v['result'] is True, 'verifycommitment failed') + v = self.rpc( + "verifycommitment", + [txo["valueCommitment"], blinded_info["blind"], blinded_info["amount"]], + ) + ensure(v["result"] is True, "verifycommitment failed") return output_n, blinded_info def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes) -> bytes: @@ -207,15 +234,23 @@ class PARTInterfaceBlind(PARTInterface): # Nonce is derived from vkbv, ephemeral_key isn't used ephemeral_key = self.getNewSecretKey() ephemeral_pubkey = self.getPubkey(ephemeral_key) - assert (len(ephemeral_pubkey) == 33) + assert len(ephemeral_pubkey) == 33 nonce = self.getScriptLockTxNonce(vkbv) p2wsh_addr = self.encode_p2wsh(getP2WSH(script)) inputs = [] - outputs = [{'type': 'blind', 'amount': self.format_amount(value), 'address': p2wsh_addr, 'nonce': nonce.hex(), 'data': ephemeral_pubkey.hex()}] + outputs = [ + { + "type": "blind", + "amount": self.format_amount(value), + "address": p2wsh_addr, + "nonce": nonce.hex(), + "data": ephemeral_pubkey.hex(), + } + ] params = [inputs, outputs] - rv = self.rpc_wallet('createrawparttransaction', params) + rv = self.rpc_wallet("createrawparttransaction", params) - tx_bytes = bytes.fromhex(rv['hex']) + tx_bytes = bytes.fromhex(rv["hex"]) return tx_bytes def fundSCLockTx(self, tx_bytes: bytes, feerate: int, vkbv: bytes) -> bytes: @@ -225,45 +260,80 @@ class PARTInterfaceBlind(PARTInterface): tx_hex = tx_bytes.hex() nonce = self.getScriptLockTxNonce(vkbv) - tx_obj = self.rpc('decoderawtransaction', [tx_hex]) + tx_obj = self.rpc("decoderawtransaction", [tx_hex]) - assert (len(tx_obj['vout']) == 1) - txo = tx_obj['vout'][0] - blinded_info = self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()]) + assert len(tx_obj["vout"]) == 1 + txo = tx_obj["vout"][0] + blinded_info = self.rpc( + "rewindrangeproof", [txo["rangeproof"], txo["valueCommitment"], nonce.hex()] + ) - outputs_info = {0: {'value': blinded_info['amount'], 'blind': blinded_info['blind'], 'nonce': nonce.hex()}} + outputs_info = { + 0: { + "value": blinded_info["amount"], + "blind": blinded_info["blind"], + "nonce": nonce.hex(), + } + } options = { - 'lockUnspents': True, - 'feeRate': feerate_str, + "lockUnspents": True, + "feeRate": feerate_str, } - rv = self.rpc('fundrawtransactionfrom', ['blind', tx_hex, {}, outputs_info, options]) - return bytes.fromhex(rv['hex']) + rv = self.rpc( + "fundrawtransactionfrom", ["blind", tx_hex, {}, outputs_info, options] + ) + return bytes.fromhex(rv["hex"]) - def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv): - lock_tx_obj = self.rpc('decoderawtransaction', [tx_lock_bytes.hex()]) - assert (self.getTxid(tx_lock_bytes).hex() == lock_tx_obj['txid']) + def createSCLockRefundTx( + self, + tx_lock_bytes, + script_lock, + Kal, + Kaf, + lock1_value, + csv_val, + tx_fee_rate, + vkbv, + ): + lock_tx_obj = self.rpc("decoderawtransaction", [tx_lock_bytes.hex()]) + assert self.getTxid(tx_lock_bytes).hex() == lock_tx_obj["txid"] # Nonce is derived from vkbv, ephemeral_key isn't used ephemeral_key = self.getNewSecretKey() ephemeral_pubkey = self.getPubkey(ephemeral_key) - assert (len(ephemeral_pubkey) == 33) + assert len(ephemeral_pubkey) == 33 nonce = self.getScriptLockTxNonce(vkbv) output_nonce = self.getScriptLockRefundTxNonce(vkbv) # Find the output of the lock tx to spend spend_n, input_blinded_info = self.findOutputByNonce(lock_tx_obj, nonce) - ensure(spend_n is not None, 'Output not found in tx') + ensure(spend_n is not None, "Output not found in tx") - locked_coin = input_blinded_info['amount'] - tx_lock_id = lock_tx_obj['txid'] + locked_coin = input_blinded_info["amount"] + tx_lock_id = lock_tx_obj["txid"] refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val) p2wsh_addr = self.encode_p2wsh(getP2WSH(refund_script)) - inputs = [{'txid': tx_lock_id, 'vout': spend_n, 'sequence': lock1_value, 'blindingfactor': input_blinded_info['blind']}] - outputs = [{'type': 'blind', 'amount': locked_coin, 'address': p2wsh_addr, 'nonce': output_nonce.hex(), 'data': ephemeral_pubkey.hex()}] + inputs = [ + { + "txid": tx_lock_id, + "vout": spend_n, + "sequence": lock1_value, + "blindingfactor": input_blinded_info["blind"], + } + ] + outputs = [ + { + "type": "blind", + "amount": locked_coin, + "address": p2wsh_addr, + "nonce": output_nonce.hex(), + "data": ephemeral_pubkey.hex(), + } + ] params = [inputs, outputs] - rv = self.rpc_wallet('createrawparttransaction', params) - lock_refund_tx_hex = rv['hex'] + rv = self.rpc_wallet("createrawparttransaction", params) + lock_refund_tx_hex = rv["hex"] # Set dummy witness data for fee estimation dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock) @@ -272,250 +342,396 @@ class PARTInterfaceBlind(PARTInterface): # Use a junk change pubkey to avoid adding unused keys to the wallet zero_change_key = self.getNewSecretKey() zero_change_pubkey = self.getPubkey(zero_change_key) - inputs_info = {'0': {'value': input_blinded_info['amount'], 'blind': input_blinded_info['blind'], 'witnessstack': dummy_witness_stack}} - outputs_info = rv['amounts'] - options = { - 'changepubkey': zero_change_pubkey.hex(), - 'feeRate': self.format_amount(tx_fee_rate), - 'subtractFeeFromOutputs': [0, ] + inputs_info = { + "0": { + "value": input_blinded_info["amount"], + "blind": input_blinded_info["blind"], + "witnessstack": dummy_witness_stack, + } } - rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_refund_tx_hex, inputs_info, outputs_info, options]) - lock_refund_tx_hex = rv['hex'] + outputs_info = rv["amounts"] + options = { + "changepubkey": zero_change_pubkey.hex(), + "feeRate": self.format_amount(tx_fee_rate), + "subtractFeeFromOutputs": [ + 0, + ], + } + rv = self.rpc_wallet( + "fundrawtransactionfrom", + ["blind", lock_refund_tx_hex, inputs_info, outputs_info, options], + ) + lock_refund_tx_hex = rv["hex"] - for vout, txo in rv['output_amounts'].items(): - if txo['value'] > 0: - refunded_value = txo['value'] + for vout, txo in rv["output_amounts"].items(): + if txo["value"] > 0: + refunded_value = txo["value"] return bytes.fromhex(lock_refund_tx_hex), refund_script, refunded_value - def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv): + def createSCLockRefundSpendTx( + self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv + ): # Returns the coinA locked coin to the leader # The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey # If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower - lock_refund_tx_obj = self.rpc('decoderawtransaction', [tx_lock_refund_bytes.hex()]) + lock_refund_tx_obj = self.rpc( + "decoderawtransaction", [tx_lock_refund_bytes.hex()] + ) # Nonce is derived from vkbv nonce = self.getScriptLockRefundTxNonce(vkbv) # Find the output of the lock refund tx to spend spend_n, input_blinded_info = self.findOutputByNonce(lock_refund_tx_obj, nonce) - ensure(spend_n is not None, 'Output not found in tx') + ensure(spend_n is not None, "Output not found in tx") - tx_lock_refund_id = lock_refund_tx_obj['txid'] + tx_lock_refund_id = lock_refund_tx_obj["txid"] addr_out = self.pkh_to_address(pkh_refund_to) - addr_info = self.rpc_wallet('getaddressinfo', [addr_out]) - output_pubkey_hex = addr_info['pubkey'] + addr_info = self.rpc_wallet("getaddressinfo", [addr_out]) + output_pubkey_hex = addr_info["pubkey"] # Follower won't be able to decode output to check amount, shouldn't matter as fee is public and output is to leader, sum has to balance - inputs = [{'txid': tx_lock_refund_id, 'vout': spend_n, 'sequence': 0, 'blindingfactor': input_blinded_info['blind']}] - outputs = [{'type': 'blind', 'amount': input_blinded_info['amount'], 'address': addr_out, 'pubkey': output_pubkey_hex}] + inputs = [ + { + "txid": tx_lock_refund_id, + "vout": spend_n, + "sequence": 0, + "blindingfactor": input_blinded_info["blind"], + } + ] + outputs = [ + { + "type": "blind", + "amount": input_blinded_info["amount"], + "address": addr_out, + "pubkey": output_pubkey_hex, + } + ] params = [inputs, outputs] - rv = self.rpc_wallet('createrawparttransaction', params) - lock_refund_spend_tx_hex = rv['hex'] + rv = self.rpc_wallet("createrawparttransaction", params) + lock_refund_spend_tx_hex = rv["hex"] # Set dummy witness data for fee estimation - dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(script_lock_refund) + dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness( + script_lock_refund + ) dummy_witness_stack = [x.hex() for x in dummy_witness_stack] # Use a junk change pubkey to avoid adding unused keys to the wallet zero_change_key = self.getNewSecretKey() zero_change_pubkey = self.getPubkey(zero_change_key) - inputs_info = {'0': {'value': input_blinded_info['amount'], 'blind': input_blinded_info['blind'], 'witnessstack': dummy_witness_stack}} - outputs_info = rv['amounts'] + inputs_info = { + "0": { + "value": input_blinded_info["amount"], + "blind": input_blinded_info["blind"], + "witnessstack": dummy_witness_stack, + } + } + outputs_info = rv["amounts"] options = { - 'changepubkey': zero_change_pubkey.hex(), - 'feeRate': self.format_amount(tx_fee_rate), - 'subtractFeeFromOutputs': [0, ] + "changepubkey": zero_change_pubkey.hex(), + "feeRate": self.format_amount(tx_fee_rate), + "subtractFeeFromOutputs": [ + 0, + ], } - rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_refund_spend_tx_hex, inputs_info, outputs_info, options]) - lock_refund_spend_tx_hex = rv['hex'] + rv = self.rpc_wallet( + "fundrawtransactionfrom", + ["blind", lock_refund_spend_tx_hex, inputs_info, outputs_info, options], + ) + lock_refund_spend_tx_hex = rv["hex"] return bytes.fromhex(lock_refund_spend_tx_hex) - def verifySCLockTx(self, tx_bytes, script_out, - swap_value, - Kal, Kaf, - feerate, - check_lock_tx_inputs, vkbv): - lock_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()]) - lock_txid_hex = lock_tx_obj['txid'] - self._log.info('Verifying lock tx: {}.'.format(lock_txid_hex)) + def verifySCLockTx( + self, + tx_bytes, + script_out, + swap_value, + Kal, + Kaf, + feerate, + check_lock_tx_inputs, + vkbv, + ): + lock_tx_obj = self.rpc("decoderawtransaction", [tx_bytes.hex()]) + lock_txid_hex = lock_tx_obj["txid"] + self._log.info("Verifying lock tx: {}.".format(lock_txid_hex)) - ensure(lock_tx_obj['version'] == self.txVersion(), 'Bad version') - ensure(lock_tx_obj['locktime'] == 0, 'Bad nLockTime') + ensure(lock_tx_obj["version"] == self.txVersion(), "Bad version") + ensure(lock_tx_obj["locktime"] == 0, "Bad nLockTime") # Find the output of the lock tx to verify nonce = self.getScriptLockTxNonce(vkbv) lock_output_n, blinded_info = self.findOutputByNonce(lock_tx_obj, nonce) - ensure(lock_output_n is not None, 'Output not found in tx') + ensure(lock_output_n is not None, "Output not found in tx") # Check value - locked_txo_value = self.make_int(blinded_info['amount']) - ensure(locked_txo_value == swap_value, 'Bad locked value') + locked_txo_value = self.make_int(blinded_info["amount"]) + ensure(locked_txo_value == swap_value, "Bad locked value") # Check script - lock_txo_scriptpk = bytes.fromhex(lock_tx_obj['vout'][lock_output_n]['scriptPubKey']['hex']) + lock_txo_scriptpk = bytes.fromhex( + lock_tx_obj["vout"][lock_output_n]["scriptPubKey"]["hex"] + ) script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()]) - ensure(lock_txo_scriptpk == script_pk, 'Bad output script') + ensure(lock_txo_scriptpk == script_pk, "Bad output script") A, B = extractScriptLockScriptValues(script_out) - ensure(A == Kal, 'Bad script leader pubkey') - ensure(B == Kaf, 'Bad script follower pubkey') + ensure(A == Kal, "Bad script leader pubkey") + ensure(B == Kaf, "Bad script follower pubkey") # TODO: Check that inputs are unspent, rangeproofs and commitments sum # Verify fee rate - vsize = lock_tx_obj['vsize'] - fee_paid = self.make_int(lock_tx_obj['vout'][0]['ct_fee']) + vsize = lock_tx_obj["vsize"] + fee_paid = self.make_int(lock_tx_obj["vout"][0]["ct_fee"]) fee_rate_paid = fee_paid * 1000 // vsize - self._log.info('tx amount, vsize, feerate: %ld, %ld, %ld', locked_txo_value, vsize, fee_rate_paid) + self._log.info( + "tx amount, vsize, feerate: %ld, %ld, %ld", + locked_txo_value, + vsize, + fee_rate_paid, + ) if not self.compareFeeRates(fee_rate_paid, feerate): - self._log.warning('feerate paid doesn\'t match expected: %ld, %ld', fee_rate_paid, feerate) + self._log.warning( + "feerate paid doesn't match expected: %ld, %ld", fee_rate_paid, feerate + ) # TODO: Display warning to user return bytes.fromhex(lock_txid_hex), lock_output_n - def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out, - prevout_id, prevout_n, prevout_seq, prevout_script, - Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv): - lock_refund_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()]) - lock_refund_txid_hex = lock_refund_tx_obj['txid'] - self._log.info('Verifying lock refund tx: {}.'.format(lock_refund_txid_hex)) + def verifySCLockRefundTx( + self, + tx_bytes, + lock_tx_bytes, + script_out, + prevout_id, + prevout_n, + prevout_seq, + prevout_script, + Kal, + Kaf, + csv_val_expect, + swap_value, + feerate, + vkbv, + ): + lock_refund_tx_obj = self.rpc("decoderawtransaction", [tx_bytes.hex()]) + lock_refund_txid_hex = lock_refund_tx_obj["txid"] + self._log.info("Verifying lock refund tx: {}.".format(lock_refund_txid_hex)) - ensure(lock_refund_tx_obj['version'] == self.txVersion(), 'Bad version') - ensure(lock_refund_tx_obj['locktime'] == 0, 'Bad nLockTime') - ensure(len(lock_refund_tx_obj['vin']) == 1, 'tx doesn\'t have one input') + ensure(lock_refund_tx_obj["version"] == self.txVersion(), "Bad version") + ensure(lock_refund_tx_obj["locktime"] == 0, "Bad nLockTime") + ensure(len(lock_refund_tx_obj["vin"]) == 1, "tx doesn't have one input") - txin = lock_refund_tx_obj['vin'][0] - ensure(txin['sequence'] == prevout_seq, 'Bad input nSequence') - ensure(txin['scriptSig']['hex'] == '', 'Input scriptsig not empty') - ensure(txin['txid'] == prevout_id.hex() and txin['vout'] == prevout_n, 'Input prevout mismatch') + txin = lock_refund_tx_obj["vin"][0] + ensure(txin["sequence"] == prevout_seq, "Bad input nSequence") + ensure(txin["scriptSig"]["hex"] == "", "Input scriptsig not empty") + ensure( + txin["txid"] == prevout_id.hex() and txin["vout"] == prevout_n, + "Input prevout mismatch", + ) - ensure(len(lock_refund_tx_obj['vout']) == 3, 'tx doesn\'t have three outputs') + ensure(len(lock_refund_tx_obj["vout"]) == 3, "tx doesn't have three outputs") # Find the output of the lock refund tx to verify nonce = self.getScriptLockRefundTxNonce(vkbv) - lock_refund_output_n, blinded_info = self.findOutputByNonce(lock_refund_tx_obj, nonce) - ensure(lock_refund_output_n is not None, 'Output not found in tx') + lock_refund_output_n, blinded_info = self.findOutputByNonce( + lock_refund_tx_obj, nonce + ) + ensure(lock_refund_output_n is not None, "Output not found in tx") - lock_refund_txo_value = self.make_int(blinded_info['amount']) + lock_refund_txo_value = self.make_int(blinded_info["amount"]) # Check script - lock_refund_txo_scriptpk = bytes.fromhex(lock_refund_tx_obj['vout'][lock_refund_output_n]['scriptPubKey']['hex']) + lock_refund_txo_scriptpk = bytes.fromhex( + lock_refund_tx_obj["vout"][lock_refund_output_n]["scriptPubKey"]["hex"] + ) script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()]) - ensure(lock_refund_txo_scriptpk == script_pk, 'Bad output script') + ensure(lock_refund_txo_scriptpk == script_pk, "Bad output script") A, B, csv_val, C = extractScriptLockRefundScriptValues(script_out) - ensure(A == Kal, 'Bad script pubkey') - ensure(B == Kaf, 'Bad script pubkey') - ensure(csv_val == csv_val_expect, 'Bad script csv value') - ensure(C == Kaf, 'Bad script pubkey') + ensure(A == Kal, "Bad script pubkey") + ensure(B == Kaf, "Bad script pubkey") + ensure(csv_val == csv_val_expect, "Bad script csv value") + ensure(C == Kaf, "Bad script pubkey") # Check rangeproofs and commitments sum - lock_tx_obj = self.rpc('decoderawtransaction', [lock_tx_bytes.hex()]) - prevout = lock_tx_obj['vout'][prevout_n] - prevtxns = [{'txid': prevout_id.hex(), 'vout': prevout_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}] - rv = self.rpc('verifyrawtransaction', [tx_bytes.hex(), prevtxns]) - ensure(rv['outputs_valid'] is True, 'Invalid outputs') - ensure(rv['inputs_valid'] is True, 'Invalid inputs') + lock_tx_obj = self.rpc("decoderawtransaction", [lock_tx_bytes.hex()]) + prevout = lock_tx_obj["vout"][prevout_n] + prevtxns = [ + { + "txid": prevout_id.hex(), + "vout": prevout_n, + "scriptPubKey": prevout["scriptPubKey"]["hex"], + "amount_commitment": prevout["valueCommitment"], + } + ] + rv = self.rpc("verifyrawtransaction", [tx_bytes.hex(), prevtxns]) + ensure(rv["outputs_valid"] is True, "Invalid outputs") + ensure(rv["inputs_valid"] is True, "Invalid inputs") # Check value - fee_paid = self.make_int(lock_refund_tx_obj['vout'][0]['ct_fee']) - ensure(swap_value - lock_refund_txo_value == fee_paid, 'Bad output value') + fee_paid = self.make_int(lock_refund_tx_obj["vout"][0]["ct_fee"]) + ensure(swap_value - lock_refund_txo_value == fee_paid, "Bad output value") # Check fee rate dummy_witness_stack = self.getScriptLockTxDummyWitness(prevout_script) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) vsize = self.getTxVSize(self.loadTx(tx_bytes), add_witness_bytes=witness_bytes) fee_rate_paid = fee_paid * 1000 // vsize - self._log.info('vsize, feerate: %ld, %ld', vsize, fee_rate_paid) + self._log.info("vsize, feerate: %ld, %ld", vsize, fee_rate_paid) - ensure(self.compareFeeRates(fee_rate_paid, feerate), 'Bad fee rate, expected: {}'.format(feerate)) + ensure( + self.compareFeeRates(fee_rate_paid, feerate), + "Bad fee rate, expected: {}".format(feerate), + ) - return bytes.fromhex(lock_refund_txid_hex), lock_refund_txo_value, lock_refund_output_n + return ( + bytes.fromhex(lock_refund_txid_hex), + lock_refund_txo_value, + lock_refund_output_n, + ) - def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes, - lock_refund_tx_id, prevout_script, - Kal, - prevout_n, prevout_value, feerate, vkbv): - lock_refund_spend_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()]) - lock_refund_spend_txid_hex = lock_refund_spend_tx_obj['txid'] - self._log.info('Verifying lock refund spend tx: {}.'.format(lock_refund_spend_txid_hex)) + def verifySCLockRefundSpendTx( + self, + tx_bytes, + lock_refund_tx_bytes, + lock_refund_tx_id, + prevout_script, + Kal, + prevout_n, + prevout_value, + feerate, + vkbv, + ): + lock_refund_spend_tx_obj = self.rpc("decoderawtransaction", [tx_bytes.hex()]) + lock_refund_spend_txid_hex = lock_refund_spend_tx_obj["txid"] + self._log.info( + "Verifying lock refund spend tx: {}.".format(lock_refund_spend_txid_hex) + ) - ensure(lock_refund_spend_tx_obj['version'] == self.txVersion(), 'Bad version') - ensure(lock_refund_spend_tx_obj['locktime'] == 0, 'Bad nLockTime') - ensure(len(lock_refund_spend_tx_obj['vin']) == 1, 'tx doesn\'t have one input') + ensure(lock_refund_spend_tx_obj["version"] == self.txVersion(), "Bad version") + ensure(lock_refund_spend_tx_obj["locktime"] == 0, "Bad nLockTime") + ensure(len(lock_refund_spend_tx_obj["vin"]) == 1, "tx doesn't have one input") - txin = lock_refund_spend_tx_obj['vin'][0] - ensure(txin['sequence'] == 0, 'Bad input nSequence') - ensure(txin['scriptSig']['hex'] == '', 'Input scriptsig not empty') - ensure(txin['txid'] == lock_refund_tx_id.hex() and txin['vout'] == prevout_n, 'Input prevout mismatch') + txin = lock_refund_spend_tx_obj["vin"][0] + ensure(txin["sequence"] == 0, "Bad input nSequence") + ensure(txin["scriptSig"]["hex"] == "", "Input scriptsig not empty") + ensure( + txin["txid"] == lock_refund_tx_id.hex() and txin["vout"] == prevout_n, + "Input prevout mismatch", + ) - ensure(len(lock_refund_spend_tx_obj['vout']) == 3, 'tx doesn\'t have three outputs') + ensure( + len(lock_refund_spend_tx_obj["vout"]) == 3, "tx doesn't have three outputs" + ) # Leader picks output destinations # Follower is not concerned with them as they pay to leader # Check rangeproofs and commitments sum - lock_refund_tx_obj = self.rpc('decoderawtransaction', [lock_refund_tx_bytes.hex()]) - prevout = lock_refund_tx_obj['vout'][prevout_n] - prevtxns = [{'txid': lock_refund_tx_id.hex(), 'vout': prevout_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}] - rv = self.rpc('verifyrawtransaction', [tx_bytes.hex(), prevtxns]) - ensure(rv['outputs_valid'] is True, 'Invalid outputs') - ensure(rv['inputs_valid'] is True, 'Invalid inputs') + lock_refund_tx_obj = self.rpc( + "decoderawtransaction", [lock_refund_tx_bytes.hex()] + ) + prevout = lock_refund_tx_obj["vout"][prevout_n] + prevtxns = [ + { + "txid": lock_refund_tx_id.hex(), + "vout": prevout_n, + "scriptPubKey": prevout["scriptPubKey"]["hex"], + "amount_commitment": prevout["valueCommitment"], + } + ] + rv = self.rpc("verifyrawtransaction", [tx_bytes.hex(), prevtxns]) + ensure(rv["outputs_valid"] is True, "Invalid outputs") + ensure(rv["inputs_valid"] is True, "Invalid inputs") # Check fee rate - dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(prevout_script) + dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness( + prevout_script + ) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) vsize = self.getTxVSize(self.loadTx(tx_bytes), add_witness_bytes=witness_bytes) - fee_paid = self.make_int(lock_refund_spend_tx_obj['vout'][0]['ct_fee']) + fee_paid = self.make_int(lock_refund_spend_tx_obj["vout"][0]["ct_fee"]) fee_rate_paid = fee_paid * 1000 // vsize - ensure(self.compareFeeRates(fee_rate_paid, feerate), 'Bad fee rate, expected: {}'.format(feerate)) + ensure( + self.compareFeeRates(fee_rate_paid, feerate), + "Bad fee rate, expected: {}".format(feerate), + ) return True def getLockTxSwapOutputValue(self, bid, xmr_swap): - lock_tx_obj = self.rpc('decoderawtransaction', [xmr_swap.a_lock_tx.hex()]) + lock_tx_obj = self.rpc("decoderawtransaction", [xmr_swap.a_lock_tx.hex()]) nonce = self.getScriptLockTxNonce(xmr_swap.vkbv) output_n, _ = self.findOutputByNonce(lock_tx_obj, nonce) - ensure(output_n is not None, 'Output not found in tx') - return bytes.fromhex(lock_tx_obj['vout'][output_n]['valueCommitment']) + ensure(output_n is not None, "Output not found in tx") + return bytes.fromhex(lock_tx_obj["vout"][output_n]["valueCommitment"]) def getLockRefundTxSwapOutputValue(self, bid, xmr_swap): - lock_refund_tx_obj = self.rpc('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()]) + lock_refund_tx_obj = self.rpc( + "decoderawtransaction", [xmr_swap.a_lock_refund_tx.hex()] + ) nonce = self.getScriptLockRefundTxNonce(xmr_swap.vkbv) output_n, _ = self.findOutputByNonce(lock_refund_tx_obj, nonce) - ensure(output_n is not None, 'Output not found in tx') - return bytes.fromhex(lock_refund_tx_obj['vout'][output_n]['valueCommitment']) + ensure(output_n is not None, "Output not found in tx") + return bytes.fromhex(lock_refund_tx_obj["vout"][output_n]["valueCommitment"]) def getLockRefundTxSwapOutput(self, xmr_swap): - lock_refund_tx_obj = self.rpc('decoderawtransaction', [xmr_swap.a_lock_refund_tx.hex()]) + lock_refund_tx_obj = self.rpc( + "decoderawtransaction", [xmr_swap.a_lock_refund_tx.hex()] + ) nonce = self.getScriptLockRefundTxNonce(xmr_swap.vkbv) output_n, _ = self.findOutputByNonce(lock_refund_tx_obj, nonce) - ensure(output_n is not None, 'Output not found in tx') + ensure(output_n is not None, "Output not found in tx") return output_n - def createSCLockSpendTx(self, tx_lock_bytes: bytes, script_lock: bytes, pk_dest: bytes, tx_fee_rate: int, vkbv: bytes, fee_info={}) -> bytes: - lock_tx_obj = self.rpc('decoderawtransaction', [tx_lock_bytes.hex()]) - lock_txid_hex = lock_tx_obj['txid'] + def createSCLockSpendTx( + self, + tx_lock_bytes: bytes, + script_lock: bytes, + pk_dest: bytes, + tx_fee_rate: int, + vkbv: bytes, + fee_info={}, + ) -> bytes: + lock_tx_obj = self.rpc("decoderawtransaction", [tx_lock_bytes.hex()]) + lock_txid_hex = lock_tx_obj["txid"] - ensure(lock_tx_obj['version'] == self.txVersion(), 'Bad version') - ensure(lock_tx_obj['locktime'] == 0, 'Bad nLockTime') + ensure(lock_tx_obj["version"] == self.txVersion(), "Bad version") + ensure(lock_tx_obj["locktime"] == 0, "Bad nLockTime") # Find the output of the lock tx to verify nonce = self.getScriptLockTxNonce(vkbv) spend_n, blinded_info = self.findOutputByNonce(lock_tx_obj, nonce) - ensure(spend_n is not None, 'Output not found in tx') + ensure(spend_n is not None, "Output not found in tx") addr_out = self.pubkey_to_address(pk_dest) - inputs = [{'txid': lock_txid_hex, 'vout': spend_n, 'sequence': 0, 'blindingfactor': blinded_info['blind']}] - outputs = [{'type': 'blind', 'amount': blinded_info['amount'], 'address': addr_out, 'pubkey': pk_dest.hex()}] + inputs = [ + { + "txid": lock_txid_hex, + "vout": spend_n, + "sequence": 0, + "blindingfactor": blinded_info["blind"], + } + ] + outputs = [ + { + "type": "blind", + "amount": blinded_info["amount"], + "address": addr_out, + "pubkey": pk_dest.hex(), + } + ] params = [inputs, outputs] - rv = self.rpc_wallet('createrawparttransaction', params) - lock_spend_tx_hex = rv['hex'] + rv = self.rpc_wallet("createrawparttransaction", params) + lock_spend_tx_hex = rv["hex"] # Set dummy witness data for fee estimation dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock) @@ -523,92 +739,138 @@ class PARTInterfaceBlind(PARTInterface): # Use a junk change pubkey to avoid adding unused keys to the wallet zero_change_key = self.getNewSecretKey() zero_change_pubkey = self.getPubkey(zero_change_key) - inputs_info = {'0': {'value': blinded_info['amount'], 'blind': blinded_info['blind'], 'witnessstack': [x.hex() for x in dummy_witness_stack]}} - outputs_info = rv['amounts'] + inputs_info = { + "0": { + "value": blinded_info["amount"], + "blind": blinded_info["blind"], + "witnessstack": [x.hex() for x in dummy_witness_stack], + } + } + outputs_info = rv["amounts"] options = { - 'changepubkey': zero_change_pubkey.hex(), - 'feeRate': self.format_amount(tx_fee_rate), - 'subtractFeeFromOutputs': [0, ] + "changepubkey": zero_change_pubkey.hex(), + "feeRate": self.format_amount(tx_fee_rate), + "subtractFeeFromOutputs": [ + 0, + ], } - rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_spend_tx_hex, inputs_info, outputs_info, options]) - lock_spend_tx_hex = rv['hex'] - lock_spend_tx_obj = self.rpc('decoderawtransaction', [lock_spend_tx_hex]) - pay_fee = self.make_int(lock_spend_tx_obj['vout'][0]['ct_fee']) + rv = self.rpc_wallet( + "fundrawtransactionfrom", + ["blind", lock_spend_tx_hex, inputs_info, outputs_info, options], + ) + lock_spend_tx_hex = rv["hex"] + lock_spend_tx_obj = self.rpc("decoderawtransaction", [lock_spend_tx_hex]) + pay_fee = self.make_int(lock_spend_tx_obj["vout"][0]["ct_fee"]) # lock_spend_tx_hex does not include the dummy witness stack witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) - vsize = self.getTxVSize(self.loadTx(bytes.fromhex(lock_spend_tx_hex)), add_witness_bytes=witness_bytes) + vsize = self.getTxVSize( + self.loadTx(bytes.fromhex(lock_spend_tx_hex)), + add_witness_bytes=witness_bytes, + ) actual_tx_fee_rate = pay_fee * 1000 // vsize - self._log.info('createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - lock_spend_tx_obj['txid'], actual_tx_fee_rate, vsize, pay_fee) + self._log.info( + "createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.", + lock_spend_tx_obj["txid"], + actual_tx_fee_rate, + vsize, + pay_fee, + ) - fee_info['vsize'] = vsize - fee_info['fee_paid'] = pay_fee - fee_info['rate_input'] = tx_fee_rate - fee_info['rate_actual'] = actual_tx_fee_rate + fee_info["vsize"] = vsize + fee_info["fee_paid"] = pay_fee + fee_info["rate_input"] = tx_fee_rate + fee_info["rate_actual"] = actual_tx_fee_rate return bytes.fromhex(lock_spend_tx_hex) - def verifySCLockSpendTx(self, tx_bytes, - lock_tx_bytes, lock_tx_script, - a_pk_f, feerate, vkbv): - lock_spend_tx_obj = self.rpc('decoderawtransaction', [tx_bytes.hex()]) - lock_spend_txid_hex = lock_spend_tx_obj['txid'] - self._log.info('Verifying lock spend tx: {}.'.format(lock_spend_txid_hex)) + def verifySCLockSpendTx( + self, tx_bytes, lock_tx_bytes, lock_tx_script, a_pk_f, feerate, vkbv + ): + lock_spend_tx_obj = self.rpc("decoderawtransaction", [tx_bytes.hex()]) + lock_spend_txid_hex = lock_spend_tx_obj["txid"] + self._log.info("Verifying lock spend tx: {}.".format(lock_spend_txid_hex)) - ensure(lock_spend_tx_obj['version'] == self.txVersion(), 'Bad version') - ensure(lock_spend_tx_obj['locktime'] == 0, 'Bad nLockTime') - ensure(len(lock_spend_tx_obj['vin']) == 1, 'tx doesn\'t have one input') + ensure(lock_spend_tx_obj["version"] == self.txVersion(), "Bad version") + ensure(lock_spend_tx_obj["locktime"] == 0, "Bad nLockTime") + ensure(len(lock_spend_tx_obj["vin"]) == 1, "tx doesn't have one input") - lock_tx_obj = self.rpc('decoderawtransaction', [lock_tx_bytes.hex()]) - lock_txid_hex = lock_tx_obj['txid'] + lock_tx_obj = self.rpc("decoderawtransaction", [lock_tx_bytes.hex()]) + lock_txid_hex = lock_tx_obj["txid"] # Find the output of the lock tx to verify nonce = self.getScriptLockTxNonce(vkbv) spend_n, input_blinded_info = self.findOutputByNonce(lock_tx_obj, nonce) - ensure(spend_n is not None, 'Output not found in tx') + ensure(spend_n is not None, "Output not found in tx") - txin = lock_spend_tx_obj['vin'][0] - ensure(txin['sequence'] == 0, 'Bad input nSequence') - ensure(txin['scriptSig']['hex'] == '', 'Input scriptsig not empty') - ensure(txin['txid'] == lock_txid_hex and txin['vout'] == spend_n, 'Input prevout mismatch') + txin = lock_spend_tx_obj["vin"][0] + ensure(txin["sequence"] == 0, "Bad input nSequence") + ensure(txin["scriptSig"]["hex"] == "", "Input scriptsig not empty") + ensure( + txin["txid"] == lock_txid_hex and txin["vout"] == spend_n, + "Input prevout mismatch", + ) - ensure(len(lock_spend_tx_obj['vout']) == 3, 'tx doesn\'t have three outputs') + ensure(len(lock_spend_tx_obj["vout"]) == 3, "tx doesn't have three outputs") addr_out = self.pubkey_to_address(a_pk_f) - privkey = self.rpc_wallet('dumpprivkey', [addr_out]) + privkey = self.rpc_wallet("dumpprivkey", [addr_out]) # Find output: output_blinded_info = None output_n = None - for txo in lock_spend_tx_obj['vout']: - if txo['type'] != 'blind': + for txo in lock_spend_tx_obj["vout"]: + if txo["type"] != "blind": continue try: - output_blinded_info = self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], privkey, txo['data_hex']]) - output_n = txo['n'] + output_blinded_info = self.rpc( + "rewindrangeproof", + [ + txo["rangeproof"], + txo["valueCommitment"], + privkey, + txo["data_hex"], + ], + ) + output_n = txo["n"] break except Exception as e: - self._log.debug('Searching for locked output: {}'.format(str(e))) + self._log.debug("Searching for locked output: {}".format(str(e))) pass - ensure(output_n is not None, 'Output not found in tx') + ensure(output_n is not None, "Output not found in tx") # Commitment - v = self.rpc('verifycommitment', [lock_spend_tx_obj['vout'][output_n]['valueCommitment'], output_blinded_info['blind'], output_blinded_info['amount']]) - ensure(v['result'] is True, 'verifycommitment failed') + v = self.rpc( + "verifycommitment", + [ + lock_spend_tx_obj["vout"][output_n]["valueCommitment"], + output_blinded_info["blind"], + output_blinded_info["amount"], + ], + ) + ensure(v["result"] is True, "verifycommitment failed") # Check rangeproofs and commitments sum - prevout = lock_tx_obj['vout'][spend_n] - prevtxns = [{'txid': lock_txid_hex, 'vout': spend_n, 'scriptPubKey': prevout['scriptPubKey']['hex'], 'amount_commitment': prevout['valueCommitment']}] - rv = self.rpc('verifyrawtransaction', [tx_bytes.hex(), prevtxns]) - ensure(rv['outputs_valid'] is True, 'Invalid outputs') - ensure(rv['inputs_valid'] is True, 'Invalid inputs') + prevout = lock_tx_obj["vout"][spend_n] + prevtxns = [ + { + "txid": lock_txid_hex, + "vout": spend_n, + "scriptPubKey": prevout["scriptPubKey"]["hex"], + "amount_commitment": prevout["valueCommitment"], + } + ] + rv = self.rpc("verifyrawtransaction", [tx_bytes.hex(), prevtxns]) + ensure(rv["outputs_valid"] is True, "Invalid outputs") + ensure(rv["inputs_valid"] is True, "Invalid inputs") # Check amount - fee_paid = self.make_int(lock_spend_tx_obj['vout'][0]['ct_fee']) - amount_difference = self.make_int(input_blinded_info['amount']) - self.make_int(output_blinded_info['amount']) - ensure(fee_paid == amount_difference, 'Invalid output amount') + fee_paid = self.make_int(lock_spend_tx_obj["vout"][0]["ct_fee"]) + amount_difference = self.make_int(input_blinded_info["amount"]) - self.make_int( + output_blinded_info["amount"] + ) + ensure(fee_paid == amount_difference, "Invalid output amount") # Check fee dummy_witness_stack = self.getScriptLockTxDummyWitness(lock_tx_script) @@ -616,76 +878,137 @@ class PARTInterfaceBlind(PARTInterface): vsize = self.getTxVSize(self.loadTx(tx_bytes), add_witness_bytes=witness_bytes) fee_rate_paid = fee_paid * 1000 // vsize - self._log.info('vsize, feerate: %ld, %ld', vsize, fee_rate_paid) + self._log.info("vsize, feerate: %ld, %ld", vsize, fee_rate_paid) if not self.compareFeeRates(fee_rate_paid, feerate): - raise ValueError('Bad fee rate, expected: {}'.format(feerate)) + raise ValueError("Bad fee rate, expected: {}".format(feerate)) return True - def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv, kbsf=None): + def createSCLockRefundSpendToFTx( + self, + tx_lock_refund_bytes, + script_lock_refund, + pkh_dest, + tx_fee_rate, + vkbv, + kbsf=None, + ): # lock refund swipe tx # Sends the coinA locked coin to the follower - lock_refund_tx_obj = self.rpc('decoderawtransaction', [tx_lock_refund_bytes.hex()]) + lock_refund_tx_obj = self.rpc( + "decoderawtransaction", [tx_lock_refund_bytes.hex()] + ) nonce = self.getScriptLockRefundTxNonce(vkbv) # Find the output of the lock refund tx to spend spend_n, input_blinded_info = self.findOutputByNonce(lock_refund_tx_obj, nonce) - ensure(spend_n is not None, 'Output not found in tx') + ensure(spend_n is not None, "Output not found in tx") - tx_lock_refund_id = lock_refund_tx_obj['txid'] + tx_lock_refund_id = lock_refund_tx_obj["txid"] addr_out = self.pkh_to_address(pkh_dest) - addr_info = self.rpc_wallet('getaddressinfo', [addr_out]) - output_pubkey_hex = addr_info['pubkey'] + addr_info = self.rpc_wallet("getaddressinfo", [addr_out]) + output_pubkey_hex = addr_info["pubkey"] A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund) # Follower won't be able to decode output to check amount, shouldn't matter as fee is public and output is to leader, sum has to balance - inputs = [{'txid': tx_lock_refund_id, 'vout': spend_n, 'sequence': lock2_value, 'blindingfactor': input_blinded_info['blind']}] - outputs = [{'type': 'blind', 'amount': input_blinded_info['amount'], 'address': addr_out, 'pubkey': output_pubkey_hex}] + inputs = [ + { + "txid": tx_lock_refund_id, + "vout": spend_n, + "sequence": lock2_value, + "blindingfactor": input_blinded_info["blind"], + } + ] + outputs = [ + { + "type": "blind", + "amount": input_blinded_info["amount"], + "address": addr_out, + "pubkey": output_pubkey_hex, + } + ] params = [inputs, outputs] - rv = self.rpc_wallet('createrawparttransaction', params) + rv = self.rpc_wallet("createrawparttransaction", params) - lock_refund_swipe_tx_hex = rv['hex'] + lock_refund_swipe_tx_hex = rv["hex"] # Set dummy witness data for fee estimation - dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness(script_lock_refund) + dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness( + script_lock_refund + ) dummy_witness_stack = [x.hex() for x in dummy_witness_stack] # Use a junk change pubkey to avoid adding unused keys to the wallet zero_change_key = self.getNewSecretKey() zero_change_pubkey = self.getPubkey(zero_change_key) - inputs_info = {'0': {'value': input_blinded_info['amount'], 'blind': input_blinded_info['blind'], 'witnessstack': dummy_witness_stack}} - outputs_info = rv['amounts'] + inputs_info = { + "0": { + "value": input_blinded_info["amount"], + "blind": input_blinded_info["blind"], + "witnessstack": dummy_witness_stack, + } + } + outputs_info = rv["amounts"] options = { - 'changepubkey': zero_change_pubkey.hex(), - 'feeRate': self.format_amount(tx_fee_rate), - 'subtractFeeFromOutputs': [0, ] + "changepubkey": zero_change_pubkey.hex(), + "feeRate": self.format_amount(tx_fee_rate), + "subtractFeeFromOutputs": [ + 0, + ], } - rv = self.rpc_wallet('fundrawtransactionfrom', ['blind', lock_refund_swipe_tx_hex, inputs_info, outputs_info, options]) - lock_refund_swipe_tx_hex = rv['hex'] + rv = self.rpc_wallet( + "fundrawtransactionfrom", + ["blind", lock_refund_swipe_tx_hex, inputs_info, outputs_info, options], + ) + lock_refund_swipe_tx_hex = rv["hex"] return bytes.fromhex(lock_refund_swipe_tx_hex) def getSpendableBalance(self) -> int: - return self.make_int(self.rpc_wallet('getbalances')['mine']['blind_trusted']) + return self.make_int(self.rpc_wallet("getbalances")["mine"]["blind_trusted"]) - def publishBLockTx(self, vkbv: bytes, Kbs: bytes, output_amount: int, feerate: int, unlock_time: int = 0) -> bytes: + def publishBLockTx( + self, + vkbv: bytes, + Kbs: bytes, + output_amount: int, + feerate: int, + unlock_time: int = 0, + ) -> bytes: Kbv = self.getPubkey(vkbv) sx_addr = self.formatStealthAddress(Kbv, Kbs) - self._log.debug('sx_addr: {}'.format(sx_addr)) + self._log.debug("sx_addr: {}".format(sx_addr)) # TODO: Fund from other balances - params = ['blind', 'blind', - [{'address': sx_addr, 'amount': self.format_amount(output_amount)}, ], - '', '', self._anon_tx_ring_size, 1, False, - {'conf_target': self._conf_target, 'blind_watchonly_visible': True}] + params = [ + "blind", + "blind", + [ + {"address": sx_addr, "amount": self.format_amount(output_amount)}, + ], + "", + "", + self._anon_tx_ring_size, + 1, + False, + {"conf_target": self._conf_target, "blind_watchonly_visible": True}, + ] - txid = self.rpc_wallet('sendtypeto', params) + txid = self.rpc_wallet("sendtypeto", params) return bytes.fromhex(txid) - def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height: int, bid_sender: bool): + def findTxB( + self, + kbv, + Kbs, + cb_swap_value, + cb_block_confirmed, + restore_height: int, + bid_sender: bool, + ): Kbv = self.getPubkey(kbv) sx_addr = self.formatStealthAddress(Kbv, Kbs) @@ -693,97 +1016,153 @@ class PARTInterfaceBlind(PARTInterface): if bid_sender: cb_swap_value *= -1 else: - addr_info = self.rpc_wallet('getaddressinfo', [sx_addr]) - if not addr_info['iswatchonly']: + addr_info = self.rpc_wallet("getaddressinfo", [sx_addr]) + if not addr_info["iswatchonly"]: wif_scan_key = self.encodeKey(kbv) - self.rpc_wallet('importstealthaddress', [wif_scan_key, Kbs.hex()]) - self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr)) - self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height)) - self.rpc_wallet('rescanblockchain', [restore_height]) + self.rpc_wallet("importstealthaddress", [wif_scan_key, Kbs.hex()]) + self._log.info("Imported watch-only sx_addr: {}".format(sx_addr)) + self._log.info( + "Rescanning {} chain from height: {}".format( + self.coin_name(), restore_height + ) + ) + self.rpc_wallet("rescanblockchain", [restore_height]) - params = [{'include_watchonly': True, 'search': sx_addr}] - txns = self.rpc_wallet('filtertransactions', params) + params = [{"include_watchonly": True, "search": sx_addr}] + txns = self.rpc_wallet("filtertransactions", params) if len(txns) == 1: tx = txns[0] - assert (tx['outputs'][0]['stealth_address'] == sx_addr) # Should not be possible - ensure(tx['outputs'][0]['type'] == 'blind', 'Output is not anon') + assert ( + tx["outputs"][0]["stealth_address"] == sx_addr + ) # Should not be possible + ensure(tx["outputs"][0]["type"] == "blind", "Output is not anon") - if self.make_int(tx['outputs'][0]['amount']) == cb_swap_value: + if self.make_int(tx["outputs"][0]["amount"]) == cb_swap_value: height = 0 - if tx['confirmations'] > 0: - chain_height = self.rpc('getblockcount') - height = chain_height - (tx['confirmations'] - 1) - return {'txid': tx['txid'], 'amount': cb_swap_value, 'height': height} + if tx["confirmations"] > 0: + chain_height = self.rpc("getblockcount") + height = chain_height - (tx["confirmations"] - 1) + return {"txid": tx["txid"], "amount": cb_swap_value, "height": height} else: - self._log.warning('Incorrect amount detected for coin b lock txn: {}'.format(tx['txid'])) + self._log.warning( + "Incorrect amount detected for coin b lock txn: {}".format( + tx["txid"] + ) + ) return -1 return None - def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes: + def spendBLockTx( + self, + chain_b_lock_txid: bytes, + address_to: str, + kbv: bytes, + kbs: bytes, + cb_swap_value: int, + b_fee: int, + restore_height: int, + spend_actual_balance: bool = False, + lock_tx_vout=None, + ) -> bytes: Kbv = self.getPubkey(kbv) Kbs = self.getPubkey(kbs) sx_addr = self.formatStealthAddress(Kbv, Kbs) - addr_info = self.rpc_wallet('getaddressinfo', [sx_addr]) - if not addr_info['ismine']: + addr_info = self.rpc_wallet("getaddressinfo", [sx_addr]) + if not addr_info["ismine"]: wif_scan_key = self.encodeKey(kbv) wif_spend_key = self.encodeKey(kbs) - self.rpc_wallet('importstealthaddress', [wif_scan_key, wif_spend_key]) - self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr)) - self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height)) - self.rpc_wallet('rescanblockchain', [restore_height]) + self.rpc_wallet("importstealthaddress", [wif_scan_key, wif_spend_key]) + self._log.info("Imported spend key for sx_addr: {}".format(sx_addr)) + self._log.info( + "Rescanning {} chain from height: {}".format( + self.coin_name(), restore_height + ) + ) + self.rpc_wallet("rescanblockchain", [restore_height]) # TODO: Remove workaround # utxos = self.rpc_wallet('listunspentblind', [1, 9999999, [sx_addr]]) utxos = [] - all_utxos = self.rpc_wallet('listunspentblind', [1, 9999999]) + all_utxos = self.rpc_wallet("listunspentblind", [1, 9999999]) for utxo in all_utxos: - if utxo.get('stealth_address', '_') == sx_addr: + if utxo.get("stealth_address", "_") == sx_addr: utxos.append(utxo) if len(utxos) < 1: - raise TemporaryError('No spendable outputs') + raise TemporaryError("No spendable outputs") elif len(utxos) > 1: - raise ValueError('Too many spendable outputs') + raise ValueError("Too many spendable outputs") utxo = utxos[0] - utxo_sats = self.make_int(utxo['amount']) + utxo_sats = self.make_int(utxo["amount"]) if spend_actual_balance and utxo_sats != cb_swap_value: - self._log.warning('Spending actual balance {}, not swap value {}.'.format(utxo_sats, cb_swap_value)) + self._log.warning( + "Spending actual balance {}, not swap value {}.".format( + utxo_sats, cb_swap_value + ) + ) cb_swap_value = utxo_sats - inputs = [{'tx': utxo['txid'], 'n': utxo['vout']}, ] - params = ['blind', 'blind', - [{'address': address_to, 'amount': self.format_amount(cb_swap_value), 'subfee': True}, ], - '', '', self._anon_tx_ring_size, 1, False, - {'conf_target': self._conf_target, 'inputs': inputs, 'show_fee': True}] - rv = self.rpc_wallet('sendtypeto', params) - return bytes.fromhex(rv['txid']) + inputs = [ + {"tx": utxo["txid"], "n": utxo["vout"]}, + ] + params = [ + "blind", + "blind", + [ + { + "address": address_to, + "amount": self.format_amount(cb_swap_value), + "subfee": True, + }, + ], + "", + "", + self._anon_tx_ring_size, + 1, + False, + {"conf_target": self._conf_target, "inputs": inputs, "show_fee": True}, + ] + rv = self.rpc_wallet("sendtypeto", params) + return bytes.fromhex(rv["txid"]) def findTxnByHash(self, txid_hex): # txindex is enabled for Particl try: - rv = self.rpc('getrawtransaction', [txid_hex, True]) - except Exception as ex: - self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) + rv = self.rpc("getrawtransaction", [txid_hex, True]) + except Exception as e: # noqa: F841 + self._log.debug( + "findTxnByHash getrawtransaction failed: {}".format(txid_hex) + ) return None - if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: - return {'txid': txid_hex, 'amount': 0, 'height': rv['height']} + if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed: + return {"txid": txid_hex, "amount": 0, "height": rv["height"]} return None - def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str: - txn = self.rpc_wallet('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) + def createRawFundedTransaction( + self, + addr_to: str, + amount: int, + sub_fee: bool = False, + lock_unspents: bool = True, + ) -> str: + txn = self.rpc_wallet( + "createrawtransaction", [[], {addr_to: self.format_amount(amount)}] + ) options = { - 'lockUnspents': lock_unspents, - 'conf_target': self._conf_target, + "lockUnspents": lock_unspents, + "conf_target": self._conf_target, } if sub_fee: - options['subtractFeeFromOutputs'] = [0,] - return self.rpc_wallet('fundrawtransactionfrom', ['blind', txn, options])['hex'] + options["subtractFeeFromOutputs"] = [ + 0, + ] + return self.rpc_wallet("fundrawtransactionfrom", ["blind", txn, options])["hex"] class PARTInterfaceAnon(PARTInterface): @@ -793,7 +1172,7 @@ class PARTInterfaceAnon(PARTInterface): @staticmethod def xmr_swap_a_lock_spend_tx_vsize() -> int: - raise ValueError('Not possible') + raise ValueError("Not possible") @staticmethod def xmr_swap_b_lock_spend_tx_vsize() -> int: @@ -805,105 +1184,169 @@ class PARTInterfaceAnon(PARTInterface): return 12 def coin_name(self) -> str: - return super().coin_name() + ' Anon' + return super().coin_name() + " Anon" - def publishBLockTx(self, kbv: bytes, Kbs: bytes, output_amount: int, feerate: int, unlock_time: int = 0) -> bytes: + def publishBLockTx( + self, + kbv: bytes, + Kbs: bytes, + output_amount: int, + feerate: int, + unlock_time: int = 0, + ) -> bytes: Kbv = self.getPubkey(kbv) sx_addr = self.formatStealthAddress(Kbv, Kbs) # TODO: Fund from other balances - params = ['anon', 'anon', - [{'address': sx_addr, 'amount': self.format_amount(output_amount)}, ], - '', '', self._anon_tx_ring_size, 1, False, - {'conf_target': self._conf_target, 'blind_watchonly_visible': True}] + params = [ + "anon", + "anon", + [ + {"address": sx_addr, "amount": self.format_amount(output_amount)}, + ], + "", + "", + self._anon_tx_ring_size, + 1, + False, + {"conf_target": self._conf_target, "blind_watchonly_visible": True}, + ] - txid = self.rpc_wallet('sendtypeto', params) + txid = self.rpc_wallet("sendtypeto", params) return bytes.fromhex(txid) - def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender): + def findTxB( + self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender + ): Kbv = self.getPubkey(kbv) sx_addr = self.formatStealthAddress(Kbv, Kbs) - self._log.debug('sx_addr: {}'.format(sx_addr)) + self._log.debug("sx_addr: {}".format(sx_addr)) # Tx recipient must import the stealth address as watch only if bid_sender: cb_swap_value *= -1 else: - addr_info = self.rpc_wallet('getaddressinfo', [sx_addr]) - if not addr_info['iswatchonly']: + addr_info = self.rpc_wallet("getaddressinfo", [sx_addr]) + if not addr_info["iswatchonly"]: wif_scan_key = self.encodeKey(kbv) - self.rpc_wallet('importstealthaddress', [wif_scan_key, Kbs.hex()]) - self._log.info('Imported watch-only sx_addr: {}'.format(sx_addr)) - self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height)) - self.rpc_wallet('rescanblockchain', [restore_height]) + self.rpc_wallet("importstealthaddress", [wif_scan_key, Kbs.hex()]) + self._log.info("Imported watch-only sx_addr: {}".format(sx_addr)) + self._log.info( + "Rescanning {} chain from height: {}".format( + self.coin_name(), restore_height + ) + ) + self.rpc_wallet("rescanblockchain", [restore_height]) - params = [{'include_watchonly': True, 'search': sx_addr}] - txns = self.rpc_wallet('filtertransactions', params) + params = [{"include_watchonly": True, "search": sx_addr}] + txns = self.rpc_wallet("filtertransactions", params) if len(txns) == 1: tx = txns[0] - assert (tx['outputs'][0]['stealth_address'] == sx_addr) # Should not be possible - ensure(tx['outputs'][0]['type'] == 'anon', 'Output is not anon') + assert ( + tx["outputs"][0]["stealth_address"] == sx_addr + ) # Should not be possible + ensure(tx["outputs"][0]["type"] == "anon", "Output is not anon") - if self.make_int(tx['outputs'][0]['amount']) == cb_swap_value: + if self.make_int(tx["outputs"][0]["amount"]) == cb_swap_value: height = 0 - if tx['confirmations'] > 0: - chain_height = self.rpc('getblockcount') - height = chain_height - (tx['confirmations'] - 1) - return {'txid': tx['txid'], 'amount': cb_swap_value, 'height': height} + if tx["confirmations"] > 0: + chain_height = self.rpc("getblockcount") + height = chain_height - (tx["confirmations"] - 1) + return {"txid": tx["txid"], "amount": cb_swap_value, "height": height} else: - self._log.warning('Incorrect amount detected for coin b lock txn: {}'.format(tx['txid'])) + self._log.warning( + "Incorrect amount detected for coin b lock txn: {}".format( + tx["txid"] + ) + ) return -1 return None - def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes: + def spendBLockTx( + self, + chain_b_lock_txid: bytes, + address_to: str, + kbv: bytes, + kbs: bytes, + cb_swap_value: int, + b_fee: int, + restore_height: int, + spend_actual_balance: bool = False, + lock_tx_vout=None, + ) -> bytes: Kbv = self.getPubkey(kbv) Kbs = self.getPubkey(kbs) sx_addr = self.formatStealthAddress(Kbv, Kbs) - addr_info = self.rpc_wallet('getaddressinfo', [sx_addr]) - if not addr_info['ismine']: + addr_info = self.rpc_wallet("getaddressinfo", [sx_addr]) + if not addr_info["ismine"]: wif_scan_key = self.encodeKey(kbv) wif_spend_key = self.encodeKey(kbs) - self.rpc_wallet('importstealthaddress', [wif_scan_key, wif_spend_key]) - self._log.info('Imported spend key for sx_addr: {}'.format(sx_addr)) - self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), restore_height)) - self.rpc_wallet('rescanblockchain', [restore_height]) + self.rpc_wallet("importstealthaddress", [wif_scan_key, wif_spend_key]) + self._log.info("Imported spend key for sx_addr: {}".format(sx_addr)) + self._log.info( + "Rescanning {} chain from height: {}".format( + self.coin_name(), restore_height + ) + ) + self.rpc_wallet("rescanblockchain", [restore_height]) - autxos = self.rpc_wallet('listunspentanon', [1, 9999999, [sx_addr]]) + autxos = self.rpc_wallet("listunspentanon", [1, 9999999, [sx_addr]]) if len(autxos) < 1: - raise TemporaryError('No spendable outputs') + raise TemporaryError("No spendable outputs") elif len(autxos) > 1: - raise ValueError('Too many spendable outputs') + raise ValueError("Too many spendable outputs") utxo = autxos[0] - utxo_sats = self.make_int(utxo['amount']) + utxo_sats = self.make_int(utxo["amount"]) if spend_actual_balance and utxo_sats != cb_swap_value: - self._log.warning('Spending actual balance {}, not swap value {}.'.format(utxo_sats, cb_swap_value)) + self._log.warning( + "Spending actual balance {}, not swap value {}.".format( + utxo_sats, cb_swap_value + ) + ) cb_swap_value = utxo_sats - inputs = [{'tx': utxo['txid'], 'n': utxo['vout']}, ] - params = ['anon', 'anon', - [{'address': address_to, 'amount': self.format_amount(cb_swap_value), 'subfee': True}, ], - '', '', self._anon_tx_ring_size, 1, False, - {'conf_target': self._conf_target, 'inputs': inputs, 'show_fee': True}] - rv = self.rpc_wallet('sendtypeto', params) - return bytes.fromhex(rv['txid']) + inputs = [ + {"tx": utxo["txid"], "n": utxo["vout"]}, + ] + params = [ + "anon", + "anon", + [ + { + "address": address_to, + "amount": self.format_amount(cb_swap_value), + "subfee": True, + }, + ], + "", + "", + self._anon_tx_ring_size, + 1, + False, + {"conf_target": self._conf_target, "inputs": inputs, "show_fee": True}, + ] + rv = self.rpc_wallet("sendtypeto", params) + return bytes.fromhex(rv["txid"]) def findTxnByHash(self, txid_hex: str): # txindex is enabled for Particl try: - rv = self.rpc('getrawtransaction', [txid_hex, True]) - except Exception as ex: - self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) + rv = self.rpc("getrawtransaction", [txid_hex, True]) + except Exception as e: # noqa: F841 + self._log.debug( + "findTxnByHash getrawtransaction failed: {}".format(txid_hex) + ) return None - if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: - return {'txid': txid_hex, 'amount': 0, 'height': rv['height']} + if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed: + return {"txid": txid_hex, "amount": 0, "height": rv["height"]} return None def getSpendableBalance(self) -> int: - return self.make_int(self.rpc_wallet('getbalances')['mine']['anon_trusted']) + return self.make_int(self.rpc_wallet("getbalances")["mine"]["anon_trusted"]) diff --git a/basicswap/interface/passthrough_btc.py b/basicswap/interface/passthrough_btc.py index c7fa214..551e986 100644 --- a/basicswap/interface/passthrough_btc.py +++ b/basicswap/interface/passthrough_btc.py @@ -6,8 +6,7 @@ # file LICENSE or http://www.opensource.org/licenses/mit-license.php. from .btc import BTCInterface -from basicswap.contrib.test_framework.messages import ( - CTxOut) +from basicswap.contrib.test_framework.messages import CTxOut class PassthroughBTCInterface(BTCInterface): @@ -15,5 +14,5 @@ class PassthroughBTCInterface(BTCInterface): super().__init__(coin_settings, network) self.txoType = CTxOut self._network = network - self.blocks_confirmed = coin_settings['blocks_confirmed'] - self.setConfTarget(coin_settings['conf_target']) + self.blocks_confirmed = coin_settings["blocks_confirmed"] + self.setConfTarget(coin_settings["conf_target"]) diff --git a/basicswap/interface/pivx.py b/basicswap/interface/pivx.py index 038744c..b9715d8 100644 --- a/basicswap/interface/pivx.py +++ b/basicswap/interface/pivx.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2022 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -11,11 +12,7 @@ from .btc import BTCInterface from basicswap.rpc import make_rpc_func from basicswap.chainparams import Coins from basicswap.util.address import decodeAddress -from .contrib.pivx_test_framework.messages import ( - CBlock, - ToHex, - FromHex, - CTransaction) +from .contrib.pivx_test_framework.messages import CBlock, ToHex, FromHex, CTransaction from basicswap.contrib.test_framework.script import ( CScript, OP_DUP, @@ -33,65 +30,79 @@ class PIVXInterface(BTCInterface): def __init__(self, coin_settings, network, swap_client=None): super(PIVXInterface, self).__init__(coin_settings, network, swap_client) # No multiwallet support - self.rpc_wallet = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) + self.rpc_wallet = make_rpc_func( + self._rpcport, self._rpcauth, host=self._rpc_host + ) def checkWallets(self) -> int: return 1 def signTxWithWallet(self, tx): - rv = self.rpc('signrawtransaction', [tx.hex()]) - return bytes.fromhex(rv['hex']) + rv = self.rpc("signrawtransaction", [tx.hex()]) + return bytes.fromhex(rv["hex"]) - def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str: - txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) + def createRawFundedTransaction( + self, + addr_to: str, + amount: int, + sub_fee: bool = False, + lock_unspents: bool = True, + ) -> str: + txn = self.rpc( + "createrawtransaction", [[], {addr_to: self.format_amount(amount)}] + ) fee_rate, fee_src = self.get_fee_rate(self._conf_target) - self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}') + self._log.debug( + f"Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}" + ) options = { - 'lockUnspents': lock_unspents, - 'feeRate': fee_rate, + "lockUnspents": lock_unspents, + "feeRate": fee_rate, } if sub_fee: - options['subtractFeeFromOutputs'] = [0,] - return self.rpc('fundrawtransaction', [txn, options])['hex'] + options["subtractFeeFromOutputs"] = [ + 0, + ] + return self.rpc("fundrawtransaction", [txn, options])["hex"] def createRawSignedTransaction(self, addr_to, amount) -> str: txn_funded = self.createRawFundedTransaction(addr_to, amount) - return self.rpc('signrawtransaction', [txn_funded])['hex'] + return self.rpc("signrawtransaction", [txn_funded])["hex"] def decodeAddress(self, address): return decodeAddress(address)[1:] def getBlockWithTxns(self, block_hash): # TODO: Bypass decoderawtransaction and getblockheader - block = self.rpc('getblock', [block_hash, False]) - block_header = self.rpc('getblockheader', [block_hash]) + block = self.rpc("getblock", [block_hash, False]) + block_header = self.rpc("getblockheader", [block_hash]) decoded_block = CBlock() decoded_block = FromHex(decoded_block, block) tx_rv = [] for tx in decoded_block.vtx: - tx_dec = self.rpc('decoderawtransaction', [ToHex(tx)]) + tx_dec = self.rpc("decoderawtransaction", [ToHex(tx)]) tx_rv.append(tx_dec) block_rv = { - 'hash': block_hash, - 'previousblockhash': block_header['previousblockhash'], - 'tx': tx_rv, - 'confirmations': block_header['confirmations'], - 'height': block_header['height'], - 'time': block_header['time'], - 'version': block_header['version'], - 'merkleroot': block_header['merkleroot'], + "hash": block_hash, + "previousblockhash": block_header["previousblockhash"], + "tx": tx_rv, + "confirmations": block_header["confirmations"], + "height": block_header["height"], + "time": block_header["time"], + "version": block_header["version"], + "merkleroot": block_header["merkleroot"], } return block_rv def withdrawCoin(self, value, addr_to, subfee): - params = [addr_to, value, '', '', subfee] - return self.rpc('sendtoaddress', params) + params = [addr_to, value, "", "", subfee] + return self.rpc("sendtoaddress", params) def getSpendableBalance(self) -> int: - return self.make_int(self.rpc('getwalletinfo')['balance']) + return self.make_int(self.rpc("getwalletinfo")["balance"]) def loadTx(self, tx_bytes): # Load tx from bytes to internal representation @@ -107,22 +118,35 @@ class PIVXInterface(BTCInterface): add_bytes = 107 size = len(tx.serialize_with_witness()) + add_bytes pay_fee = round(fee_rate * size / 1000) - self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.') + self._log.info( + f"BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}." + ) return pay_fee def signTxWithKey(self, tx: bytes, key: bytes) -> bytes: key_wif = self.encodeKey(key) - rv = self.rpc('signrawtransaction', [tx.hex(), [], [key_wif, ]]) - return bytes.fromhex(rv['hex']) + rv = self.rpc( + "signrawtransaction", + [ + tx.hex(), + [], + [ + key_wif, + ], + ], + ) + return bytes.fromhex(rv["hex"]) def findTxnByHash(self, txid_hex: str): # Only works for wallet txns try: - rv = self.rpc('gettransaction', [txid_hex]) - except Exception as ex: - self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) + rv = self.rpc("gettransaction", [txid_hex]) + except Exception as e: # noqa: F841 + self._log.debug( + "findTxnByHash getrawtransaction failed: {}".format(txid_hex) + ) return None - if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: - block_height = self.getBlockHeader(rv['blockhash'])['height'] - return {'txid': txid_hex, 'amount': 0, 'height': block_height} + if "confirmations" in rv and rv["confirmations"] >= self.blocks_confirmed: + block_height = self.getBlockHeader(rv["blockhash"])["height"] + return {"txid": txid_hex, "amount": 0, "height": block_height} return None diff --git a/basicswap/interface/xmr.py b/basicswap/interface/xmr.py index 3bc3efb..1f2759f 100644 --- a/basicswap/interface/xmr.py +++ b/basicswap/interface/xmr.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020-2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -26,16 +27,9 @@ from coincurve.dleag import ( from basicswap.interface.base import ( Curves, ) -from basicswap.util import ( - i2b, b2i, b2h, - dumpj, - ensure, - TemporaryError) -from basicswap.util.network import ( - is_private_ip_address) -from basicswap.rpc_xmr import ( - make_xmr_rpc_func, - make_xmr_rpc2_func) +from basicswap.util import i2b, b2i, b2h, dumpj, ensure, TemporaryError +from basicswap.util.network import is_private_ip_address +from basicswap.rpc_xmr import make_xmr_rpc_func, make_xmr_rpc2_func from basicswap.chainparams import XMR_COIN, Coins from basicswap.interface.base import CoinInterface @@ -75,7 +69,7 @@ class XMRInterface(CoinInterface): @staticmethod def xmr_swap_a_lock_spend_tx_vsize() -> int: - raise ValueError('Not possible') + raise ValueError("Not possible") @staticmethod def xmr_swap_b_lock_spend_tx_vsize() -> int: @@ -84,62 +78,93 @@ class XMRInterface(CoinInterface): def is_transient_error(self, ex) -> bool: str_error: str = str(ex).lower() - if 'failed to get output distribution' in str_error: + if "failed to get output distribution" in str_error: return True return super().is_transient_error(ex) def __init__(self, coin_settings, network, swap_client=None): super().__init__(network) - self._addr_prefix = self.chainparams_network()['address_prefix'] + self._addr_prefix = self.chainparams_network()["address_prefix"] - self.blocks_confirmed = coin_settings['blocks_confirmed'] - self._restore_height = coin_settings.get('restore_height', 0) - self.setFeePriority(coin_settings.get('fee_priority', 0)) + self.blocks_confirmed = coin_settings["blocks_confirmed"] + self._restore_height = coin_settings.get("restore_height", 0) + self.setFeePriority(coin_settings.get("fee_priority", 0)) self._sc = swap_client self._log = self._sc.log if self._sc and self._sc.log else logging self._wallet_password = None self._have_checked_seed = False daemon_login = None - if coin_settings.get('rpcuser', '') != '': - daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', '')) + if coin_settings.get("rpcuser", "") != "": + daemon_login = ( + coin_settings.get("rpcuser", ""), + coin_settings.get("rpcpassword", ""), + ) - rpchost = coin_settings.get('rpchost', '127.0.0.1') + rpchost = coin_settings.get("rpchost", "127.0.0.1") proxy_host = None proxy_port = None # Connect to the daemon over a proxy if not running locally if swap_client: chain_client_settings = swap_client.getChainClientSettings(self.coin_type()) - manage_daemon: bool = chain_client_settings['manage_daemon'] + manage_daemon: bool = chain_client_settings["manage_daemon"] if swap_client.use_tor_proxy: if manage_daemon is False: - log_str: str = '' - have_cc_tor_opt = 'use_tor' in chain_client_settings - if have_cc_tor_opt and chain_client_settings['use_tor'] is False: - log_str = ' bypassing proxy (use_tor false for XMR)' + log_str: str = "" + have_cc_tor_opt = "use_tor" in chain_client_settings + if have_cc_tor_opt and chain_client_settings["use_tor"] is False: + log_str = " bypassing proxy (use_tor false for XMR)" elif have_cc_tor_opt is False and is_private_ip_address(rpchost): - log_str = ' bypassing proxy (private ip address)' + log_str = " bypassing proxy (private ip address)" else: proxy_host = swap_client.tor_proxy_host proxy_port = swap_client.tor_proxy_port - log_str = f' through proxy at {proxy_host}' - self._log.info(f'Connecting to remote {self.coin_name()} daemon at {rpchost}{log_str}.') + log_str = f" through proxy at {proxy_host}" + self._log.info( + f"Connecting to remote {self.coin_name()} daemon at {rpchost}{log_str}." + ) else: - self._log.info(f'Not connecting to local {self.coin_name()} daemon through proxy.') + self._log.info( + f"Not connecting to local {self.coin_name()} daemon through proxy." + ) elif manage_daemon is False: - self._log.info(f'Connecting to remote {self.coin_name()} daemon at {rpchost}.') + self._log.info( + f"Connecting to remote {self.coin_name()} daemon at {rpchost}." + ) - self._rpctimeout = coin_settings.get('rpctimeout', 60) - self._walletrpctimeout = coin_settings.get('walletrpctimeout', 120) - self._walletrpctimeoutlong = coin_settings.get('walletrpctimeoutlong', 600) + self._rpctimeout = coin_settings.get("rpctimeout", 60) + self._walletrpctimeout = coin_settings.get("walletrpctimeout", 120) + self._walletrpctimeoutlong = coin_settings.get("walletrpctimeoutlong", 600) - self.rpc = make_xmr_rpc_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port, default_timeout=self._rpctimeout, tag='Node(j) ') - self.rpc2 = make_xmr_rpc2_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port, default_timeout=self._rpctimeout, tag='Node ') # non-json endpoint - self.rpc_wallet = make_xmr_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'), default_timeout=self._walletrpctimeout, tag='Wallet ') + self.rpc = make_xmr_rpc_func( + coin_settings["rpcport"], + daemon_login, + host=rpchost, + proxy_host=proxy_host, + proxy_port=proxy_port, + default_timeout=self._rpctimeout, + tag="Node(j) ", + ) + self.rpc2 = make_xmr_rpc2_func( + coin_settings["rpcport"], + daemon_login, + host=rpchost, + proxy_host=proxy_host, + proxy_port=proxy_port, + default_timeout=self._rpctimeout, + tag="Node ", + ) # non-json endpoint + self.rpc_wallet = make_xmr_rpc_func( + coin_settings["walletrpcport"], + coin_settings["walletrpcauth"], + host=coin_settings.get("walletrpchost", "127.0.0.1"), + default_timeout=self._walletrpctimeout, + tag="Wallet ", + ) def setFeePriority(self, new_priority): - ensure(new_priority >= 0 and new_priority < 4, 'Invalid fee_priority value') + ensure(new_priority >= 0 and new_priority < 4, "Invalid fee_priority value") self._fee_priority = new_priority def setWalletFilename(self, wallet_filename): @@ -147,29 +172,31 @@ class XMRInterface(CoinInterface): def createWallet(self, params): if self._wallet_password is not None: - params['password'] = self._wallet_password - rv = self.rpc_wallet('generate_from_keys', params) - self._log.info('generate_from_keys %s', dumpj(rv)) + params["password"] = self._wallet_password + rv = self.rpc_wallet("generate_from_keys", params) + self._log.info("generate_from_keys %s", dumpj(rv)) def openWallet(self, filename): - params = {'filename': filename} + params = {"filename": filename} if self._wallet_password is not None: - params['password'] = self._wallet_password + params["password"] = self._wallet_password try: # Can't reopen the same wallet in windows, !is_keys_file_locked() - self.rpc_wallet('close_wallet') + self.rpc_wallet("close_wallet") except Exception: pass - self.rpc_wallet('open_wallet', params) + self.rpc_wallet("open_wallet", params) - def initialiseWallet(self, key_view: bytes, key_spend: bytes, restore_height=None) -> None: + def initialiseWallet( + self, key_view: bytes, key_spend: bytes, restore_height=None + ) -> None: with self._mx_wallet: try: self.openWallet(self._wallet_filename) # TODO: Check address return # Wallet exists - except Exception as e: + except Exception as e: # noqa: F841 pass Kbv = self.getPubkey(key_view) @@ -177,11 +204,11 @@ class XMRInterface(CoinInterface): address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix) params = { - 'filename': self._wallet_filename, - 'address': address_b58, - 'viewkey': b2h(key_view[::-1]), - 'spendkey': b2h(key_spend[::-1]), - 'restore_height': self._restore_height, + "filename": self._wallet_filename, + "address": address_b58, + "viewkey": b2h(key_view[::-1]), + "spendkey": b2h(key_spend[::-1]), + "restore_height": self._restore_height, } self.createWallet(params) self.openWallet(self._wallet_filename) @@ -191,84 +218,95 @@ class XMRInterface(CoinInterface): self.openWallet(self._wallet_filename) def testDaemonRPC(self, with_wallet=True) -> None: - self.rpc_wallet('get_languages') + self.rpc_wallet("get_languages") def getDaemonVersion(self): - return self.rpc_wallet('get_version')['version'] + return self.rpc_wallet("get_version")["version"] def getBlockchainInfo(self): - get_height = self.rpc2('get_height', timeout=self._rpctimeout) + get_height = self.rpc2("get_height", timeout=self._rpctimeout) rv = { - 'blocks': get_height['height'], - 'verificationprogress': 0.0, + "blocks": get_height["height"], + "verificationprogress": 0.0, } try: # get_block_count.block_count is how many blocks are in the longest chain known to the node. # get_block_count returns "Internal error" if bootstrap-daemon is active - if get_height['untrusted'] is True: - rv['bootstrapping'] = True - get_info = self.rpc2('get_info', timeout=self._rpctimeout) - if 'height_without_bootstrap' in get_info: - rv['blocks'] = get_info['height_without_bootstrap'] + if get_height["untrusted"] is True: + rv["bootstrapping"] = True + get_info = self.rpc2("get_info", timeout=self._rpctimeout) + if "height_without_bootstrap" in get_info: + rv["blocks"] = get_info["height_without_bootstrap"] - rv['known_block_count'] = get_info['height'] - if rv['known_block_count'] > rv['blocks']: - rv['verificationprogress'] = rv['blocks'] / rv['known_block_count'] + rv["known_block_count"] = get_info["height"] + if rv["known_block_count"] > rv["blocks"]: + rv["verificationprogress"] = rv["blocks"] / rv["known_block_count"] else: - rv['known_block_count'] = self.rpc('get_block_count', timeout=self._rpctimeout)['count'] - rv['verificationprogress'] = rv['blocks'] / rv['known_block_count'] + rv["known_block_count"] = self.rpc( + "get_block_count", timeout=self._rpctimeout + )["count"] + rv["verificationprogress"] = rv["blocks"] / rv["known_block_count"] except Exception as e: - self._log.warning(f'{self.ticker_str()} get_block_count failed with: {e}') - rv['verificationprogress'] = 0.0 + self._log.warning(f"{self.ticker_str()} get_block_count failed with: {e}") + rv["verificationprogress"] = 0.0 return rv def getChainHeight(self): - return self.rpc2('get_height', timeout=self._rpctimeout)['height'] + return self.rpc2("get_height", timeout=self._rpctimeout)["height"] def getWalletInfo(self): with self._mx_wallet: try: self.openWallet(self._wallet_filename) except Exception as e: - if 'Failed to open wallet' in str(e): - rv = {'encrypted': True, 'locked': True, 'balance': 0, 'unconfirmed_balance': 0} + if "Failed to open wallet" in str(e): + rv = { + "encrypted": True, + "locked": True, + "balance": 0, + "unconfirmed_balance": 0, + } return rv raise e rv = {} - self.rpc_wallet('refresh') - balance_info = self.rpc_wallet('get_balance') + self.rpc_wallet("refresh") + balance_info = self.rpc_wallet("get_balance") - rv['balance'] = self.format_amount(balance_info['unlocked_balance']) - rv['unconfirmed_balance'] = self.format_amount(balance_info['balance'] - balance_info['unlocked_balance']) - rv['encrypted'] = False if self._wallet_password is None else True - rv['locked'] = False + rv["balance"] = self.format_amount(balance_info["unlocked_balance"]) + rv["unconfirmed_balance"] = self.format_amount( + balance_info["balance"] - balance_info["unlocked_balance"] + ) + rv["encrypted"] = False if self._wallet_password is None else True + rv["locked"] = False return rv def getMainWalletAddress(self) -> str: with self._mx_wallet: self.openWallet(self._wallet_filename) - return self.rpc_wallet('get_address')['address'] + return self.rpc_wallet("get_address")["address"] def getNewAddress(self, placeholder) -> str: with self._mx_wallet: self.openWallet(self._wallet_filename) - new_address = self.rpc_wallet('create_address', {'account_index': 0})['address'] - self.rpc_wallet('store') + new_address = self.rpc_wallet("create_address", {"account_index": 0})[ + "address" + ] + self.rpc_wallet("store") return new_address def get_fee_rate(self, conf_target: int = 2): # fees - array of unsigned int; Represents the base fees at different priorities [slow, normal, fast, fastest]. - fee_est = self.rpc('get_fee_estimate') + fee_est = self.rpc("get_fee_estimate") if conf_target <= 1: conf_target = 1 # normal else: conf_target = 0 # slow - fee_per_k_bytes = fee_est['fees'][conf_target] * 1000 + fee_per_k_bytes = fee_est["fees"][conf_target] * 1000 - return float(self.format_amount(fee_per_k_bytes)), 'get_fee_estimate' + return float(self.format_amount(fee_per_k_bytes)), "get_fee_estimate" def getNewSecretKey(self) -> bytes: # Note: Returned bytes are in big endian order @@ -299,7 +337,7 @@ class XMRInterface(CoinInterface): def verifyKey(self, k: int) -> bool: i = b2i(k) - return (i < edf.l and i > 8) + return i < edf.l and i > 8 def verifyPubkey(self, pubkey_bytes): # Calls ed25519_decode_check_point() in secp256k1 @@ -325,45 +363,59 @@ class XMRInterface(CoinInterface): def encodeSharedAddress(self, Kbv: bytes, Kbs: bytes) -> str: return xmr_util.encode_address(Kbv, Kbs, self._addr_prefix) - def publishBLockTx(self, kbv: bytes, Kbs: bytes, output_amount: int, feerate: int, unlock_time: int = 0) -> bytes: + def publishBLockTx( + self, + kbv: bytes, + Kbs: bytes, + output_amount: int, + feerate: int, + unlock_time: int = 0, + ) -> bytes: with self._mx_wallet: self.openWallet(self._wallet_filename) - self.rpc_wallet('refresh') + self.rpc_wallet("refresh") Kbv = self.getPubkey(kbv) shared_addr = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix) - params = {'destinations': [{'amount': output_amount, 'address': shared_addr}], 'unlock_time': unlock_time} + params = { + "destinations": [{"amount": output_amount, "address": shared_addr}], + "unlock_time": unlock_time, + } if self._fee_priority > 0: - params['priority'] = self._fee_priority - rv = self.rpc_wallet('transfer', params) - self._log.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr) - tx_hash = bytes.fromhex(rv['tx_hash']) + params["priority"] = self._fee_priority + rv = self.rpc_wallet("transfer", params) + self._log.info( + "publishBLockTx %s to address_b58 %s", rv["tx_hash"], shared_addr + ) + tx_hash = bytes.fromhex(rv["tx_hash"]) return tx_hash - def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender): + def findTxB( + self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender + ): with self._mx_wallet: Kbv = self.getPubkey(kbv) address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix) kbv_le = kbv[::-1] params = { - 'restore_height': restore_height, - 'filename': address_b58, - 'address': address_b58, - 'viewkey': b2h(kbv_le), + "restore_height": restore_height, + "filename": address_b58, + "address": address_b58, + "viewkey": b2h(kbv_le), } try: self.openWallet(address_b58) - except Exception as e: + except Exception as e: # noqa: F841 self.createWallet(params) self.openWallet(address_b58) - self.rpc_wallet('refresh', timeout=self._walletrpctimeoutlong) + self.rpc_wallet("refresh", timeout=self._walletrpctimeoutlong) - ''' + """ # Debug try: current_height = self.rpc_wallet('get_height')['height'] @@ -372,137 +424,222 @@ class XMRInterface(CoinInterface): self._log.info('rpc failed %s', str(e)) current_height = None # If the transfer is available it will be deep enough # and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed): - ''' - params = {'transfer_type': 'available'} - transfers = self.rpc_wallet('incoming_transfers', params) + """ + params = {"transfer_type": "available"} + transfers = self.rpc_wallet("incoming_transfers", params) rv = None - if 'transfers' in transfers: - for transfer in transfers['transfers']: + if "transfers" in transfers: + for transfer in transfers["transfers"]: # unlocked <- wallet->is_transfer_unlocked() checks unlock_time and CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE - if not transfer['unlocked']: - full_tx = self.rpc_wallet('get_transfer_by_txid', {'txid': transfer['tx_hash']}) - unlock_time = full_tx['transfer']['unlock_time'] + if not transfer["unlocked"]: + full_tx = self.rpc_wallet( + "get_transfer_by_txid", {"txid": transfer["tx_hash"]} + ) + unlock_time = full_tx["transfer"]["unlock_time"] if unlock_time != 0: - self._log.warning('Coin b lock txn is locked: {}, unlock_time {}'.format(transfer['tx_hash'], unlock_time)) + self._log.warning( + "Coin b lock txn is locked: {}, unlock_time {}".format( + transfer["tx_hash"], unlock_time + ) + ) rv = -1 continue - if transfer['amount'] == cb_swap_value: - return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': 0 if 'block_height' not in transfer else transfer['block_height']} + if transfer["amount"] == cb_swap_value: + return { + "txid": transfer["tx_hash"], + "amount": transfer["amount"], + "height": ( + 0 + if "block_height" not in transfer + else transfer["block_height"] + ), + } else: - self._log.warning('Incorrect amount detected for coin b lock txn: {}'.format(transfer['tx_hash'])) + self._log.warning( + "Incorrect amount detected for coin b lock txn: {}".format( + transfer["tx_hash"] + ) + ) rv = -1 return rv def findTxnByHash(self, txid): with self._mx_wallet: self.openWallet(self._wallet_filename) - self.rpc_wallet('refresh', timeout=self._walletrpctimeoutlong) + self.rpc_wallet("refresh", timeout=self._walletrpctimeoutlong) try: - current_height = self.rpc2('get_height', timeout=self._rpctimeout)['height'] - self._log.info(f'findTxnByHash {self.ticker_str()} current_height {current_height}\nhash: {txid}') + current_height = self.rpc2("get_height", timeout=self._rpctimeout)[ + "height" + ] + self._log.info( + f"findTxnByHash {self.ticker_str()} current_height {current_height}\nhash: {txid}" + ) except Exception as e: - self._log.info('rpc failed %s', str(e)) - current_height = None # If the transfer is available it will be deep enough + self._log.info("rpc failed %s", str(e)) + current_height = ( + None # If the transfer is available it will be deep enough + ) - params = {'transfer_type': 'available'} - rv = self.rpc_wallet('incoming_transfers', params) - if 'transfers' in rv: - for transfer in rv['transfers']: - if transfer['tx_hash'] == txid \ - and (current_height is None or current_height - transfer['block_height'] > self.blocks_confirmed): - return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': transfer['block_height']} + params = {"transfer_type": "available"} + rv = self.rpc_wallet("incoming_transfers", params) + if "transfers" in rv: + for transfer in rv["transfers"]: + if transfer["tx_hash"] == txid and ( + current_height is None + or current_height - transfer["block_height"] + > self.blocks_confirmed + ): + return { + "txid": transfer["tx_hash"], + "amount": transfer["amount"], + "height": transfer["block_height"], + } return None - def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee_rate: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes: - ''' + def spendBLockTx( + self, + chain_b_lock_txid: bytes, + address_to: str, + kbv: bytes, + kbs: bytes, + cb_swap_value: int, + b_fee_rate: int, + restore_height: int, + spend_actual_balance: bool = False, + lock_tx_vout=None, + ) -> bytes: + """ Notes: "Error: No unlocked balance in the specified subaddress(es)" can mean not enough funds after tx fee. - ''' + """ with self._mx_wallet: Kbv = self.getPubkey(kbv) Kbs = self.getPubkey(kbs) address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix) - wallet_filename = address_b58 + '_spend' + wallet_filename = address_b58 + "_spend" params = { - 'filename': wallet_filename, - 'address': address_b58, - 'viewkey': b2h(kbv[::-1]), - 'spendkey': b2h(kbs[::-1]), - 'restore_height': restore_height, + "filename": wallet_filename, + "address": address_b58, + "viewkey": b2h(kbv[::-1]), + "spendkey": b2h(kbs[::-1]), + "restore_height": restore_height, } try: self.openWallet(wallet_filename) - except Exception as e: + except Exception as e: # noqa: F841 self.createWallet(params) self.openWallet(wallet_filename) - self.rpc_wallet('refresh') - rv = self.rpc_wallet('get_balance') - if rv['balance'] < cb_swap_value: - self._log.warning('Balance is too low, checking for existing spend.') - txns = self.rpc_wallet('get_transfers', {'out': True}) - if 'out' in txns: - txns = txns['out'] + self.rpc_wallet("refresh") + rv = self.rpc_wallet("get_balance") + if rv["balance"] < cb_swap_value: + self._log.warning("Balance is too low, checking for existing spend.") + txns = self.rpc_wallet("get_transfers", {"out": True}) + if "out" in txns: + txns = txns["out"] if len(txns) > 0: - txid = txns[0]['txid'] - self._log.warning(f'spendBLockTx detected spending tx: {txid}.') + txid = txns[0]["txid"] + self._log.warning(f"spendBLockTx detected spending tx: {txid}.") # Should check for address_to, but only the from address is found in the output - if txns[0]['address'] == address_b58: + if txns[0]["address"] == address_b58: return bytes.fromhex(txid) - self._log.error('wallet {} balance {}, expected {}'.format(wallet_filename, rv['balance'], cb_swap_value)) + self._log.error( + "wallet {} balance {}, expected {}".format( + wallet_filename, rv["balance"], cb_swap_value + ) + ) if not spend_actual_balance: - raise TemporaryError('Invalid balance') + raise TemporaryError("Invalid balance") - if spend_actual_balance and rv['balance'] != cb_swap_value: - self._log.warning('Spending actual balance {}, not swap value {}.'.format(rv['balance'], cb_swap_value)) - cb_swap_value = rv['balance'] - if rv['unlocked_balance'] < cb_swap_value: - self._log.error('wallet {} balance {}, expected {}, blocks_to_unlock {}'.format(wallet_filename, rv['unlocked_balance'], cb_swap_value, rv['blocks_to_unlock'])) - raise TemporaryError('Invalid unlocked_balance') + if spend_actual_balance and rv["balance"] != cb_swap_value: + self._log.warning( + "Spending actual balance {}, not swap value {}.".format( + rv["balance"], cb_swap_value + ) + ) + cb_swap_value = rv["balance"] + if rv["unlocked_balance"] < cb_swap_value: + self._log.error( + "wallet {} balance {}, expected {}, blocks_to_unlock {}".format( + wallet_filename, + rv["unlocked_balance"], + cb_swap_value, + rv["blocks_to_unlock"], + ) + ) + raise TemporaryError("Invalid unlocked_balance") - params = {'address': address_to} + params = {"address": address_to} if self._fee_priority > 0: - params['priority'] = self._fee_priority + params["priority"] = self._fee_priority - rv = self.rpc_wallet('sweep_all', params) + rv = self.rpc_wallet("sweep_all", params) - return bytes.fromhex(rv['tx_hash_list'][0]) + return bytes.fromhex(rv["tx_hash_list"][0]) - def withdrawCoin(self, value, addr_to: str, sweepall: bool, estimate_fee: bool = False) -> str: + def withdrawCoin( + self, value, addr_to: str, sweepall: bool, estimate_fee: bool = False + ) -> str: with self._mx_wallet: self.openWallet(self._wallet_filename) - self.rpc_wallet('refresh') + self.rpc_wallet("refresh") if sweepall: - balance = self.rpc_wallet('get_balance') - if balance['balance'] != balance['unlocked_balance']: - raise ValueError('Balance must be fully confirmed to use sweep all.') - self._log.info('{} {} sweep_all.'.format(self.ticker_str(), 'estimate fee' if estimate_fee else 'withdraw')) - self._log.debug('{} balance: {}'.format(self.ticker_str(), balance['balance'])) - params = {'address': addr_to, 'do_not_relay': estimate_fee, 'subaddr_indices_all': True} + balance = self.rpc_wallet("get_balance") + if balance["balance"] != balance["unlocked_balance"]: + raise ValueError( + "Balance must be fully confirmed to use sweep all." + ) + self._log.info( + "{} {} sweep_all.".format( + self.ticker_str(), + "estimate fee" if estimate_fee else "withdraw", + ) + ) + self._log.debug( + "{} balance: {}".format(self.ticker_str(), balance["balance"]) + ) + params = { + "address": addr_to, + "do_not_relay": estimate_fee, + "subaddr_indices_all": True, + } if self._fee_priority > 0: - params['priority'] = self._fee_priority - rv = self.rpc_wallet('sweep_all', params) + params["priority"] = self._fee_priority + rv = self.rpc_wallet("sweep_all", params) if estimate_fee: - return {'num_txns': len(rv['fee_list']), 'sum_amount': sum(rv['amount_list']), 'sum_fee': sum(rv['fee_list']), 'sum_weight': sum(rv['weight_list'])} - return rv['tx_hash_list'][0] + return { + "num_txns": len(rv["fee_list"]), + "sum_amount": sum(rv["amount_list"]), + "sum_fee": sum(rv["fee_list"]), + "sum_weight": sum(rv["weight_list"]), + } + return rv["tx_hash_list"][0] value_sats: int = self.make_int(value) - params = {'destinations': [{'amount': value_sats, 'address': addr_to}], 'do_not_relay': estimate_fee} + params = { + "destinations": [{"amount": value_sats, "address": addr_to}], + "do_not_relay": estimate_fee, + } if self._fee_priority > 0: - params['priority'] = self._fee_priority - rv = self.rpc_wallet('transfer', params) + params["priority"] = self._fee_priority + rv = self.rpc_wallet("transfer", params) if estimate_fee: - return {'num_txns': 1, 'sum_amount': rv['amount'], 'sum_fee': rv['fee'], 'sum_weight': rv['weight']} - return rv['tx_hash'] + return { + "num_txns": 1, + "sum_amount": rv["amount"], + "sum_fee": rv["fee"], + "sum_weight": rv["weight"], + } + return rv["tx_hash"] def estimateFee(self, value: int, addr_to: str, sweepall: bool) -> str: return self.withdrawCoin(value, addr_to, sweepall, estimate_fee=True) @@ -512,7 +649,7 @@ class XMRInterface(CoinInterface): try: Kbv = self.getPubkey(kbv) address_b58 = xmr_util.encode_address(Kbv, Kbs, self._addr_prefix) - wallet_file = address_b58 + '_spend' + wallet_file = address_b58 + "_spend" try: self.openWallet(wallet_file) except Exception: @@ -520,54 +657,62 @@ class XMRInterface(CoinInterface): try: self.openWallet(wallet_file) except Exception: - self._log.info(f'showLockTransfers trying to create wallet for address {address_b58}.') + self._log.info( + f"showLockTransfers trying to create wallet for address {address_b58}." + ) kbv_le = kbv[::-1] params = { - 'restore_height': restore_height, - 'filename': address_b58, - 'address': address_b58, - 'viewkey': b2h(kbv_le), + "restore_height": restore_height, + "filename": address_b58, + "address": address_b58, + "viewkey": b2h(kbv_le), } self.createWallet(params) self.openWallet(address_b58) - self.rpc_wallet('refresh') + self.rpc_wallet("refresh") - rv = self.rpc_wallet('get_transfers', {'in': True, 'out': True, 'pending': True, 'failed': True}) - rv['filename'] = wallet_file + rv = self.rpc_wallet( + "get_transfers", + {"in": True, "out": True, "pending": True, "failed": True}, + ) + rv["filename"] = wallet_file return rv except Exception as e: - return {'error': str(e)} + return {"error": str(e)} def getSpendableBalance(self) -> int: with self._mx_wallet: self.openWallet(self._wallet_filename) - self.rpc_wallet('refresh') - balance_info = self.rpc_wallet('get_balance') - return balance_info['unlocked_balance'] + self.rpc_wallet("refresh") + balance_info = self.rpc_wallet("get_balance") + return balance_info["unlocked_balance"] def changeWalletPassword(self, old_password, new_password): - self._log.info('changeWalletPassword - {}'.format(self.ticker())) + self._log.info("changeWalletPassword - {}".format(self.ticker())) orig_password = self._wallet_password - if old_password != '': + if old_password != "": self._wallet_password = old_password try: self.openWallet(self._wallet_filename) - self.rpc_wallet('change_wallet_password', {'old_password': old_password, 'new_password': new_password}) + self.rpc_wallet( + "change_wallet_password", + {"old_password": old_password, "new_password": new_password}, + ) except Exception as e: self._wallet_password = orig_password raise e def unlockWallet(self, password: str) -> None: - self._log.info('unlockWallet - {}'.format(self.ticker())) + self._log.info("unlockWallet - {}".format(self.ticker())) self._wallet_password = password if not self._have_checked_seed: self._sc.checkWalletSeed(self.coin_type()) def lockWallet(self) -> None: - self._log.info('lockWallet - {}'.format(self.ticker())) + self._log.info("lockWallet - {}".format(self.ticker())) self._wallet_password = None def isAddressMine(self, address): @@ -576,7 +721,14 @@ class XMRInterface(CoinInterface): def ensureFunds(self, amount: int) -> None: if self.getSpendableBalance() < amount: - raise ValueError('Balance too low') + raise ValueError("Balance too low") def getTransaction(self, txid: bytes): - return self.rpc2('get_transactions', {'txs_hashes': [txid.hex(), ]}) + return self.rpc2( + "get_transactions", + { + "txs_hashes": [ + txid.hex(), + ] + }, + ) diff --git a/basicswap/js_server.py b/basicswap/js_server.py index 2c0fc39..7f9f16d 100644 --- a/basicswap/js_server.py +++ b/basicswap/js_server.py @@ -40,11 +40,11 @@ from .protocols.xmr_swap_1 import recoverNoScriptTxnWithKey, getChainBSplitKey def getFormData(post_string: str, is_json: bool): - if post_string == '': - raise ValueError('No post data') + if post_string == "": + raise ValueError("No post data") if is_json: form_data = json.loads(post_string) - form_data['is_json'] = True + form_data["is_json"] = True else: form_data = urllib.parse.parse_qs(post_string) return form_data @@ -52,39 +52,41 @@ def getFormData(post_string: str, is_json: bool): def withdraw_coin(swap_client, coin_type, post_string, is_json): post_data = getFormData(post_string, is_json) - address = get_data_entry(post_data, 'address') + address = get_data_entry(post_data, "address") if coin_type in (Coins.XMR, Coins.WOW): value = None - sweepall = get_data_entry(post_data, 'sweepall') + sweepall = get_data_entry(post_data, "sweepall") if not isinstance(sweepall, bool): sweepall = toBool(sweepall) if not sweepall: - value = get_data_entry(post_data, 'value') + value = get_data_entry(post_data, "value") else: - value = get_data_entry(post_data, 'value') - subfee = get_data_entry(post_data, 'subfee') + value = get_data_entry(post_data, "value") + subfee = get_data_entry(post_data, "subfee") if not isinstance(subfee, bool): subfee = toBool(subfee) if coin_type == Coins.PART: - type_from = get_data_entry_or(post_data, 'type_from', 'plain') - type_to = get_data_entry_or(post_data, 'type_to', 'plain') - txid_hex = swap_client.withdrawParticl(type_from, type_to, value, address, subfee) + type_from = get_data_entry_or(post_data, "type_from", "plain") + type_to = get_data_entry_or(post_data, "type_to", "plain") + txid_hex = swap_client.withdrawParticl( + type_from, type_to, value, address, subfee + ) elif coin_type == Coins.LTC: - type_from = get_data_entry_or(post_data, 'type_from', 'plain') + type_from = get_data_entry_or(post_data, "type_from", "plain") txid_hex = swap_client.withdrawLTC(type_from, value, address, subfee) elif coin_type in (Coins.XMR, Coins.WOW): txid_hex = swap_client.withdrawCoin(coin_type, value, address, sweepall) else: txid_hex = swap_client.withdrawCoin(coin_type, value, address, subfee) - return {'txid': txid_hex} + return {"txid": txid_hex} def js_error(self, error_str) -> bytes: - error_str_json = json.dumps({'error': error_str}) - return bytes(error_str_json, 'UTF-8') + error_str_json = json.dumps({"error": error_str}) + return bytes(error_str_json, "UTF-8") def js_coins(self, url_split, post_string, is_json) -> bytes: @@ -93,26 +95,26 @@ def js_coins(self, url_split, post_string, is_json) -> bytes: coins = [] for coin in Coins: cc = swap_client.coin_clients[coin] - coin_chainparams = chainparams[cc['coin']] - coin_active: bool = False if cc['connection_type'] == 'none' else True + coin_chainparams = chainparams[cc["coin"]] + coin_active: bool = False if cc["connection_type"] == "none" else True if coin == Coins.LTC_MWEB: coin_active = False entry = { - 'id': int(coin), - 'ticker': coin_chainparams['ticker'], - 'name': getCoinName(coin), - 'active': coin_active, - 'decimal_places': coin_chainparams['decimal_places'], + "id": int(coin), + "ticker": coin_chainparams["ticker"], + "name": getCoinName(coin), + "active": coin_active, + "decimal_places": coin_chainparams["decimal_places"], } if coin == Coins.PART_ANON: - entry['variant'] = 'Anon' + entry["variant"] = "Anon" elif coin == Coins.PART_BLIND: - entry['variant'] = 'Blind' + entry["variant"] = "Blind" elif coin == Coins.LTC_MWEB: - entry['variant'] = 'MWEB' + entry["variant"] = "MWEB" coins.append(entry) - return bytes(json.dumps(coins), 'UTF-8') + return bytes(json.dumps(coins), "UTF-8") def js_wallets(self, url_split, post_string, is_json): @@ -124,29 +126,43 @@ def js_wallets(self, url_split, post_string, is_json): if len(url_split) > 4: cmd = url_split[4] - if cmd == 'withdraw': - return bytes(json.dumps(withdraw_coin(swap_client, coin_type, post_string, is_json)), 'UTF-8') - elif cmd == 'nextdepositaddr': - return bytes(json.dumps(swap_client.cacheNewAddressForCoin(coin_type)), 'UTF-8') - elif cmd == 'createutxo': + if cmd == "withdraw": + return bytes( + json.dumps( + withdraw_coin(swap_client, coin_type, post_string, is_json) + ), + "UTF-8", + ) + elif cmd == "nextdepositaddr": + return bytes( + json.dumps(swap_client.cacheNewAddressForCoin(coin_type)), "UTF-8" + ) + elif cmd == "createutxo": post_data = getFormData(post_string, is_json) ci = swap_client.ci(coin_type) - value = ci.make_int(get_data_entry(post_data, 'value')) + value = ci.make_int(get_data_entry(post_data, "value")) txid_hex, new_addr = ci.createUTXO(value) - return bytes(json.dumps({'txid': txid_hex, 'address': new_addr}), 'UTF-8') - elif cmd == 'reseed': + return bytes( + json.dumps({"txid": txid_hex, "address": new_addr}), "UTF-8" + ) + elif cmd == "reseed": swap_client.reseedWallet(coin_type) - return bytes(json.dumps({'reseeded': True}), 'UTF-8') - elif cmd == 'newstealthaddress': + return bytes(json.dumps({"reseeded": True}), "UTF-8") + elif cmd == "newstealthaddress": if coin_type != Coins.PART: - raise ValueError('Invalid coin for command') - return bytes(json.dumps(swap_client.ci(coin_type).getNewStealthAddress()), 'UTF-8') - elif cmd == 'newmwebaddress': + raise ValueError("Invalid coin for command") + return bytes( + json.dumps(swap_client.ci(coin_type).getNewStealthAddress()), + "UTF-8", + ) + elif cmd == "newmwebaddress": if coin_type not in (Coins.LTC, Coins.LTC_MWEB): - raise ValueError('Invalid coin for command') - return bytes(json.dumps(swap_client.ci(coin_type).getNewMwebAddress()), 'UTF-8') + raise ValueError("Invalid coin for command") + return bytes( + json.dumps(swap_client.ci(coin_type).getNewMwebAddress()), "UTF-8" + ) - raise ValueError('Unknown command') + raise ValueError("Unknown command") if coin_type == Coins.LTC_MWEB: coin_type = Coins.LTC @@ -154,9 +170,9 @@ def js_wallets(self, url_split, post_string, is_json): rv.update(swap_client.getBlockchainInfo(coin_type)) ci = swap_client.ci(coin_type) checkAddressesOwned(swap_client, ci, rv) - return bytes(json.dumps(rv), 'UTF-8') + return bytes(json.dumps(rv), "UTF-8") - return bytes(json.dumps(swap_client.getWalletsInfo({'ticker_key': True})), 'UTF-8') + return bytes(json.dumps(swap_client.getWalletsInfo({"ticker_key": True})), "UTF-8") def js_offers(self, url_split, post_string, is_json, sent=False) -> bytes: @@ -164,52 +180,52 @@ def js_offers(self, url_split, post_string, is_json, sent=False) -> bytes: swap_client.checkSystemStatus() offer_id = None if len(url_split) > 3: - if url_split[3] == 'new': + if url_split[3] == "new": form_data = getFormData(post_string, is_json) offer_id = postNewOffer(swap_client, form_data) - rv = {'offer_id': offer_id.hex()} - return bytes(json.dumps(rv), 'UTF-8') + rv = {"offer_id": offer_id.hex()} + return bytes(json.dumps(rv), "UTF-8") offer_id = bytes.fromhex(url_split[3]) with_extra_info = False filters = { - 'coin_from': -1, - 'coin_to': -1, - 'page_no': 1, - 'limit': PAGE_LIMIT, - 'sort_by': 'created_at', - 'sort_dir': 'desc', + "coin_from": -1, + "coin_to": -1, + "page_no": 1, + "limit": PAGE_LIMIT, + "sort_by": "created_at", + "sort_dir": "desc", } if offer_id: - filters['offer_id'] = offer_id + filters["offer_id"] = offer_id - if post_string != '': + if post_string != "": post_data = getFormData(post_string, is_json) - filters['coin_from'] = setCoinFilter(post_data, 'coin_from') - filters['coin_to'] = setCoinFilter(post_data, 'coin_to') + filters["coin_from"] = setCoinFilter(post_data, "coin_from") + filters["coin_to"] = setCoinFilter(post_data, "coin_to") - if have_data_entry(post_data, 'sort_by'): - sort_by = get_data_entry(post_data, 'sort_by') - ensure(sort_by in ['created_at', 'rate'], 'Invalid sort by') - filters['sort_by'] = sort_by - if have_data_entry(post_data, 'sort_dir'): - sort_dir = get_data_entry(post_data, 'sort_dir') - ensure(sort_dir in ['asc', 'desc'], 'Invalid sort dir') - filters['sort_dir'] = sort_dir + if have_data_entry(post_data, "sort_by"): + sort_by = get_data_entry(post_data, "sort_by") + ensure(sort_by in ["created_at", "rate"], "Invalid sort by") + filters["sort_by"] = sort_by + if have_data_entry(post_data, "sort_dir"): + sort_dir = get_data_entry(post_data, "sort_dir") + ensure(sort_dir in ["asc", "desc"], "Invalid sort dir") + filters["sort_dir"] = sort_dir - if have_data_entry(post_data, 'offset'): - filters['offset'] = int(get_data_entry(post_data, 'offset')) - if have_data_entry(post_data, 'limit'): - filters['limit'] = int(get_data_entry(post_data, 'limit')) - ensure(filters['limit'] > 0, 'Invalid limit') - if have_data_entry(post_data, 'active'): - filters['active'] = get_data_entry(post_data, 'active') - if have_data_entry(post_data, 'include_sent'): - filters['include_sent'] = toBool(get_data_entry(post_data, 'include_sent')) + if have_data_entry(post_data, "offset"): + filters["offset"] = int(get_data_entry(post_data, "offset")) + if have_data_entry(post_data, "limit"): + filters["limit"] = int(get_data_entry(post_data, "limit")) + ensure(filters["limit"] > 0, "Invalid limit") + if have_data_entry(post_data, "active"): + filters["active"] = get_data_entry(post_data, "active") + if have_data_entry(post_data, "include_sent"): + filters["include_sent"] = toBool(get_data_entry(post_data, "include_sent")) - if have_data_entry(post_data, 'with_extra_info'): - with_extra_info = toBool(get_data_entry(post_data, 'with_extra_info')) + if have_data_entry(post_data, "with_extra_info"): + with_extra_info = toBool(get_data_entry(post_data, "with_extra_info")) offers = swap_client.listOffers(sent, filters) rv = [] @@ -217,38 +233,40 @@ def js_offers(self, url_split, post_string, is_json, sent=False) -> bytes: ci_from = swap_client.ci(o.coin_from) ci_to = swap_client.ci(o.coin_to) offer_data = { - 'swap_type': o.swap_type, - 'addr_from': o.addr_from, - 'addr_to': o.addr_to, - 'offer_id': o.offer_id.hex(), - 'created_at': o.created_at, - 'expire_at': o.expire_at, - 'coin_from': ci_from.coin_name(), - 'coin_to': ci_to.coin_name(), - 'amount_from': ci_from.format_amount(o.amount_from), - 'amount_to': ci_to.format_amount((o.amount_from * o.rate) // ci_from.COIN()), - 'rate': ci_to.format_amount(o.rate), - 'min_bid_amount': ci_from.format_amount(o.min_bid_amount), - 'is_expired': o.expire_at <= swap_client.getTime(), - 'is_own_offer': o.was_sent, - 'is_revoked': True if o.active_ind == 2 else False, + "swap_type": o.swap_type, + "addr_from": o.addr_from, + "addr_to": o.addr_to, + "offer_id": o.offer_id.hex(), + "created_at": o.created_at, + "expire_at": o.expire_at, + "coin_from": ci_from.coin_name(), + "coin_to": ci_to.coin_name(), + "amount_from": ci_from.format_amount(o.amount_from), + "amount_to": ci_to.format_amount( + (o.amount_from * o.rate) // ci_from.COIN() + ), + "rate": ci_to.format_amount(o.rate), + "min_bid_amount": ci_from.format_amount(o.min_bid_amount), + "is_expired": o.expire_at <= swap_client.getTime(), + "is_own_offer": o.was_sent, + "is_revoked": True if o.active_ind == 2 else False, } if with_extra_info: - offer_data['amount_negotiable'] = o.amount_negotiable - offer_data['rate_negotiable'] = o.rate_negotiable + offer_data["amount_negotiable"] = o.amount_negotiable + offer_data["rate_negotiable"] = o.rate_negotiable if o.swap_type == SwapTypes.XMR_SWAP: _, xmr_offer = swap_client.getXmrOffer(o.offer_id) - offer_data['lock_time_1'] = xmr_offer.lock_time_1 - offer_data['lock_time_2'] = xmr_offer.lock_time_2 + offer_data["lock_time_1"] = xmr_offer.lock_time_1 + offer_data["lock_time_2"] = xmr_offer.lock_time_2 - offer_data['feerate_from'] = xmr_offer.a_fee_rate - offer_data['feerate_to'] = xmr_offer.b_fee_rate + offer_data["feerate_from"] = xmr_offer.a_fee_rate + offer_data["feerate_to"] = xmr_offer.b_fee_rate else: - offer_data['feerate_from'] = o.from_feerate - offer_data['feerate_to'] = o.to_feerate + offer_data["feerate_from"] = o.from_feerate + offer_data["feerate_to"] = o.to_feerate rv.append(offer_data) - return bytes(json.dumps(rv), 'UTF-8') + return bytes(json.dumps(rv), "UTF-8") def js_sentoffers(self, url_split, post_string, is_json) -> bytes: @@ -259,157 +277,211 @@ def parseBidFilters(post_data): offer_id = None filters = {} - if have_data_entry(post_data, 'offer_id'): - offer_id = bytes.fromhex(get_data_entry(post_data, 'offer_id')) - assert (len(offer_id) == 28) + if have_data_entry(post_data, "offer_id"): + offer_id = bytes.fromhex(get_data_entry(post_data, "offer_id")) + assert len(offer_id) == 28 - if have_data_entry(post_data, 'sort_by'): - sort_by = get_data_entry(post_data, 'sort_by') - assert (sort_by in ['created_at', ]), 'Invalid sort by' - filters['sort_by'] = sort_by - if have_data_entry(post_data, 'sort_dir'): - sort_dir = get_data_entry(post_data, 'sort_dir') - assert (sort_dir in ['asc', 'desc']), 'Invalid sort dir' - filters['sort_dir'] = sort_dir + if have_data_entry(post_data, "sort_by"): + sort_by = get_data_entry(post_data, "sort_by") + assert sort_by in [ + "created_at", + ], "Invalid sort by" + filters["sort_by"] = sort_by + if have_data_entry(post_data, "sort_dir"): + sort_dir = get_data_entry(post_data, "sort_dir") + assert sort_dir in ["asc", "desc"], "Invalid sort dir" + filters["sort_dir"] = sort_dir - if have_data_entry(post_data, 'offset'): - filters['offset'] = int(get_data_entry(post_data, 'offset')) - if have_data_entry(post_data, 'limit'): - filters['limit'] = int(get_data_entry(post_data, 'limit')) - assert (filters['limit'] > 0 and filters['limit'] <= PAGE_LIMIT), 'Invalid limit' + if have_data_entry(post_data, "offset"): + filters["offset"] = int(get_data_entry(post_data, "offset")) + if have_data_entry(post_data, "limit"): + filters["limit"] = int(get_data_entry(post_data, "limit")) + assert filters["limit"] > 0 and filters["limit"] <= PAGE_LIMIT, "Invalid limit" - if have_data_entry(post_data, 'with_available_or_active'): - filters['with_available_or_active'] = toBool(get_data_entry(post_data, 'with_available_or_active')) - elif have_data_entry(post_data, 'with_expired'): - filters['with_expired'] = toBool(get_data_entry(post_data, 'with_expired')) + if have_data_entry(post_data, "with_available_or_active"): + filters["with_available_or_active"] = toBool( + get_data_entry(post_data, "with_available_or_active") + ) + elif have_data_entry(post_data, "with_expired"): + filters["with_expired"] = toBool(get_data_entry(post_data, "with_expired")) - if have_data_entry(post_data, 'with_extra_info'): - filters['with_extra_info'] = toBool(get_data_entry(post_data, 'with_extra_info')) + if have_data_entry(post_data, "with_extra_info"): + filters["with_extra_info"] = toBool( + get_data_entry(post_data, "with_extra_info") + ) return offer_id, filters def formatBids(swap_client, bids, filters) -> bytes: - with_extra_info = filters.get('with_extra_info', False) + with_extra_info = filters.get("with_extra_info", False) rv = [] for b in bids: bid_data = { - 'bid_id': b[2].hex(), - 'offer_id': b[3].hex(), - 'created_at': b[0], - 'expire_at': b[1], - 'coin_from': b[9], - 'amount_from': swap_client.ci(b[9]).format_amount(b[4]), - 'bid_rate': swap_client.ci(b[14]).format_amount(b[10]), - 'bid_state': strBidState(b[5]) + "bid_id": b[2].hex(), + "offer_id": b[3].hex(), + "created_at": b[0], + "expire_at": b[1], + "coin_from": b[9], + "amount_from": swap_client.ci(b[9]).format_amount(b[4]), + "bid_rate": swap_client.ci(b[14]).format_amount(b[10]), + "bid_state": strBidState(b[5]), } if with_extra_info: - bid_data['addr_from'] = b[11] + bid_data["addr_from"] = b[11] rv.append(bid_data) - return bytes(json.dumps(rv), 'UTF-8') + return bytes(json.dumps(rv), "UTF-8") def js_bids(self, url_split, post_string: str, is_json: bool) -> bytes: swap_client = self.server.swap_client swap_client.checkSystemStatus() if len(url_split) > 3: - if url_split[3] == 'new': + if url_split[3] == "new": post_data = getFormData(post_string, is_json) - offer_id = bytes.fromhex(get_data_entry(post_data, 'offer_id')) - assert (len(offer_id) == 28) + offer_id = bytes.fromhex(get_data_entry(post_data, "offer_id")) + assert len(offer_id) == 28 offer = swap_client.getOffer(offer_id) - assert (offer), 'Offer not found.' + assert offer, "Offer not found." ci_from = swap_client.ci(offer.coin_from) ci_to = swap_client.ci(offer.coin_to) - amount_from = inputAmount(get_data_entry(post_data, 'amount_from'), ci_from) + amount_from = inputAmount(get_data_entry(post_data, "amount_from"), ci_from) addr_from = None - if have_data_entry(post_data, 'addr_from'): - addr_from = get_data_entry(post_data, 'addr_from') - if addr_from == '-1': + if have_data_entry(post_data, "addr_from"): + addr_from = get_data_entry(post_data, "addr_from") + if addr_from == "-1": addr_from = None - if have_data_entry(post_data, 'validmins'): - valid_for_seconds = int(get_data_entry(post_data, 'validmins')) * 60 - elif have_data_entry(post_data, 'valid_for_seconds'): - valid_for_seconds = int(get_data_entry(post_data, 'valid_for_seconds')) + if have_data_entry(post_data, "validmins"): + valid_for_seconds = int(get_data_entry(post_data, "validmins")) * 60 + elif have_data_entry(post_data, "valid_for_seconds"): + valid_for_seconds = int(get_data_entry(post_data, "valid_for_seconds")) else: valid_for_seconds = 10 * 60 extra_options = { - 'valid_for_seconds': valid_for_seconds, + "valid_for_seconds": valid_for_seconds, } - if have_data_entry(post_data, 'amount_to'): - extra_options['amount_to'] = inputAmount(get_data_entry(post_data, 'amount_to'), ci_to) - elif have_data_entry(post_data, 'bid_rate'): - extra_options['bid_rate'] = ci_to.make_int(get_data_entry(post_data, 'bid_rate'), r=1) - if have_data_entry(post_data, 'bid_amount'): - amount_from = inputAmount(get_data_entry(post_data, 'bid_amount'), ci_from) + if have_data_entry(post_data, "amount_to"): + extra_options["amount_to"] = inputAmount( + get_data_entry(post_data, "amount_to"), ci_to + ) + elif have_data_entry(post_data, "bid_rate"): + extra_options["bid_rate"] = ci_to.make_int( + get_data_entry(post_data, "bid_rate"), r=1 + ) + if have_data_entry(post_data, "bid_amount"): + amount_from = inputAmount( + get_data_entry(post_data, "bid_amount"), ci_from + ) if offer.swap_type == SwapTypes.XMR_SWAP: - bid_id = swap_client.postXmrBid(offer_id, amount_from, addr_send_from=addr_from, extra_options=extra_options) + bid_id = swap_client.postXmrBid( + offer_id, + amount_from, + addr_send_from=addr_from, + extra_options=extra_options, + ) else: - bid_id = swap_client.postBid(offer_id, amount_from, addr_send_from=addr_from, extra_options=extra_options) + bid_id = swap_client.postBid( + offer_id, + amount_from, + addr_send_from=addr_from, + extra_options=extra_options, + ) - if have_data_entry(post_data, 'debugind'): - swap_client.setBidDebugInd(bid_id, int(get_data_entry(post_data, 'debugind'))) + if have_data_entry(post_data, "debugind"): + swap_client.setBidDebugInd( + bid_id, int(get_data_entry(post_data, "debugind")) + ) - rv = {'bid_id': bid_id.hex()} - return bytes(json.dumps(rv), 'UTF-8') + rv = {"bid_id": bid_id.hex()} + return bytes(json.dumps(rv), "UTF-8") bid_id = bytes.fromhex(url_split[3]) - assert (len(bid_id) == 28) + assert len(bid_id) == 28 show_txns: bool = False with_events: bool = False - if post_string != '': + if post_string != "": post_data = getFormData(post_string, is_json) - if have_data_entry(post_data, 'accept'): + if have_data_entry(post_data, "accept"): swap_client.acceptBid(bid_id) - elif have_data_entry(post_data, 'abandon'): + elif have_data_entry(post_data, "abandon"): swap_client.abandonBid(bid_id) - elif have_data_entry(post_data, 'debugind'): - swap_client.setBidDebugInd(bid_id, int(get_data_entry(post_data, 'debugind'))) + elif have_data_entry(post_data, "debugind"): + swap_client.setBidDebugInd( + bid_id, int(get_data_entry(post_data, "debugind")) + ) - if have_data_entry(post_data, 'show_extra'): + if have_data_entry(post_data, "show_extra"): show_txns = True - if have_data_entry(post_data, 'with_events'): + if have_data_entry(post_data, "with_events"): with_events = True bid, xmr_swap, offer, xmr_offer, events = swap_client.getXmrBidAndOffer(bid_id) - assert (bid), 'Unknown bid ID' + assert bid, "Unknown bid ID" - if post_string != '': - if have_data_entry(post_data, 'chainbkeysplit'): - return bytes(json.dumps({'splitkey': getChainBSplitKey(swap_client, bid, xmr_swap, offer)}), 'UTF-8') - elif have_data_entry(post_data, 'spendchainblocktx'): - remote_key = get_data_entry(post_data, 'remote_key') - return bytes(json.dumps({'txid': recoverNoScriptTxnWithKey(swap_client, bid_id, remote_key).hex()}), 'UTF-8') + if post_string != "": + if have_data_entry(post_data, "chainbkeysplit"): + return bytes( + json.dumps( + { + "splitkey": getChainBSplitKey( + swap_client, bid, xmr_swap, offer + ) + } + ), + "UTF-8", + ) + elif have_data_entry(post_data, "spendchainblocktx"): + remote_key = get_data_entry(post_data, "remote_key") + return bytes( + json.dumps( + { + "txid": recoverNoScriptTxnWithKey( + swap_client, bid_id, remote_key + ).hex() + } + ), + "UTF-8", + ) - if len(url_split) > 4 and url_split[4] == 'states': + if len(url_split) > 4 and url_split[4] == "states": old_states = listOldBidStates(bid) if with_events: new_list = [] for entry in old_states: entry_list = list(entry) - entry_list.insert(1, 'state') + entry_list.insert(1, "state") new_list.append(entry_list) for event in events: - new_list.append([event['at'], 'event', event['desc']]) + new_list.append([event["at"], "event", event["desc"]]) old_states = sorted(new_list, key=lambda x: x[0]) - return bytes(json.dumps(old_states), 'UTF-8') + return bytes(json.dumps(old_states), "UTF-8") edit_bid = False - data = describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, events, edit_bid, show_txns, for_api=True) - return bytes(json.dumps(data), 'UTF-8') + data = describeBid( + swap_client, + bid, + xmr_swap, + offer, + xmr_offer, + events, + edit_bid, + show_txns, + for_api=True, + ) + return bytes(json.dumps(data), "UTF-8") - post_data = {} if post_string == '' else getFormData(post_string, is_json) + post_data = {} if post_string == "" else getFormData(post_string, is_json) offer_id, filters = parseBidFilters(post_data) bids = swap_client.listBids(offer_id=offer_id, filters=filters) @@ -429,131 +501,154 @@ def js_sentbids(self, url_split, post_string, is_json) -> bytes: def js_network(self, url_split, post_string, is_json) -> bytes: swap_client = self.server.swap_client swap_client.checkSystemStatus() - return bytes(json.dumps(swap_client.get_network_info()), 'UTF-8') + return bytes(json.dumps(swap_client.get_network_info()), "UTF-8") def js_revokeoffer(self, url_split, post_string, is_json) -> bytes: swap_client = self.server.swap_client swap_client.checkSystemStatus() offer_id = bytes.fromhex(url_split[3]) - assert (len(offer_id) == 28) + assert len(offer_id) == 28 swap_client.revokeOffer(offer_id) - return bytes(json.dumps({'revoked_offer': offer_id.hex()}), 'UTF-8') + return bytes(json.dumps({"revoked_offer": offer_id.hex()}), "UTF-8") def js_smsgaddresses(self, url_split, post_string, is_json) -> bytes: swap_client = self.server.swap_client swap_client.checkSystemStatus() - post_data = {} if post_string == '' else getFormData(post_string, is_json) + post_data = {} if post_string == "" else getFormData(post_string, is_json) if len(url_split) > 3: mode: str = url_split[3] - if mode == 'new': - addressnote = get_data_entry_or(post_data, 'addressnote', '') + if mode == "new": + addressnote = get_data_entry_or(post_data, "addressnote", "") new_addr, pubkey = swap_client.newSMSGAddress(addressnote=addressnote) - return bytes(json.dumps({'new_address': new_addr, 'pubkey': pubkey}), 'UTF-8') - if mode == 'add': - addressnote = get_data_entry_or(post_data, 'addressnote', '') - pubkey_hex = get_data_entry(post_data, 'addresspubkey') + return bytes( + json.dumps({"new_address": new_addr, "pubkey": pubkey}), "UTF-8" + ) + if mode == "add": + addressnote = get_data_entry_or(post_data, "addressnote", "") + pubkey_hex = get_data_entry(post_data, "addresspubkey") added_address = swap_client.addSMSGAddress(pubkey_hex, addressnote) - return bytes(json.dumps({'added_address': added_address, 'pubkey': pubkey_hex}), 'UTF-8') - elif mode == 'edit': - address = get_data_entry(post_data, 'address') - activeind = int(get_data_entry(post_data, 'active_ind')) - addressnote = get_data_entry_or(post_data, 'addressnote', '') + return bytes( + json.dumps({"added_address": added_address, "pubkey": pubkey_hex}), + "UTF-8", + ) + elif mode == "edit": + address = get_data_entry(post_data, "address") + activeind = int(get_data_entry(post_data, "active_ind")) + addressnote = get_data_entry_or(post_data, "addressnote", "") new_addr = swap_client.editSMSGAddress(address, activeind, addressnote) - return bytes(json.dumps({'edited_address': address}), 'UTF-8') - elif mode == 'disableall': + return bytes(json.dumps({"edited_address": address}), "UTF-8") + elif mode == "disableall": rv = swap_client.disableAllSMSGAddresses() - return bytes(json.dumps(rv), 'UTF-8') + return bytes(json.dumps(rv), "UTF-8") filters = { - 'exclude_inactive': post_data.get('exclude_inactive', True), + "exclude_inactive": post_data.get("exclude_inactive", True), } - return bytes(json.dumps(swap_client.listAllSMSGAddresses(filters)), 'UTF-8') + return bytes(json.dumps(swap_client.listAllSMSGAddresses(filters)), "UTF-8") def js_rates(self, url_split, post_string, is_json) -> bytes: post_data = getFormData(post_string, is_json) sc = self.server.swap_client - coin_from = get_data_entry(post_data, 'coin_from') - coin_to = get_data_entry(post_data, 'coin_to') - return bytes(json.dumps(sc.lookupRates(coin_from, coin_to)), 'UTF-8') + coin_from = get_data_entry(post_data, "coin_from") + coin_to = get_data_entry(post_data, "coin_to") + return bytes(json.dumps(sc.lookupRates(coin_from, coin_to)), "UTF-8") def js_rates_list(self, url_split, query_string, is_json) -> bytes: get_data = urllib.parse.parse_qs(query_string) sc = self.server.swap_client - coin_from = getCoinType(get_data['from'][0]) - coin_to = getCoinType(get_data['to'][0]) - return bytes(json.dumps(sc.lookupRates(coin_from, coin_to, True)), 'UTF-8') + coin_from = getCoinType(get_data["from"][0]) + coin_to = getCoinType(get_data["to"][0]) + return bytes(json.dumps(sc.lookupRates(coin_from, coin_to, True)), "UTF-8") def js_rate(self, url_split, post_string, is_json) -> bytes: post_data = getFormData(post_string, is_json) sc = self.server.swap_client - coin_from = getCoinType(get_data_entry(post_data, 'coin_from')) + coin_from = getCoinType(get_data_entry(post_data, "coin_from")) ci_from = sc.ci(coin_from) - coin_to = getCoinType(get_data_entry(post_data, 'coin_to')) + coin_to = getCoinType(get_data_entry(post_data, "coin_to")) ci_to = sc.ci(coin_to) # Set amount to if rate is provided - rate = get_data_entry_or(post_data, 'rate', None) + rate = get_data_entry_or(post_data, "rate", None) if rate is not None: - amt_from_str = get_data_entry_or(post_data, 'amt_from', None) - amt_to_str = get_data_entry_or(post_data, 'amt_to', None) + amt_from_str = get_data_entry_or(post_data, "amt_from", None) + amt_to_str = get_data_entry_or(post_data, "amt_to", None) if amt_from_str is not None: rate = ci_to.make_int(rate, r=1) amt_from = inputAmount(amt_from_str, ci_from) - amount_to = ci_to.format_amount(int((amt_from * rate) // ci_from.COIN()), r=1) - return bytes(json.dumps({'amount_to': amount_to}), 'UTF-8') + amount_to = ci_to.format_amount( + int((amt_from * rate) // ci_from.COIN()), r=1 + ) + return bytes(json.dumps({"amount_to": amount_to}), "UTF-8") if amt_to_str is not None: rate = ci_from.make_int(1.0 / float(rate), r=1) amt_to = inputAmount(amt_to_str, ci_to) - amount_from = ci_from.format_amount(int((amt_to * rate) // ci_to.COIN()), r=1) - return bytes(json.dumps({'amount_from': amount_from}), 'UTF-8') + amount_from = ci_from.format_amount( + int((amt_to * rate) // ci_to.COIN()), r=1 + ) + return bytes(json.dumps({"amount_from": amount_from}), "UTF-8") - amt_from: int = inputAmount(get_data_entry(post_data, 'amt_from'), ci_from) - amt_to: int = inputAmount(get_data_entry(post_data, 'amt_to'), ci_to) + amt_from: int = inputAmount(get_data_entry(post_data, "amt_from"), ci_from) + amt_to: int = inputAmount(get_data_entry(post_data, "amt_to"), ci_to) rate: int = ci_to.format_amount(ci_from.make_int(amt_to / amt_from, r=1)) - return bytes(json.dumps({'rate': rate}), 'UTF-8') + return bytes(json.dumps({"rate": rate}), "UTF-8") def js_index(self, url_split, post_string, is_json) -> bytes: swap_client = self.server.swap_client swap_client.checkSystemStatus() - return bytes(json.dumps(swap_client.getSummary()), 'UTF-8') + return bytes(json.dumps(swap_client.getSummary()), "UTF-8") def js_generatenotification(self, url_split, post_string, is_json) -> bytes: swap_client = self.server.swap_client if not swap_client.debug: - raise ValueError('Debug mode not active.') + raise ValueError("Debug mode not active.") r = random.randint(0, 3) if r == 0: - swap_client.notify(NT.OFFER_RECEIVED, {'offer_id': random.randbytes(28).hex()}) + swap_client.notify(NT.OFFER_RECEIVED, {"offer_id": random.randbytes(28).hex()}) elif r == 1: - swap_client.notify(NT.BID_RECEIVED, {'type': 'atomic', 'bid_id': random.randbytes(28).hex(), 'offer_id': random.randbytes(28).hex()}) + swap_client.notify( + NT.BID_RECEIVED, + { + "type": "atomic", + "bid_id": random.randbytes(28).hex(), + "offer_id": random.randbytes(28).hex(), + }, + ) elif r == 2: - swap_client.notify(NT.BID_ACCEPTED, {'bid_id': random.randbytes(28).hex()}) + swap_client.notify(NT.BID_ACCEPTED, {"bid_id": random.randbytes(28).hex()}) elif r == 3: - swap_client.notify(NT.BID_RECEIVED, {'type': 'ads', 'bid_id': random.randbytes(28).hex(), 'offer_id': random.randbytes(28).hex()}) + swap_client.notify( + NT.BID_RECEIVED, + { + "type": "ads", + "bid_id": random.randbytes(28).hex(), + "offer_id": random.randbytes(28).hex(), + }, + ) - return bytes(json.dumps({'type': r}), 'UTF-8') + return bytes(json.dumps({"type": r}), "UTF-8") def js_notifications(self, url_split, post_string, is_json) -> bytes: swap_client = self.server.swap_client swap_client.checkSystemStatus() - return bytes(json.dumps(swap_client.getNotifications()), 'UTF-8') + return bytes(json.dumps(swap_client.getNotifications()), "UTF-8") def js_identities(self, url_split, post_string: str, is_json: bool) -> bytes: @@ -561,49 +656,55 @@ def js_identities(self, url_split, post_string: str, is_json: bool) -> bytes: swap_client.checkSystemStatus() filters = { - 'page_no': 1, - 'limit': PAGE_LIMIT, - 'sort_by': 'created_at', - 'sort_dir': 'desc', + "page_no": 1, + "limit": PAGE_LIMIT, + "sort_by": "created_at", + "sort_dir": "desc", } if len(url_split) > 3: address = url_split[3] - filters['address'] = address + filters["address"] = address - if post_string != '': + if post_string != "": post_data = getFormData(post_string, is_json) - if have_data_entry(post_data, 'sort_by'): - sort_by = get_data_entry(post_data, 'sort_by') - assert (sort_by in ['created_at', 'rate']), 'Invalid sort by' - filters['sort_by'] = sort_by - if have_data_entry(post_data, 'sort_dir'): - sort_dir = get_data_entry(post_data, 'sort_dir') - assert (sort_dir in ['asc', 'desc']), 'Invalid sort dir' - filters['sort_dir'] = sort_dir + if have_data_entry(post_data, "sort_by"): + sort_by = get_data_entry(post_data, "sort_by") + assert sort_by in ["created_at", "rate"], "Invalid sort by" + filters["sort_by"] = sort_by + if have_data_entry(post_data, "sort_dir"): + sort_dir = get_data_entry(post_data, "sort_dir") + assert sort_dir in ["asc", "desc"], "Invalid sort dir" + filters["sort_dir"] = sort_dir - if have_data_entry(post_data, 'offset'): - filters['offset'] = int(get_data_entry(post_data, 'offset')) - if have_data_entry(post_data, 'limit'): - filters['limit'] = int(get_data_entry(post_data, 'limit')) - assert (filters['limit'] > 0 and filters['limit'] <= PAGE_LIMIT), 'Invalid limit' + if have_data_entry(post_data, "offset"): + filters["offset"] = int(get_data_entry(post_data, "offset")) + if have_data_entry(post_data, "limit"): + filters["limit"] = int(get_data_entry(post_data, "limit")) + assert ( + filters["limit"] > 0 and filters["limit"] <= PAGE_LIMIT + ), "Invalid limit" set_data = {} - if have_data_entry(post_data, 'set_label'): - set_data['label'] = get_data_entry(post_data, 'set_label') - if have_data_entry(post_data, 'set_automation_override'): - set_data['automation_override'] = get_data_entry(post_data, 'set_automation_override') - if have_data_entry(post_data, 'set_visibility_override'): - set_data['visibility_override'] = get_data_entry(post_data, 'set_visibility_override') - if have_data_entry(post_data, 'set_note'): - set_data['note'] = get_data_entry(post_data, 'set_note') + if have_data_entry(post_data, "set_label"): + set_data["label"] = get_data_entry(post_data, "set_label") + if have_data_entry(post_data, "set_automation_override"): + set_data["automation_override"] = get_data_entry( + post_data, "set_automation_override" + ) + if have_data_entry(post_data, "set_visibility_override"): + set_data["visibility_override"] = get_data_entry( + post_data, "set_visibility_override" + ) + if have_data_entry(post_data, "set_note"): + set_data["note"] = get_data_entry(post_data, "set_note") if set_data: - ensure('address' in filters, 'Must provide an address to modify data') + ensure("address" in filters, "Must provide an address to modify data") swap_client.setIdentityData(filters, set_data) - return bytes(json.dumps(swap_client.listIdentities(filters)), 'UTF-8') + return bytes(json.dumps(swap_client.listIdentities(filters)), "UTF-8") def js_automationstrategies(self, url_split, post_string: str, is_json: bool) -> bytes: @@ -611,49 +712,51 @@ def js_automationstrategies(self, url_split, post_string: str, is_json: bool) -> swap_client.checkSystemStatus() filters = { - 'page_no': 1, - 'limit': PAGE_LIMIT, - 'sort_by': 'created_at', - 'sort_dir': 'desc', + "page_no": 1, + "limit": PAGE_LIMIT, + "sort_by": "created_at", + "sort_dir": "desc", } - if post_string != '': + if post_string != "": post_data = getFormData(post_string, is_json) - if have_data_entry(post_data, 'sort_by'): - sort_by = get_data_entry(post_data, 'sort_by') - assert (sort_by in ['created_at', 'rate']), 'Invalid sort by' - filters['sort_by'] = sort_by - if have_data_entry(post_data, 'sort_dir'): - sort_dir = get_data_entry(post_data, 'sort_dir') - assert (sort_dir in ['asc', 'desc']), 'Invalid sort dir' - filters['sort_dir'] = sort_dir + if have_data_entry(post_data, "sort_by"): + sort_by = get_data_entry(post_data, "sort_by") + assert sort_by in ["created_at", "rate"], "Invalid sort by" + filters["sort_by"] = sort_by + if have_data_entry(post_data, "sort_dir"): + sort_dir = get_data_entry(post_data, "sort_dir") + assert sort_dir in ["asc", "desc"], "Invalid sort dir" + filters["sort_dir"] = sort_dir - if have_data_entry(post_data, 'offset'): - filters['offset'] = int(get_data_entry(post_data, 'offset')) - if have_data_entry(post_data, 'limit'): - filters['limit'] = int(get_data_entry(post_data, 'limit')) - assert (filters['limit'] > 0 and filters['limit'] <= PAGE_LIMIT), 'Invalid limit' + if have_data_entry(post_data, "offset"): + filters["offset"] = int(get_data_entry(post_data, "offset")) + if have_data_entry(post_data, "limit"): + filters["limit"] = int(get_data_entry(post_data, "limit")) + assert ( + filters["limit"] > 0 and filters["limit"] <= PAGE_LIMIT + ), "Invalid limit" if len(url_split) > 3: strat_id = int(url_split[3]) strat_data = swap_client.getAutomationStrategy(strat_id) rv = { - 'record_id': strat_data.record_id, - 'label': strat_data.label, - 'type_ind': strat_data.type_ind, - 'only_known_identities': strat_data.only_known_identities, - 'num_concurrent': strat_data.num_concurrent, - 'data': json.loads(strat_data.data.decode('utf-8')), - 'note': '' if strat_data.note is None else strat_data.note, + "record_id": strat_data.record_id, + "label": strat_data.label, + "type_ind": strat_data.type_ind, + "only_known_identities": strat_data.only_known_identities, + "num_concurrent": strat_data.num_concurrent, + "data": json.loads(strat_data.data.decode("utf-8")), + "note": "" if strat_data.note is None else strat_data.note, } - return bytes(json.dumps(rv), 'UTF-8') + return bytes(json.dumps(rv), "UTF-8") rv = [] strats = swap_client.listAutomationStrategies(filters) for row in strats: rv.append((row[0], row[1], row[2])) - return bytes(json.dumps(rv), 'UTF-8') + return bytes(json.dumps(rv), "UTF-8") def js_validateamount(self, url_split, post_string: str, is_json: bool) -> bytes: @@ -662,25 +765,27 @@ def js_validateamount(self, url_split, post_string: str, is_json: bool) -> bytes post_data = getFormData(post_string, is_json) - ticker_str = post_data['coin'] - amount = post_data['amount'] - round_method = post_data.get('method', 'none') + ticker_str = post_data["coin"] + amount = post_data["amount"] + round_method = post_data.get("method", "none") - valid_round_methods = ('roundoff', 'rounddown', 'none') + valid_round_methods = ("roundoff", "rounddown", "none") if round_method not in valid_round_methods: - raise ValueError(f'Unknown rounding method, must be one of {valid_round_methods}') + raise ValueError( + f"Unknown rounding method, must be one of {valid_round_methods}" + ) coin_type = tickerToCoinId(ticker_str) ci = swap_client.ci(coin_type) r = 0 - if round_method == 'roundoff': + if round_method == "roundoff": r = 1 - elif round_method == 'rounddown': + elif round_method == "rounddown": r = -1 output_amount = ci.format_amount(amount, conv_int=True, r=r) - return bytes(json.dumps(output_amount), 'UTF-8') + return bytes(json.dumps(output_amount), "UTF-8") def js_vacuumdb(self, url_split, post_string, is_json) -> bytes: @@ -688,7 +793,7 @@ def js_vacuumdb(self, url_split, post_string, is_json) -> bytes: swap_client.checkSystemStatus() swap_client.vacuumDB() - return bytes(json.dumps({'completed': True}), 'UTF-8') + return bytes(json.dumps({"completed": True}), "UTF-8") def js_getcoinseed(self, url_split, post_string, is_json) -> bytes: @@ -696,20 +801,35 @@ def js_getcoinseed(self, url_split, post_string, is_json) -> bytes: swap_client.checkSystemStatus() post_data = getFormData(post_string, is_json) - coin = getCoinType(get_data_entry(post_data, 'coin')) + coin = getCoinType(get_data_entry(post_data, "coin")) if coin in (Coins.PART, Coins.PART_ANON, Coins.PART_BLIND): - raise ValueError('Particl wallet seed is set from the Basicswap mnemonic.') + raise ValueError("Particl wallet seed is set from the Basicswap mnemonic.") ci = swap_client.ci(coin) if coin in (Coins.XMR, Coins.WOW): key_view = swap_client.getWalletKey(coin, 1, for_ed25519=True) key_spend = swap_client.getWalletKey(coin, 2, for_ed25519=True) address = ci.getAddressFromKeys(key_view, key_spend) - return bytes(json.dumps({'coin': ci.ticker(), 'key_view': ci.encodeKey(key_view), 'key_spend': ci.encodeKey(key_spend), 'address': address}), 'UTF-8') + return bytes( + json.dumps( + { + "coin": ci.ticker(), + "key_view": ci.encodeKey(key_view), + "key_spend": ci.encodeKey(key_spend), + "address": address, + } + ), + "UTF-8", + ) seed_key = swap_client.getWalletKey(coin, 1) seed_id = ci.getSeedHash(seed_key) - return bytes(json.dumps({'coin': ci.ticker(), 'seed': seed_key.hex(), 'seed_id': seed_id.hex()}), 'UTF-8') + return bytes( + json.dumps( + {"coin": ci.ticker(), "seed": seed_key.hex(), "seed_id": seed_id.hex()} + ), + "UTF-8", + ) def js_setpassword(self, url_split, post_string, is_json) -> bytes: @@ -719,56 +839,56 @@ def js_setpassword(self, url_split, post_string, is_json) -> bytes: swap_client = self.server.swap_client post_data = getFormData(post_string, is_json) - old_password = get_data_entry(post_data, 'oldpassword') - new_password = get_data_entry(post_data, 'newpassword') + old_password = get_data_entry(post_data, "oldpassword") + new_password = get_data_entry(post_data, "newpassword") - if have_data_entry(post_data, 'coin'): + if have_data_entry(post_data, "coin"): # Set password for one coin - coin = getCoinType(get_data_entry(post_data, 'coin')) + coin = getCoinType(get_data_entry(post_data, "coin")) if coin in (Coins.PART_ANON, Coins.PART_BLIND, Coins.LTC_MWEB): - raise ValueError('Invalid coin.') + raise ValueError("Invalid coin.") swap_client.changeWalletPasswords(old_password, new_password, coin) - return bytes(json.dumps({'success': True}), 'UTF-8') + return bytes(json.dumps({"success": True}), "UTF-8") # Set password for all coins swap_client.changeWalletPasswords(old_password, new_password) - return bytes(json.dumps({'success': True}), 'UTF-8') + return bytes(json.dumps({"success": True}), "UTF-8") def js_unlock(self, url_split, post_string, is_json) -> bytes: swap_client = self.server.swap_client post_data = getFormData(post_string, is_json) - password = get_data_entry(post_data, 'password') + password = get_data_entry(post_data, "password") - if have_data_entry(post_data, 'coin'): - coin = getCoinType(str(get_data_entry(post_data, 'coin'))) + if have_data_entry(post_data, "coin"): + coin = getCoinType(str(get_data_entry(post_data, "coin"))) if coin in (Coins.PART_ANON, Coins.PART_BLIND): - raise ValueError('Invalid coin.') + raise ValueError("Invalid coin.") swap_client.unlockWallets(password, coin) - return bytes(json.dumps({'success': True}), 'UTF-8') + return bytes(json.dumps({"success": True}), "UTF-8") swap_client.unlockWallets(password) - return bytes(json.dumps({'success': True}), 'UTF-8') + return bytes(json.dumps({"success": True}), "UTF-8") def js_lock(self, url_split, post_string, is_json) -> bytes: swap_client = self.server.swap_client - post_data = {} if post_string == '' else getFormData(post_string, is_json) + post_data = {} if post_string == "" else getFormData(post_string, is_json) - if have_data_entry(post_data, 'coin'): - coin = getCoinType(get_data_entry(post_data, 'coin')) + if have_data_entry(post_data, "coin"): + coin = getCoinType(get_data_entry(post_data, "coin")) if coin in (Coins.PART_ANON, Coins.PART_BLIND): - raise ValueError('Invalid coin.') + raise ValueError("Invalid coin.") swap_client.lockWallets(coin) - return bytes(json.dumps({'success': True}), 'UTF-8') + return bytes(json.dumps({"success": True}), "UTF-8") swap_client.lockWallets() - return bytes(json.dumps({'success': True}), 'UTF-8') + return bytes(json.dumps({"success": True}), "UTF-8") def js_404(self, url_split, post_string, is_json) -> bytes: - return bytes(json.dumps({'Error': 'path unknown'}), 'UTF-8') + return bytes(json.dumps({"Error": "path unknown"}), "UTF-8") def js_help(self, url_split, post_string, is_json) -> bytes: @@ -776,55 +896,55 @@ def js_help(self, url_split, post_string, is_json) -> bytes: commands = [] for k in pages: commands.append(k) - return bytes(json.dumps({'commands': commands}), 'UTF-8') + return bytes(json.dumps({"commands": commands}), "UTF-8") def js_readurl(self, url_split, post_string, is_json) -> bytes: swap_client = self.server.swap_client - post_data = {} if post_string == '' else getFormData(post_string, is_json) - if have_data_entry(post_data, 'url'): - url = get_data_entry(post_data, 'url') + post_data = {} if post_string == "" else getFormData(post_string, is_json) + if have_data_entry(post_data, "url"): + url = get_data_entry(post_data, "url") default_headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', - 'Accept-Language': 'en-US,en;q=0.5', + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.5", } response = swap_client.readURL(url, headers=default_headers) try: error = json.loads(response.decode()) if "Error" in error: - return json.dumps({"Error": error['Error']}).encode() + return json.dumps({"Error": error["Error"]}).encode() except json.JSONDecodeError: pass return response - raise ValueError('Requires URL.') + raise ValueError("Requires URL.") pages = { - 'coins': js_coins, - 'wallets': js_wallets, - 'offers': js_offers, - 'sentoffers': js_sentoffers, - 'bids': js_bids, - 'sentbids': js_sentbids, - 'network': js_network, - 'revokeoffer': js_revokeoffer, - 'smsgaddresses': js_smsgaddresses, - 'rate': js_rate, - 'rates': js_rates, - 'rateslist': js_rates_list, - 'generatenotification': js_generatenotification, - 'notifications': js_notifications, - 'identities': js_identities, - 'automationstrategies': js_automationstrategies, - 'validateamount': js_validateamount, - 'vacuumdb': js_vacuumdb, - 'getcoinseed': js_getcoinseed, - 'setpassword': js_setpassword, - 'unlock': js_unlock, - 'lock': js_lock, - 'help': js_help, - 'readurl': js_readurl, + "coins": js_coins, + "wallets": js_wallets, + "offers": js_offers, + "sentoffers": js_sentoffers, + "bids": js_bids, + "sentbids": js_sentbids, + "network": js_network, + "revokeoffer": js_revokeoffer, + "smsgaddresses": js_smsgaddresses, + "rate": js_rate, + "rates": js_rates, + "rateslist": js_rates_list, + "generatenotification": js_generatenotification, + "notifications": js_notifications, + "identities": js_identities, + "automationstrategies": js_automationstrategies, + "validateamount": js_validateamount, + "vacuumdb": js_vacuumdb, + "getcoinseed": js_getcoinseed, + "setpassword": js_setpassword, + "unlock": js_unlock, + "lock": js_lock, + "help": js_help, + "readurl": js_readurl, } diff --git a/basicswap/messages_npb.py b/basicswap/messages_npb.py index 0a7c82b..fa3ac14 100644 --- a/basicswap/messages_npb.py +++ b/basicswap/messages_npb.py @@ -6,7 +6,7 @@ # file LICENSE or http://www.opensource.org/licenses/mit-license.php. -''' +""" syntax = "proto3"; 0 VARINT int32, int64, uint32, uint64, sint32, sint64, bool, enum @@ -18,12 +18,12 @@ Don't encode fields of default values. When decoding initialise all fields not set from data. protobuf ParseFromString would reset the whole object, from_bytes won't. -''' +""" from basicswap.util.integer import encode_varint, decode_varint -class NonProtobufClass(): +class NonProtobufClass: def __init__(self, init_all: bool = True, **kwargs): for key, value in kwargs.items(): found_field: bool = False @@ -34,7 +34,7 @@ class NonProtobufClass(): found_field = True break if found_field is False: - raise ValueError(f'got an unexpected keyword argument \'{key}\'') + raise ValueError(f"got an unexpected keyword argument '{key}'") if init_all: self.init_fields() @@ -53,7 +53,7 @@ class NonProtobufClass(): else: setattr(self, field_name, bytes()) else: - raise ValueError(f'Unknown wire_type {wire_type}') + raise ValueError(f"Unknown wire_type {wire_type}") def to_bytes(self) -> bytes: rv = bytes() @@ -74,11 +74,11 @@ class NonProtobufClass(): continue rv += encode_varint(tag) if isinstance(field_value, str): - field_value = field_value.encode('utf-8') + field_value = field_value.encode("utf-8") rv += encode_varint(len(field_value)) rv += field_value else: - raise ValueError(f'Unknown wire_type {wire_type}') + raise ValueError(f"Unknown wire_type {wire_type}") return rv def from_bytes(self, b: bytes, init_all: bool = True) -> None: @@ -92,7 +92,9 @@ class NonProtobufClass(): field_name, wire_type_expect, field_type = self._map[field_num] if wire_type != wire_type_expect: - raise ValueError(f'Unexpected wire_type {wire_type} for field {field_num}') + raise ValueError( + f"Unexpected wire_type {wire_type} for field {field_num}" + ) if wire_type == 0: field_value, lv = decode_varint(b, o) @@ -100,12 +102,12 @@ class NonProtobufClass(): elif wire_type == 2: field_len, lv = decode_varint(b, o) o += lv - field_value = b[o: o + field_len] + field_value = b[o : o + field_len] o += field_len if field_type == 1: - field_value = field_value.decode('utf-8') + field_value = field_value.decode("utf-8") else: - raise ValueError(f'Unknown wire_type {wire_type}') + raise ValueError(f"Unknown wire_type {wire_type}") setattr(self, field_name, field_value) @@ -115,151 +117,150 @@ class NonProtobufClass(): class OfferMessage(NonProtobufClass): _map = { - 1: ('protocol_version', 0, 0), - 2: ('coin_from', 0, 0), - 3: ('coin_to', 0, 0), - 4: ('amount_from', 0, 0), - 5: ('amount_to', 0, 0), - 6: ('min_bid_amount', 0, 0), - 7: ('time_valid', 0, 0), - 8: ('lock_type', 0, 0), - 9: ('lock_value', 0, 0), - 10: ('swap_type', 0, 0), - 11: ('proof_address', 2, 1), - 12: ('proof_signature', 2, 1), - 13: ('pkhash_seller', 2, 0), - 14: ('secret_hash', 2, 0), - 15: ('fee_rate_from', 0, 0), - 16: ('fee_rate_to', 0, 0), - 17: ('amount_negotiable', 0, 2), - 18: ('rate_negotiable', 0, 2), - 19: ('proof_utxos', 2, 0), + 1: ("protocol_version", 0, 0), + 2: ("coin_from", 0, 0), + 3: ("coin_to", 0, 0), + 4: ("amount_from", 0, 0), + 5: ("amount_to", 0, 0), + 6: ("min_bid_amount", 0, 0), + 7: ("time_valid", 0, 0), + 8: ("lock_type", 0, 0), + 9: ("lock_value", 0, 0), + 10: ("swap_type", 0, 0), + 11: ("proof_address", 2, 1), + 12: ("proof_signature", 2, 1), + 13: ("pkhash_seller", 2, 0), + 14: ("secret_hash", 2, 0), + 15: ("fee_rate_from", 0, 0), + 16: ("fee_rate_to", 0, 0), + 17: ("amount_negotiable", 0, 2), + 18: ("rate_negotiable", 0, 2), + 19: ("proof_utxos", 2, 0), } class BidMessage(NonProtobufClass): _map = { - 1: ('protocol_version', 0, 0), - 2: ('offer_msg_id', 2, 0), - 3: ('time_valid', 0, 0), - 4: ('amount', 0, 0), - 5: ('amount_to', 0, 0), - 6: ('pkhash_buyer', 2, 0), - 7: ('proof_address', 2, 1), - 8: ('proof_signature', 2, 1), - 9: ('proof_utxos', 2, 0), - 10: ('pkhash_buyer_to', 2, 0), + 1: ("protocol_version", 0, 0), + 2: ("offer_msg_id", 2, 0), + 3: ("time_valid", 0, 0), + 4: ("amount", 0, 0), + 5: ("amount_to", 0, 0), + 6: ("pkhash_buyer", 2, 0), + 7: ("proof_address", 2, 1), + 8: ("proof_signature", 2, 1), + 9: ("proof_utxos", 2, 0), + 10: ("pkhash_buyer_to", 2, 0), } class BidAcceptMessage(NonProtobufClass): # Step 3, seller -> buyer _map = { - 1: ('bid_msg_id', 2, 0), - 2: ('initiate_txid', 2, 0), - 3: ('contract_script', 2, 0), - 4: ('pkhash_seller', 2, 0), + 1: ("bid_msg_id", 2, 0), + 2: ("initiate_txid", 2, 0), + 3: ("contract_script", 2, 0), + 4: ("pkhash_seller", 2, 0), } class OfferRevokeMessage(NonProtobufClass): _map = { - 1: ('offer_msg_id', 2, 0), - 2: ('signature', 2, 0), + 1: ("offer_msg_id", 2, 0), + 2: ("signature", 2, 0), } class BidRejectMessage(NonProtobufClass): _map = { - 1: ('bid_msg_id', 2, 0), - 2: ('reject_code', 0, 0), + 1: ("bid_msg_id", 2, 0), + 2: ("reject_code", 0, 0), } class XmrBidMessage(NonProtobufClass): # MSG1L, F -> L _map = { - 1: ('protocol_version', 0, 0), - 2: ('offer_msg_id', 2, 0), - 3: ('time_valid', 0, 0), - 4: ('amount', 0, 0), - 5: ('amount_to', 0, 0), - 6: ('pkaf', 2, 0), - 7: ('kbvf', 2, 0), - 8: ('kbsf_dleag', 2, 0), - 9: ('dest_af', 2, 0), + 1: ("protocol_version", 0, 0), + 2: ("offer_msg_id", 2, 0), + 3: ("time_valid", 0, 0), + 4: ("amount", 0, 0), + 5: ("amount_to", 0, 0), + 6: ("pkaf", 2, 0), + 7: ("kbvf", 2, 0), + 8: ("kbsf_dleag", 2, 0), + 9: ("dest_af", 2, 0), } class XmrSplitMessage(NonProtobufClass): _map = { - 1: ('msg_id', 2, 0), - 2: ('msg_type', 0, 0), - 3: ('sequence', 0, 0), - 4: ('dleag', 2, 0), + 1: ("msg_id", 2, 0), + 2: ("msg_type", 0, 0), + 3: ("sequence", 0, 0), + 4: ("dleag", 2, 0), } class XmrBidAcceptMessage(NonProtobufClass): _map = { - 1: ('bid_msg_id', 2, 0), - 2: ('pkal', 2, 0), - 3: ('kbvl', 2, 0), - 4: ('kbsl_dleag', 2, 0), - + 1: ("bid_msg_id", 2, 0), + 2: ("pkal", 2, 0), + 3: ("kbvl", 2, 0), + 4: ("kbsl_dleag", 2, 0), # MSG2F - 5: ('a_lock_tx', 2, 0), - 6: ('a_lock_tx_script', 2, 0), - 7: ('a_lock_refund_tx', 2, 0), - 8: ('a_lock_refund_tx_script', 2, 0), - 9: ('a_lock_refund_spend_tx', 2, 0), - 10: ('al_lock_refund_tx_sig', 2, 0), + 5: ("a_lock_tx", 2, 0), + 6: ("a_lock_tx_script", 2, 0), + 7: ("a_lock_refund_tx", 2, 0), + 8: ("a_lock_refund_tx_script", 2, 0), + 9: ("a_lock_refund_spend_tx", 2, 0), + 10: ("al_lock_refund_tx_sig", 2, 0), } class XmrBidLockTxSigsMessage(NonProtobufClass): # MSG3L _map = { - 1: ('bid_msg_id', 2, 0), - 2: ('af_lock_refund_spend_tx_esig', 2, 0), - 3: ('af_lock_refund_tx_sig', 2, 0), + 1: ("bid_msg_id", 2, 0), + 2: ("af_lock_refund_spend_tx_esig", 2, 0), + 3: ("af_lock_refund_tx_sig", 2, 0), } class XmrBidLockSpendTxMessage(NonProtobufClass): # MSG4F _map = { - 1: ('bid_msg_id', 2, 0), - 2: ('a_lock_spend_tx', 2, 0), - 3: ('kal_sig', 2, 0), + 1: ("bid_msg_id", 2, 0), + 2: ("a_lock_spend_tx", 2, 0), + 3: ("kal_sig", 2, 0), } class XmrBidLockReleaseMessage(NonProtobufClass): # MSG5F _map = { - 1: ('bid_msg_id', 2, 0), - 2: ('al_lock_spend_tx_esig', 2, 0), + 1: ("bid_msg_id", 2, 0), + 2: ("al_lock_spend_tx_esig", 2, 0), } class ADSBidIntentMessage(NonProtobufClass): # L -> F Sent from bidder, construct a reverse bid _map = { - 1: ('protocol_version', 0, 0), - 2: ('offer_msg_id', 2, 0), - 3: ('time_valid', 0, 0), - 4: ('amount_from', 0, 0), - 5: ('amount_to', 0, 0), + 1: ("protocol_version", 0, 0), + 2: ("offer_msg_id", 2, 0), + 3: ("time_valid", 0, 0), + 4: ("amount_from", 0, 0), + 5: ("amount_to", 0, 0), } class ADSBidIntentAcceptMessage(NonProtobufClass): # F -> L Sent from offerer, construct a reverse bid _map = { - 1: ('bid_msg_id', 2, 0), - 2: ('pkaf', 2, 0), - 3: ('kbvf', 2, 0), - 4: ('kbsf_dleag', 2, 0), - 5: ('dest_af', 2, 0), + 1: ("bid_msg_id", 2, 0), + 2: ("pkaf", 2, 0), + 3: ("kbvf", 2, 0), + 4: ("kbsf_dleag", 2, 0), + 5: ("dest_af", 2, 0), } diff --git a/basicswap/network.py b/basicswap/network.py index 40ea57e..0208c6d 100644 --- a/basicswap/network.py +++ b/basicswap/network.py @@ -5,7 +5,7 @@ # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. -''' +""" Message 2 bytes msg_class, 4 bytes length, [ 2 bytes msg_type, payload ] Handshake procedure: @@ -17,7 +17,7 @@ Both nodes are initialised XChaCha20_Poly1305 mac is 16bytes -''' +""" import time import queue @@ -36,11 +36,12 @@ from Crypto.Cipher import ChaCha20_Poly1305 # TODO: Add to libsecp256k1/coincur from coincurve.keys import PrivateKey, PublicKey from basicswap.contrib.rfc6979 import ( rfc6979_hmac_sha256_initialize, - rfc6979_hmac_sha256_generate) + rfc6979_hmac_sha256_generate, +) -START_TOKEN = 0xabcd -MSG_START_TOKEN = START_TOKEN.to_bytes(2, 'big') +START_TOKEN = 0xABCD +MSG_START_TOKEN = START_TOKEN.to_bytes(2, "big") MSG_MAX_SIZE = 0x200000 # 2MB @@ -63,49 +64,71 @@ class NetMessageTypes(IntEnum): return value in cls._value2member_map_ -''' +""" class NetMessage: def __init__(self): self._msg_class = None # 2 bytes self._len = None # 4 bytes self._msg_type = None # 2 bytes -''' +""" # Ensure handshake keys are not reused by including the time in the msg, mac and key hash # Verify timestamp is not too old # Add keys to db to catch concurrent attempts, records can be cleared periodically, the timestamp should catch older replay attempts class MsgHandshake: - __slots__ = ('_timestamp', '_ephem_pk', '_ct', '_mac') + __slots__ = ("_timestamp", "_ephem_pk", "_ct", "_mac") def __init__(self): pass def encode_aad(self): # Additional Authenticated Data - return int(NetMessageTypes.HANDSHAKE).to_bytes(2, 'big') + \ - self._timestamp.to_bytes(8, 'big') + \ - self._ephem_pk + return ( + int(NetMessageTypes.HANDSHAKE).to_bytes(2, "big") + + self._timestamp.to_bytes(8, "big") + + self._ephem_pk + ) def encode(self): return self.encode_aad() + self._ct + self._mac def decode(self, msg_mv): o = 2 - self._timestamp = int.from_bytes(msg_mv[o: o + 8], 'big') + self._timestamp = int.from_bytes(msg_mv[o : o + 8], "big") o += 8 - self._ephem_pk = bytes(msg_mv[o: o + 33]) + self._ephem_pk = bytes(msg_mv[o : o + 33]) o += 33 - self._ct = bytes(msg_mv[o: -16]) + self._ct = bytes(msg_mv[o:-16]) self._mac = bytes(msg_mv[-16:]) class Peer: __slots__ = ( - '_mx', '_pubkey', '_address', '_socket', '_version', '_ready', '_incoming', - '_connected_at', '_last_received_at', '_bytes_sent', '_bytes_received', - '_receiving_length', '_receiving_buffer', '_recv_messages', '_misbehaving_score', - '_ke', '_km', '_dir', '_sent_nonce', '_recv_nonce', '_last_handshake_at', - '_ping_nonce', '_last_ping_at', '_last_ping_rtt') + "_mx", + "_pubkey", + "_address", + "_socket", + "_version", + "_ready", + "_incoming", + "_connected_at", + "_last_received_at", + "_bytes_sent", + "_bytes_received", + "_receiving_length", + "_receiving_buffer", + "_recv_messages", + "_misbehaving_score", + "_ke", + "_km", + "_dir", + "_sent_nonce", + "_recv_nonce", + "_last_handshake_at", + "_ping_nonce", + "_last_ping_at", + "_last_ping_rtt", + ) def __init__(self, address, socket, pubkey): self._mx = threading.Lock() @@ -141,14 +164,16 @@ def listen_thread(cls): max_bytes = 0x10000 while cls._running: # logging.info('[rm] network loop %d', cls._running) - readable, writable, errored = select.select(cls._read_sockets, cls._write_sockets, cls._error_sockets, timeout) + readable, writable, errored = select.select( + cls._read_sockets, cls._write_sockets, cls._error_sockets, timeout + ) cls._mx.acquire() try: disconnected_peers = [] for s in readable: if s == cls._socket: peer_socket, address = cls._socket.accept() - logging.info('Connection from %s', address) + logging.info("Connection from %s", address) new_peer = Peer(address, peer_socket, None) new_peer._incoming = True cls._peers.append(new_peer) @@ -160,12 +185,12 @@ def listen_thread(cls): try: bytes_recv = s.recv(max_bytes, socket.MSG_DONTWAIT) except socket.error as se: - if se.args[0] not in (socket.EWOULDBLOCK, ): - logging.error('Receive error %s', str(se)) + if se.args[0] not in (socket.EWOULDBLOCK,): + logging.error("Receive error %s", str(se)) disconnected_peers.append(peer) continue except Exception as e: - logging.error('Receive error %s', str(e)) + logging.error("Receive error %s", str(e)) disconnected_peers.append(peer) continue @@ -175,7 +200,7 @@ def listen_thread(cls): cls.receive_bytes(peer, bytes_recv) for s in errored: - logging.warning('Socket error') + logging.warning("Socket error") for peer in disconnected_peers: cls.disconnect(peer) @@ -193,7 +218,9 @@ def msg_thread(cls): try: now_us = time.time_ns() // 1000 if peer._ready is True: - if now_us - peer._last_ping_at >= 5000000: # 5 seconds TODO: Make variable + if ( + now_us - peer._last_ping_at >= 5000000 + ): # 5 seconds TODO: Make variable cls.send_ping(peer) msg = peer._recv_messages.get(False) cls.process_message(peer, msg) @@ -201,7 +228,7 @@ def msg_thread(cls): except queue.Empty: pass except Exception as e: - logging.warning('process message error %s', str(e)) + logging.warning("process message error %s", str(e)) if cls._sc.debug: logging.error(traceback.format_exc()) @@ -211,9 +238,24 @@ def msg_thread(cls): class Network: __slots__ = ( - '_p2p_host', '_p2p_port', '_network_key', '_network_pubkey', - '_sc', '_peers', '_max_connections', '_running', '_network_thread', '_msg_thread', - '_mx', '_socket', '_read_sockets', '_write_sockets', '_error_sockets', '_csprng', '_seen_ephem_keys') + "_p2p_host", + "_p2p_port", + "_network_key", + "_network_pubkey", + "_sc", + "_peers", + "_max_connections", + "_running", + "_network_thread", + "_msg_thread", + "_mx", + "_socket", + "_read_sockets", + "_write_sockets", + "_error_sockets", + "_csprng", + "_seen_ephem_keys", + ) def __init__(self, p2p_host, p2p_port, network_key, swap_client): self._p2p_host = p2p_host @@ -278,7 +320,13 @@ class Network: self._mx.release() def add_connection(self, host, port, peer_pubkey): - self._sc.log.info('Connecting from %s to %s at %s %d', self._network_pubkey.hex(), peer_pubkey.hex(), host, port) + self._sc.log.info( + "Connecting from %s to %s at %s %d", + self._network_pubkey.hex(), + peer_pubkey.hex(), + host, + port, + ) self._mx.acquire() try: address = (host, port) @@ -294,7 +342,7 @@ class Network: self.send_handshake(peer) def disconnect(self, peer): - self._sc.log.info('Closing peer socket %s', peer._address) + self._sc.log.info("Closing peer socket %s", peer._address) self._read_sockets.pop(self._read_sockets.index(peer._socket)) self._error_sockets.pop(self._error_sockets.index(peer._socket)) peer.close() @@ -305,7 +353,11 @@ class Network: used = self._seen_ephem_keys.get(ephem_pk) if used: - raise ValueError('Handshake ephem_pk reused %s peer %s', 'for' if direction == 1 else 'by', used[0]) + raise ValueError( + "Handshake ephem_pk reused %s peer %s", + "for" if direction == 1 else "by", + used[0], + ) self._seen_ephem_keys[ephem_pk] = (peer._address, timestamp) @@ -313,12 +365,14 @@ class Network: self._seen_ephem_keys.popitem(last=False) def send_handshake(self, peer): - self._sc.log.debug('send_handshake %s', peer._address) + self._sc.log.debug("send_handshake %s", peer._address) peer._mx.acquire() try: # TODO: Drain peer._recv_messages if not peer._recv_messages.empty(): - self._sc.log.warning('send_handshake %s - Receive queue dumped.', peer._address) + self._sc.log.warning( + "send_handshake %s - Receive queue dumped.", peer._address + ) while not peer._recv_messages.empty(): peer._recv_messages.get(False) @@ -332,7 +386,7 @@ class Network: ss = k.ecdh(peer._pubkey) - hashed = hashlib.sha512(ss + msg._timestamp.to_bytes(8, 'big')).digest() + hashed = hashlib.sha512(ss + msg._timestamp.to_bytes(8, "big")).digest() peer._ke = hashed[:32] peer._km = hashed[32:] @@ -361,11 +415,13 @@ class Network: peer._mx.release() def process_handshake(self, peer, msg_mv): - self._sc.log.debug('process_handshake %s', peer._address) + self._sc.log.debug("process_handshake %s", peer._address) # TODO: Drain peer._recv_messages if not peer._recv_messages.empty(): - self._sc.log.warning('process_handshake %s - Receive queue dumped.', peer._address) + self._sc.log.warning( + "process_handshake %s - Receive queue dumped.", peer._address + ) while not peer._recv_messages.empty(): peer._recv_messages.get(False) @@ -375,17 +431,19 @@ class Network: try: now = int(time.time()) if now - peer._last_handshake_at < 30: - raise ValueError('Too many handshakes from peer %s', peer._address) + raise ValueError("Too many handshakes from peer %s", peer._address) if abs(msg._timestamp - now) > TIMESTAMP_LEEWAY: - raise ValueError('Bad handshake timestamp from peer %s', peer._address) + raise ValueError("Bad handshake timestamp from peer %s", peer._address) - self.check_handshake_ephem_key(peer, msg._timestamp, msg._ephem_pk, direction=2) + self.check_handshake_ephem_key( + peer, msg._timestamp, msg._ephem_pk, direction=2 + ) nk = PrivateKey(self._network_key) ss = nk.ecdh(msg._ephem_pk) - hashed = hashlib.sha512(ss + msg._timestamp.to_bytes(8, 'big')).digest() + hashed = hashlib.sha512(ss + msg._timestamp.to_bytes(8, "big")).digest() peer._ke = hashed[:32] peer._km = hashed[32:] @@ -395,7 +453,9 @@ class Network: aad += nonce cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce) cipher.update(aad) - plaintext = cipher.decrypt_and_verify(msg._ct, msg._mac) # Will raise error if mac doesn't match + plaintext = cipher.decrypt_and_verify( + msg._ct, msg._mac + ) # Will raise error if mac doesn't match peer._version = plaintext[:6] sig = plaintext[6:] @@ -414,26 +474,30 @@ class Network: except Exception as e: # TODO: misbehaving - self._sc.log.debug('[rm] process_handshake %s', str(e)) + self._sc.log.debug("[rm] process_handshake %s", str(e)) def process_ping(self, peer, msg_mv): nonce = peer._recv_nonce[:24] cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce) - cipher.update(msg_mv[0: 2]) + cipher.update(msg_mv[0:2]) cipher.update(nonce) mac = msg_mv[-16:] - plaintext = cipher.decrypt_and_verify(msg_mv[2: -16], mac) + plaintext = cipher.decrypt_and_verify(msg_mv[2:-16], mac) - ping_nonce = int.from_bytes(plaintext[:4], 'big') + ping_nonce = int.from_bytes(plaintext[:4], "big") # Version is added to a ping following a handshake message if len(plaintext) >= 10: peer._ready = True - version = plaintext[4: 10] + version = plaintext[4:10] if peer._version is None: peer._version = version - self._sc.log.debug('Set version from ping %s, %s', peer._pubkey.hex(), peer._version.hex()) + self._sc.log.debug( + "Set version from ping %s, %s", + peer._pubkey.hex(), + peer._version.hex(), + ) peer._recv_nonce = hashlib.sha256(nonce + mac).digest() @@ -443,32 +507,32 @@ class Network: nonce = peer._recv_nonce[:24] cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce) - cipher.update(msg_mv[0: 2]) + cipher.update(msg_mv[0:2]) cipher.update(nonce) mac = msg_mv[-16:] - plaintext = cipher.decrypt_and_verify(msg_mv[2: -16], mac) + plaintext = cipher.decrypt_and_verify(msg_mv[2:-16], mac) - pong_nonce = int.from_bytes(plaintext[:4], 'big') + pong_nonce = int.from_bytes(plaintext[:4], "big") if pong_nonce == peer._ping_nonce: peer._last_ping_rtt = (time.time_ns() // 1000) - peer._last_ping_at else: - self._sc.log.debug('Pong received out of order %s', peer._address) + self._sc.log.debug("Pong received out of order %s", peer._address) peer._recv_nonce = hashlib.sha256(nonce + mac).digest() def send_ping(self, peer): ping_nonce = random.getrandbits(32) - msg_bytes = int(NetMessageTypes.PING).to_bytes(2, 'big') + msg_bytes = int(NetMessageTypes.PING).to_bytes(2, "big") nonce = peer._sent_nonce[:24] cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce) cipher.update(msg_bytes) cipher.update(nonce) - payload = ping_nonce.to_bytes(4, 'big') + payload = ping_nonce.to_bytes(4, "big") if peer._last_ping_at == 0: payload += self._sc._version ct, mac = cipher.encrypt_and_digest(payload) @@ -483,14 +547,14 @@ class Network: self.send_msg(peer, msg_bytes) def send_pong(self, peer, ping_nonce): - msg_bytes = int(NetMessageTypes.PONG).to_bytes(2, 'big') + msg_bytes = int(NetMessageTypes.PONG).to_bytes(2, "big") nonce = peer._sent_nonce[:24] cipher = ChaCha20_Poly1305.new(key=peer._ke, nonce=nonce) cipher.update(msg_bytes) cipher.update(nonce) - payload = ping_nonce.to_bytes(4, 'big') + payload = ping_nonce.to_bytes(4, "big") ct, mac = cipher.encrypt_and_digest(payload) msg_bytes += ct + mac @@ -502,19 +566,21 @@ class Network: msg_encoded = msg if isinstance(msg, bytes) else msg.encode() len_encoded = len(msg_encoded) - msg_packed = bytearray(MSG_START_TOKEN) + len_encoded.to_bytes(4, 'big') + msg_encoded + msg_packed = ( + bytearray(MSG_START_TOKEN) + len_encoded.to_bytes(4, "big") + msg_encoded + ) peer._socket.sendall(msg_packed) peer._bytes_sent += len_encoded def process_message(self, peer, msg_bytes): - logging.info('[rm] process_message %s len %d', peer._address, len(msg_bytes)) + logging.info("[rm] process_message %s len %d", peer._address, len(msg_bytes)) peer._mx.acquire() try: mv = memoryview(msg_bytes) o = 0 - msg_type = int.from_bytes(mv[o: o + 2], 'big') + msg_type = int.from_bytes(mv[o : o + 2], "big") if msg_type == NetMessageTypes.HANDSHAKE: self.process_handshake(peer, mv) elif msg_type == NetMessageTypes.PING: @@ -522,7 +588,7 @@ class Network: elif msg_type == NetMessageTypes.PONG: self.process_pong(peer, mv) else: - self._sc.log.debug('Unknown message type %d', msg_type) + self._sc.log.debug("Unknown message type %d", msg_type) finally: peer._mx.release() @@ -533,7 +599,6 @@ class Network: peer._last_received_at = time.time() peer._bytes_received += len_received - invalid_msg = False mv = memoryview(bytes_recv) o = 0 @@ -541,34 +606,34 @@ class Network: while o < len_received: if peer._receiving_length == 0: if len(bytes_recv) < MSG_HEADER_LEN: - raise ValueError('Msg too short') + raise ValueError("Msg too short") - if mv[o: o + 2] != MSG_START_TOKEN: - raise ValueError('Invalid start token') + if mv[o : o + 2] != MSG_START_TOKEN: + raise ValueError("Invalid start token") o += 2 - msg_len = int.from_bytes(mv[o: o + 4], 'big') + msg_len = int.from_bytes(mv[o : o + 4], "big") o += 4 if msg_len < 2 or msg_len > MSG_MAX_SIZE: - raise ValueError('Invalid data length') + raise ValueError("Invalid data length") # Precheck msg_type - msg_type = int.from_bytes(mv[o: o + 2], 'big') + msg_type = int.from_bytes(mv[o : o + 2], "big") # o += 2 # Don't inc offset, msg includes type if not NetMessageTypes.has_value(msg_type): - raise ValueError('Invalid msg type') + raise ValueError("Invalid msg type") peer._receiving_length = msg_len - len_pkt = (len_received - o) + len_pkt = len_received - o nc = msg_len if len_pkt > msg_len else len_pkt - peer._receiving_buffer = mv[o: o + nc] + peer._receiving_buffer = mv[o : o + nc] o += nc else: len_to_go = peer._receiving_length - len(peer._receiving_buffer) - len_pkt = (len_received - o) + len_pkt = len_received - o nc = len_to_go if len_pkt > len_to_go else len_pkt - peer._receiving_buffer = mv[o: o + nc] + peer._receiving_buffer = mv[o : o + nc] o += nc if len(peer._receiving_buffer) == peer._receiving_length: peer._recv_messages.put(peer._receiving_buffer) @@ -576,11 +641,13 @@ class Network: except Exception as e: if self._sc.debug: - self._sc.log.error('Invalid message received from %s %s', peer._address, str(e)) + self._sc.log.error( + "Invalid message received from %s %s", peer._address, str(e) + ) # TODO: misbehaving def test_onion(self, path): - self._sc.log.debug('test_onion packet') + self._sc.log.debug("test_onion packet") def get_info(self): rv = {} @@ -589,14 +656,14 @@ class Network: with self._mx: for peer in self._peers: peer_info = { - 'pubkey': 'Unknown' if not peer._pubkey else peer._pubkey.hex(), - 'address': '{}:{}'.format(peer._address[0], peer._address[1]), - 'bytessent': peer._bytes_sent, - 'bytesrecv': peer._bytes_received, - 'ready': peer._ready, - 'incoming': peer._incoming, + "pubkey": "Unknown" if not peer._pubkey else peer._pubkey.hex(), + "address": "{}:{}".format(peer._address[0], peer._address[1]), + "bytessent": peer._bytes_sent, + "bytesrecv": peer._bytes_received, + "ready": peer._ready, + "incoming": peer._incoming, } peers.append(peer_info) - rv['peers'] = peers + rv["peers"] = peers return rv diff --git a/basicswap/protocols/__init__.py b/basicswap/protocols/__init__.py index 04fd842..1bea668 100644 --- a/basicswap/protocols/__init__.py +++ b/basicswap/protocols/__init__.py @@ -16,19 +16,26 @@ class ProtocolInterface: swap_type = None def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes: - raise ValueError('base class') + raise ValueError("base class") def getMockScript(self) -> bytearray: - return bytearray([ - OpCodes.OP_RETURN, OpCodes.OP_1]) + return bytearray([OpCodes.OP_RETURN, OpCodes.OP_1]) def getMockScriptScriptPubkey(self, ci) -> bytearray: script = self.getMockScript() - return ci.getScriptDest(script) if ci._use_segwit else ci.get_p2sh_script_pubkey(script) + return ( + ci.getScriptDest(script) + if ci._use_segwit + else ci.get_p2sh_script_pubkey(script) + ) def getMockAddrTo(self, ci): script = self.getMockScript() - return ci.encodeScriptDest(ci.getScriptDest(script)) if ci._use_segwit else ci.encode_p2sh(script) + return ( + ci.encodeScriptDest(ci.getScriptDest(script)) + if ci._use_segwit + else ci.encode_p2sh(script) + ) def findMockVout(self, ci, itx_decoded): mock_addr = self.getMockAddrTo(ci) diff --git a/basicswap/protocols/atomic_swap_1.py b/basicswap/protocols/atomic_swap_1.py index 6bea165..d825728 100644 --- a/basicswap/protocols/atomic_swap_1.py +++ b/basicswap/protocols/atomic_swap_1.py @@ -26,73 +26,91 @@ INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin ABS_LOCK_TIME_LEEWAY = 10 * 60 -def buildContractScript(lock_val: int, secret_hash: bytes, pkh_redeem: bytes, pkh_refund: bytes, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256) -> bytearray: - script = bytearray([ - OpCodes.OP_IF, - OpCodes.OP_SIZE, - 0x01, 0x20, # 32 - OpCodes.OP_EQUALVERIFY, - op_hash, - 0x20]) \ - + secret_hash \ - + bytearray([ - OpCodes.OP_EQUALVERIFY, - OpCodes.OP_DUP, - OpCodes.OP_HASH160, - 0x14]) \ - + pkh_redeem \ - + bytearray([OpCodes.OP_ELSE, ]) \ - + SerialiseNum(lock_val) \ - + bytearray([ - op_lock, - OpCodes.OP_DROP, - OpCodes.OP_DUP, - OpCodes.OP_HASH160, - 0x14]) \ - + pkh_refund \ - + bytearray([ - OpCodes.OP_ENDIF, - OpCodes.OP_EQUALVERIFY, - OpCodes.OP_CHECKSIG]) +def buildContractScript( + lock_val: int, + secret_hash: bytes, + pkh_redeem: bytes, + pkh_refund: bytes, + op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, + op_hash=OpCodes.OP_SHA256, +) -> bytearray: + script = ( + bytearray( + [ + OpCodes.OP_IF, + OpCodes.OP_SIZE, + 0x01, + 0x20, # 32 + OpCodes.OP_EQUALVERIFY, + op_hash, + 0x20, + ] + ) + + secret_hash + + bytearray([OpCodes.OP_EQUALVERIFY, OpCodes.OP_DUP, OpCodes.OP_HASH160, 0x14]) + + pkh_redeem + + bytearray( + [ + OpCodes.OP_ELSE, + ] + ) + + SerialiseNum(lock_val) + + bytearray( + [op_lock, OpCodes.OP_DROP, OpCodes.OP_DUP, OpCodes.OP_HASH160, 0x14] + ) + + pkh_refund + + bytearray([OpCodes.OP_ENDIF, OpCodes.OP_EQUALVERIFY, OpCodes.OP_CHECKSIG]) + ) return script -def verifyContractScript(script, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256): - if script[0] != OpCodes.OP_IF or \ - script[1] != OpCodes.OP_SIZE or \ - script[2] != 0x01 or script[3] != 0x20 or \ - script[4] != OpCodes.OP_EQUALVERIFY or \ - script[5] != op_hash or \ - script[6] != 0x20: +def verifyContractScript( + script, op_lock=OpCodes.OP_CHECKSEQUENCEVERIFY, op_hash=OpCodes.OP_SHA256 +): + if ( + script[0] != OpCodes.OP_IF + or script[1] != OpCodes.OP_SIZE + or script[2] != 0x01 + or script[3] != 0x20 + or script[4] != OpCodes.OP_EQUALVERIFY + or script[5] != op_hash + or script[6] != 0x20 + ): return False, None, None, None, None o = 7 - script_hash = script[o: o + 32] + script_hash = script[o : o + 32] o += 32 - if script[o] != OpCodes.OP_EQUALVERIFY or \ - script[o + 1] != OpCodes.OP_DUP or \ - script[o + 2] != OpCodes.OP_HASH160 or \ - script[o + 3] != 0x14: + if ( + script[o] != OpCodes.OP_EQUALVERIFY + or script[o + 1] != OpCodes.OP_DUP + or script[o + 2] != OpCodes.OP_HASH160 + or script[o + 3] != 0x14 + ): return False, script_hash, None, None, None o += 4 - pkh_redeem = script[o: o + 20] + pkh_redeem = script[o : o + 20] o += 20 if script[o] != OpCodes.OP_ELSE: return False, script_hash, pkh_redeem, None, None o += 1 lock_val, nb = decodeScriptNum(script, o) o += nb - if script[o] != op_lock or \ - script[o + 1] != OpCodes.OP_DROP or \ - script[o + 2] != OpCodes.OP_DUP or \ - script[o + 3] != OpCodes.OP_HASH160 or \ - script[o + 4] != 0x14: + if ( + script[o] != op_lock + or script[o + 1] != OpCodes.OP_DROP + or script[o + 2] != OpCodes.OP_DUP + or script[o + 3] != OpCodes.OP_HASH160 + or script[o + 4] != 0x14 + ): return False, script_hash, pkh_redeem, lock_val, None o += 5 - pkh_refund = script[o: o + 20] + pkh_refund = script[o : o + 20] o += 20 - if script[o] != OpCodes.OP_ENDIF or \ - script[o + 1] != OpCodes.OP_EQUALVERIFY or \ - script[o + 2] != OpCodes.OP_CHECKSIG: + if ( + script[o] != OpCodes.OP_ENDIF + or script[o + 1] != OpCodes.OP_EQUALVERIFY + or script[o + 2] != OpCodes.OP_CHECKSIG + ): return False, script_hash, pkh_redeem, lock_val, pkh_refund return True, script_hash, pkh_redeem, lock_val, pkh_refund @@ -105,12 +123,19 @@ def redeemITx(self, bid_id: bytes, session): bid, offer = self.getBidAndOffer(bid_id, session) ci_from = self.ci(offer.coin_from) - txn = self.createRedeemTxn(ci_from.coin_type(), bid, for_txn_type='initiate', session=session) + txn = self.createRedeemTxn( + ci_from.coin_type(), bid, for_txn_type="initiate", session=session + ) txid = ci_from.publishTx(bytes.fromhex(txn)) bid.initiate_tx.spend_txid = bytes.fromhex(txid) - self.log.debug('Submitted initiate redeem txn %s to %s chain for bid %s', txid, ci_from.coin_name(), bid_id.hex()) - self.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, '', session) + self.log.debug( + "Submitted initiate redeem txn %s to %s chain for bid %s", + txid, + ci_from.coin_name(), + bid_id.hex(), + ) + self.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, "", session) class AtomicSwapInterface(ProtocolInterface): @@ -118,13 +143,19 @@ class AtomicSwapInterface(ProtocolInterface): def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes: addr_to = self.getMockAddrTo(ci) - funded_tx = ci.createRawFundedTransaction(addr_to, amount, sub_fee, lock_unspents=False) + funded_tx = ci.createRawFundedTransaction( + addr_to, amount, sub_fee, lock_unspents=False + ) return bytes.fromhex(funded_tx) def promoteMockTx(self, ci, mock_tx: bytes, script: bytearray) -> bytearray: mock_txo_script = self.getMockScriptScriptPubkey(ci) - real_txo_script = ci.getScriptDest(script) if ci._use_segwit else ci.get_p2sh_script_pubkey(script) + real_txo_script = ( + ci.getScriptDest(script) + if ci._use_segwit + else ci.get_p2sh_script_pubkey(script) + ) found: int = 0 ctx = ci.loadTx(mock_tx) @@ -134,9 +165,9 @@ class AtomicSwapInterface(ProtocolInterface): found += 1 if found < 1: - raise ValueError('Mocked output not found') + raise ValueError("Mocked output not found") if found > 1: - raise ValueError('Too many mocked outputs found') + raise ValueError("Too many mocked outputs found") ctx.nLockTime = 0 funded_tx = ctx.serialize() diff --git a/basicswap/protocols/xmr_swap_1.py b/basicswap/protocols/xmr_swap_1.py index c128687..9ddb270 100644 --- a/basicswap/protocols/xmr_swap_1.py +++ b/basicswap/protocols/xmr_swap_1.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020-2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -19,18 +20,17 @@ from basicswap.basicswap_util import ( EventLogTypes, ) from . import ProtocolInterface -from basicswap.contrib.test_framework.script import ( - CScript, CScriptOp, - OP_CHECKMULTISIG -) +from basicswap.contrib.test_framework.script import CScript, CScriptOp, OP_CHECKMULTISIG def addLockRefundSigs(self, xmr_swap, ci): - self.log.debug('Setting lock refund tx sigs') + self.log.debug("Setting lock refund tx sigs") witness_stack = [] - if ci.coin_type() not in (Coins.DCR, ): - witness_stack += [b'', ] + if ci.coin_type() not in (Coins.DCR,): + witness_stack += [ + b"", + ] witness_stack += [ xmr_swap.al_lock_refund_tx_sig, xmr_swap.af_lock_refund_tx_sig, @@ -38,37 +38,40 @@ def addLockRefundSigs(self, xmr_swap, ci): ] signed_tx = ci.setTxSignature(xmr_swap.a_lock_refund_tx, witness_stack) - ensure(signed_tx, 'setTxSignature failed') + ensure(signed_tx, "setTxSignature failed") xmr_swap.a_lock_refund_tx = signed_tx def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, session=None): - self.log.info(f'Manually recovering {bid_id.hex()}') + self.log.info(f"Manually recovering {bid_id.hex()}") # Manually recover txn if other key is known try: use_session = self.openSession(session) bid, xmr_swap = self.getXmrBidFromSession(use_session, bid_id) - ensure(bid, 'Bid not found: {}.'.format(bid_id.hex())) - ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex())) - offer, xmr_offer = self.getXmrOfferFromSession(use_session, bid.offer_id, sent=False) - ensure(offer, 'Offer not found: {}.'.format(bid.offer_id.hex())) - ensure(xmr_offer, 'Adaptor-sig offer not found: {}.'.format(bid.offer_id.hex())) + ensure(bid, "Bid not found: {}.".format(bid_id.hex())) + ensure(xmr_swap, "Adaptor-sig swap not found: {}.".format(bid_id.hex())) + offer, xmr_offer = self.getXmrOfferFromSession( + use_session, bid.offer_id, sent=False + ) + ensure(offer, "Offer not found: {}.".format(bid.offer_id.hex())) + ensure(xmr_offer, "Adaptor-sig offer not found: {}.".format(bid.offer_id.hex())) # The no-script coin is always the follower reverse_bid: bool = self.is_reverse_ads_bid(offer.coin_from) ci_from = self.ci(Coins(offer.coin_from)) ci_to = self.ci(Coins(offer.coin_to)) - ci_leader = ci_to if reverse_bid else ci_from ci_follower = ci_from if reverse_bid else ci_to try: decoded_key_half = ci_follower.decodeKey(encoded_key) except Exception as e: - raise ValueError('Failed to decode provided key-half: ', str(e)) + raise ValueError("Failed to decode provided key-half: ", str(e)) was_sent: bool = bid.was_received if reverse_bid else bid.was_sent - localkeyhalf = ci_follower.decodeKey(getChainBSplitKey(self, bid, xmr_swap, offer)) + localkeyhalf = ci_follower.decodeKey( + getChainBSplitKey(self, bid, xmr_swap, offer) + ) if was_sent: kbsl = decoded_key_half kbsf = localkeyhalf @@ -76,32 +79,54 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key, session=None): kbsl = localkeyhalf kbsf = decoded_key_half - ensure(ci_follower.verifyKey(kbsl), 'Invalid kbsl') - ensure(ci_follower.verifyKey(kbsf), 'Invalid kbsf') + ensure(ci_follower.verifyKey(kbsl), "Invalid kbsl") + ensure(ci_follower.verifyKey(kbsf), "Invalid kbsf") if kbsl == kbsf: - raise ValueError('Provided key matches local key') + raise ValueError("Provided key matches local key") vkbs = ci_follower.sumKeys(kbsl, kbsf) - ensure(ci_follower.verifyPubkey(xmr_swap.pkbs), 'Invalid pkbs') # Sanity check + ensure(ci_follower.verifyPubkey(xmr_swap.pkbs), "Invalid pkbs") # Sanity check # Ensure summed key matches the expected pubkey summed_pkbs = ci_follower.getPubkey(vkbs) - if (summed_pkbs != xmr_swap.pkbs): - err_msg: str = 'Summed key does not match expected wallet spend pubkey' + if summed_pkbs != xmr_swap.pkbs: + err_msg: str = "Summed key does not match expected wallet spend pubkey" have_pk = summed_pkbs.hex() expect_pk = xmr_swap.pkbs.hex() - self.log.error(f'{err_msg}. Got: {have_pk}, Expect: {expect_pk}') + self.log.error(f"{err_msg}. Got: {have_pk}, Expect: {expect_pk}") raise ValueError(err_msg) if ci_follower.coin_type() in (Coins.XMR, Coins.WOW): address_to = self.getCachedMainWalletAddress(ci_follower, use_session) else: - address_to = self.getCachedStealthAddressForCoin(ci_follower.coin_type(), use_session) + address_to = self.getCachedStealthAddressForCoin( + ci_follower.coin_type(), use_session + ) amount = bid.amount_to lock_tx_vout = bid.getLockTXBVout() - txid = ci_follower.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, amount, xmr_offer.b_fee_rate, bid.chain_b_height_start, spend_actual_balance=True, lock_tx_vout=lock_tx_vout) - self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_follower.coin_name(), bid_id.hex()) - self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, txid.hex(), use_session) + txid = ci_follower.spendBLockTx( + xmr_swap.b_lock_tx_id, + address_to, + xmr_swap.vkbv, + vkbs, + amount, + xmr_offer.b_fee_rate, + bid.chain_b_height_start, + spend_actual_balance=True, + lock_tx_vout=lock_tx_vout, + ) + self.log.debug( + "Submitted lock B spend txn %s to %s chain for bid %s", + txid.hex(), + ci_follower.coin_name(), + bid_id.hex(), + ) + self.logBidEvent( + bid.bid_id, + EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, + txid.hex(), + use_session, + ) use_session.commit() return txid @@ -122,7 +147,16 @@ def getChainBSplitKey(swap_client, bid, xmr_swap, offer): was_sent: bool = bid.was_received if reverse_bid else bid.was_sent key_type = KeyTypes.KBSF if was_sent else KeyTypes.KBSL - return ci_follower.encodeKey(swap_client.getPathKey(ci_leader.coin_type(), ci_follower.coin_type(), bid.created_at, xmr_swap.contract_count, key_type, for_ed25519)) + return ci_follower.encodeKey( + swap_client.getPathKey( + ci_leader.coin_type(), + ci_follower.coin_type(), + bid.created_at, + xmr_swap.contract_count, + key_type, + for_ed25519, + ) + ) def getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer): @@ -132,13 +166,21 @@ def getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer): if bid.was_sent: if xmr_swap.a_lock_refund_spend_tx: - af_lock_refund_spend_tx_sig = ci_leader.extractFollowerSig(xmr_swap.a_lock_refund_spend_tx) - kbsl = ci_leader.recoverEncKey(xmr_swap.af_lock_refund_spend_tx_esig, af_lock_refund_spend_tx_sig, xmr_swap.pkasl) + af_lock_refund_spend_tx_sig = ci_leader.extractFollowerSig( + xmr_swap.a_lock_refund_spend_tx + ) + kbsl = ci_leader.recoverEncKey( + xmr_swap.af_lock_refund_spend_tx_esig, + af_lock_refund_spend_tx_sig, + xmr_swap.pkasl, + ) return ci_follower.encodeKey(kbsl) else: if xmr_swap.a_lock_spend_tx: al_lock_spend_tx_sig = ci_leader.extractLeaderSig(xmr_swap.a_lock_spend_tx) - kbsf = ci_leader.recoverEncKey(xmr_swap.al_lock_spend_tx_esig, al_lock_spend_tx_sig, xmr_swap.pkasf) + kbsf = ci_leader.recoverEncKey( + xmr_swap.al_lock_spend_tx_esig, al_lock_spend_tx_sig, xmr_swap.pkasf + ) return ci_follower.encodeKey(kbsf) return None @@ -146,18 +188,22 @@ def getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer): def setDLEAG(xmr_swap, ci_to, kbsf: bytes) -> None: if ci_to.curve_type() == Curves.ed25519: xmr_swap.kbsf_dleag = ci_to.proveDLEAG(kbsf) - xmr_swap.pkasf = xmr_swap.kbsf_dleag[0: 33] + xmr_swap.pkasf = xmr_swap.kbsf_dleag[0:33] elif ci_to.curve_type() == Curves.secp256k1: for i in range(10): - xmr_swap.kbsf_dleag = ci_to.signRecoverable(kbsf, 'proof kbsf owned for swap') - pk_recovered: bytes = ci_to.verifySigAndRecover(xmr_swap.kbsf_dleag, 'proof kbsf owned for swap') + xmr_swap.kbsf_dleag = ci_to.signRecoverable( + kbsf, "proof kbsf owned for swap" + ) + pk_recovered: bytes = ci_to.verifySigAndRecover( + xmr_swap.kbsf_dleag, "proof kbsf owned for swap" + ) if pk_recovered == xmr_swap.pkbsf: break # self.log.debug('kbsl recovered pubkey mismatch, retrying.') - assert (pk_recovered == xmr_swap.pkbsf) + assert pk_recovered == xmr_swap.pkbsf xmr_swap.pkasf = xmr_swap.pkbsf else: - raise ValueError('Unknown curve') + raise ValueError("Unknown curve") class XmrSwapInterface(ProtocolInterface): @@ -165,7 +211,7 @@ class XmrSwapInterface(ProtocolInterface): def genScriptLockTxScript(self, ci, Kal: bytes, Kaf: bytes, **kwargs) -> CScript: # fallthrough to ci if genScriptLockTxScript is implemented there - if hasattr(ci, 'genScriptLockTxScript') and callable(ci.genScriptLockTxScript): + if hasattr(ci, "genScriptLockTxScript") and callable(ci.genScriptLockTxScript): return ci.genScriptLockTxScript(ci, Kal, Kaf, **kwargs) Kal_enc = Kal if len(Kal) == 33 else ci.encodePubkey(Kal) @@ -175,7 +221,9 @@ class XmrSwapInterface(ProtocolInterface): def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes: addr_to = self.getMockAddrTo(ci) - funded_tx = ci.createRawFundedTransaction(addr_to, amount, sub_fee, lock_unspents=False) + funded_tx = ci.createRawFundedTransaction( + addr_to, amount, sub_fee, lock_unspents=False + ) return bytes.fromhex(funded_tx) @@ -191,9 +239,9 @@ class XmrSwapInterface(ProtocolInterface): found += 1 if found < 1: - raise ValueError('Mocked output not found') + raise ValueError("Mocked output not found") if found > 1: - raise ValueError('Too many mocked outputs found') + raise ValueError("Too many mocked outputs found") ctx.nLockTime = 0 return ctx.serialize() diff --git a/basicswap/rpc.py b/basicswap/rpc.py index 56cbdd7..45e9708 100644 --- a/basicswap/rpc.py +++ b/basicswap/rpc.py @@ -18,31 +18,42 @@ from xmlrpc.client import ( from .util import jsonDecimal -class Jsonrpc(): +class Jsonrpc: # __getattr__ complicates extending ServerProxy - def __init__(self, uri, transport=None, encoding=None, verbose=False, - allow_none=False, use_datetime=False, use_builtin_types=False, - *, context=None): + def __init__( + self, + uri, + transport=None, + encoding=None, + verbose=False, + allow_none=False, + use_datetime=False, + use_builtin_types=False, + *, + context=None, + ): # establish a "logical" server connection # get the url parsed = urllib.parse.urlparse(uri) - if parsed.scheme not in ('http', 'https'): - raise OSError('unsupported XML-RPC protocol') + if parsed.scheme not in ("http", "https"): + raise OSError("unsupported XML-RPC protocol") self.__host = parsed.netloc self.__handler = parsed.path if not self.__handler: - self.__handler = '/RPC2' + self.__handler = "/RPC2" if transport is None: - handler = SafeTransport if parsed.scheme == 'https' else Transport + handler = SafeTransport if parsed.scheme == "https" else Transport extra_kwargs = {} - transport = handler(use_datetime=use_datetime, - use_builtin_types=use_builtin_types, - **extra_kwargs) + transport = handler( + use_datetime=use_datetime, + use_builtin_types=use_builtin_types, + **extra_kwargs, + ) self.__transport = transport - self.__encoding = encoding or 'utf-8' + self.__encoding = encoding or "utf-8" self.__verbose = verbose self.__allow_none = allow_none @@ -57,17 +68,16 @@ class Jsonrpc(): connection = self.__transport.make_connection(self.__host) headers = self.__transport._extra_headers[:] - request_body = { - 'method': method, - 'params': params, - 'id': self.__request_id - } + request_body = {"method": method, "params": params, "id": self.__request_id} - connection.putrequest('POST', self.__handler) - headers.append(('Content-Type', 'application/json')) - headers.append(('User-Agent', 'jsonrpc')) + connection.putrequest("POST", self.__handler) + headers.append(("Content-Type", "application/json")) + headers.append(("User-Agent", "jsonrpc")) self.__transport.send_headers(connection, headers) - self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8')) + self.__transport.send_content( + connection, + json.dumps(request_body, default=jsonDecimal).encode("utf-8"), + ) self.__request_id += 1 resp = connection.getresponse() @@ -82,55 +92,59 @@ class Jsonrpc(): raise -def callrpc(rpc_port, auth, method, params=[], wallet=None, host='127.0.0.1'): +def callrpc(rpc_port, auth, method, params=[], wallet=None, host="127.0.0.1"): try: - url = 'http://{}@{}:{}/'.format(auth, host, rpc_port) + url = "http://{}@{}:{}/".format(auth, host, rpc_port) if wallet is not None: - url += 'wallet/' + urllib.parse.quote(wallet) + url += "wallet/" + urllib.parse.quote(wallet) x = Jsonrpc(url) v = x.json_request(method, params) x.close() - r = json.loads(v.decode('utf-8')) + r = json.loads(v.decode("utf-8")) except Exception as ex: traceback.print_exc() - raise ValueError('RPC server error ' + str(ex) + ', method: ' + method) + raise ValueError("RPC server error " + str(ex) + ", method: " + method) - if 'error' in r and r['error'] is not None: - raise ValueError('RPC error ' + str(r['error'])) + if "error" in r and r["error"] is not None: + raise ValueError("RPC error " + str(r["error"])) - return r['result'] + return r["result"] -def openrpc(rpc_port, auth, wallet=None, host='127.0.0.1'): +def openrpc(rpc_port, auth, wallet=None, host="127.0.0.1"): try: - url = 'http://{}@{}:{}/'.format(auth, host, rpc_port) + url = "http://{}@{}:{}/".format(auth, host, rpc_port) if wallet is not None: - url += 'wallet/' + urllib.parse.quote(wallet) + url += "wallet/" + urllib.parse.quote(wallet) return Jsonrpc(url) except Exception as ex: traceback.print_exc() - raise ValueError('RPC error ' + str(ex)) + raise ValueError("RPC error " + str(ex)) -def callrpc_cli(bindir, datadir, chain, cmd, cli_bin='particl-cli', wallet=None): +def callrpc_cli(bindir, datadir, chain, cmd, cli_bin="particl-cli", wallet=None): cli_bin = os.path.join(bindir, cli_bin) - args = [cli_bin, ] - if chain != 'mainnet': - args.append('-' + chain) - args.append('-datadir=' + datadir) + args = [ + cli_bin, + ] + if chain != "mainnet": + args.append("-" + chain) + args.append("-datadir=" + datadir) if wallet is not None: - args.append('-rpcwallet=' + wallet) + args.append("-rpcwallet=" + wallet) args += shlex.split(cmd) - p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen( + args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) out = p.communicate() if len(out[1]) > 0: - raise ValueError('RPC error ' + str(out[1])) + raise ValueError("RPC error " + str(out[1])) - r = out[0].decode('utf-8').strip() + r = out[0].decode("utf-8").strip() try: r = json.loads(r) except Exception: @@ -138,7 +152,7 @@ def callrpc_cli(bindir, datadir, chain, cmd, cli_bin='particl-cli', wallet=None) return r -def make_rpc_func(port, auth, wallet=None, host='127.0.0.1'): +def make_rpc_func(port, auth, wallet=None, host="127.0.0.1"): port = port auth = auth wallet = wallet @@ -146,11 +160,19 @@ def make_rpc_func(port, auth, wallet=None, host='127.0.0.1'): def rpc_func(method, params=None, wallet_override=None): nonlocal port, auth, wallet, host - return callrpc(port, auth, method, params, wallet if wallet_override is None else wallet_override, host) + return callrpc( + port, + auth, + method, + params, + wallet if wallet_override is None else wallet_override, + host, + ) + return rpc_func def escape_rpcauth(auth_str: str) -> str: - username, password = auth_str.split(':', 1) - password = urllib.parse.quote(password, safe='') - return f'{username}:{password}' + username, password = auth_str.split(":", 1) + password = urllib.parse.quote(password, safe="") + return f"{username}:{password}" diff --git a/basicswap/rpc_xmr.py b/basicswap/rpc_xmr.py index 8e3a9a8..c79f234 100644 --- a/basicswap/rpc_xmr.py +++ b/basicswap/rpc_xmr.py @@ -33,31 +33,50 @@ class SocksTransport(Transport): return self._connection[1] # create a HTTP connection object from a host descriptor chost, self._extra_headers, x509 = self.get_host_info(host) - self._connection = host, SocksiPyConnection(self.proxy_type, self.proxy_host, self.proxy_port, self.proxy_rdns, self.proxy_username, self.proxy_password, chost) + self._connection = host, SocksiPyConnection( + self.proxy_type, + self.proxy_host, + self.proxy_port, + self.proxy_rdns, + self.proxy_username, + self.proxy_password, + chost, + ) return self._connection[1] -class JsonrpcDigest(): +class JsonrpcDigest: # __getattr__ complicates extending ServerProxy - def __init__(self, uri, transport=None, encoding=None, verbose=False, - allow_none=False, use_datetime=False, use_builtin_types=False, - *, context=None): + def __init__( + self, + uri, + transport=None, + encoding=None, + verbose=False, + allow_none=False, + use_datetime=False, + use_builtin_types=False, + *, + context=None, + ): parsed = urllib.parse.urlparse(uri) - if parsed.scheme not in ('http', 'https'): - raise OSError('unsupported XML-RPC protocol') + if parsed.scheme not in ("http", "https"): + raise OSError("unsupported XML-RPC protocol") self.__host = parsed.netloc self.__handler = parsed.path if transport is None: - handler = SafeTransport if parsed.scheme == 'https' else Transport + handler = SafeTransport if parsed.scheme == "https" else Transport extra_kwargs = {} - transport = handler(use_datetime=use_datetime, - use_builtin_types=use_builtin_types, - **extra_kwargs) + transport = handler( + use_datetime=use_datetime, + use_builtin_types=use_builtin_types, + **extra_kwargs, + ) self.__transport = transport - self.__encoding = encoding or 'utf-8' + self.__encoding = encoding or "utf-8" self.__verbose = verbose self.__allow_none = allow_none @@ -77,11 +96,18 @@ class JsonrpcDigest(): connection.timeout = timeout headers = self.__transport._extra_headers[:] - connection.putrequest('POST', self.__handler) - headers.append(('Content-Type', 'application/json')) - headers.append(('User-Agent', 'jsonrpc')) + connection.putrequest("POST", self.__handler) + headers.append(("Content-Type", "application/json")) + headers.append(("User-Agent", "jsonrpc")) self.__transport.send_headers(connection, headers) - self.__transport.send_content(connection, '' if params is None else json.dumps(params, default=jsonDecimal).encode('utf-8')) + self.__transport.send_content( + connection, + ( + "" + if params is None + else json.dumps(params, default=jsonDecimal).encode("utf-8") + ), + ) self.__request_id += 1 resp = connection.getresponse() @@ -93,7 +119,7 @@ class JsonrpcDigest(): self.__transport.close() raise - def json_request(self, request_body, username='', password='', timeout=None): + def json_request(self, request_body, username="", password="", timeout=None): try: connection = self.__transport.make_connection(self.__host) if timeout: @@ -101,65 +127,82 @@ class JsonrpcDigest(): headers = self.__transport._extra_headers[:] - connection.putrequest('POST', self.__handler) - headers.append(('Content-Type', 'application/json')) - headers.append(('Connection', 'keep-alive')) + connection.putrequest("POST", self.__handler) + headers.append(("Content-Type", "application/json")) + headers.append(("Connection", "keep-alive")) self.__transport.send_headers(connection, headers) - self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '') + self.__transport.send_content( + connection, + ( + json.dumps(request_body, default=jsonDecimal).encode("utf-8") + if request_body + else "" + ), + ) resp = connection.getresponse() if resp.status == 401: resp_headers = resp.getheaders() - v = resp.read() + _ = resp.read() - algorithm = '' - realm = '' - nonce = '' + realm = "" + nonce = "" for h in resp_headers: - if h[0] != 'WWW-authenticate': + if h[0] != "WWW-authenticate": continue - fields = h[1].split(',') + fields = h[1].split(",") for f in fields: - key, value = f.split('=', 1) - if key == 'algorithm' and value != 'MD5': + key, value = f.split("=", 1) + if key == "algorithm" and value != "MD5": break - if key == 'realm': + if key == "realm": realm = value.strip('"') - if key == 'nonce': + if key == "nonce": nonce = value.strip('"') - if realm != '' and nonce != '': + if realm != "" and nonce != "": break - if realm == '' or nonce == '': - raise ValueError('Authenticate header not found.') + if realm == "" or nonce == "": + raise ValueError("Authenticate header not found.") path = self.__handler - HA1 = hashlib.md5(f'{username}:{realm}:{password}'.encode('utf-8')).hexdigest() + HA1 = hashlib.md5( + f"{username}:{realm}:{password}".encode("utf-8") + ).hexdigest() - http_method = 'POST' - HA2 = hashlib.md5(f'{http_method}:{path}'.encode('utf-8')).hexdigest() + http_method = "POST" + HA2 = hashlib.md5(f"{http_method}:{path}".encode("utf-8")).hexdigest() - ncvalue = '{:08x}'.format(1) - s = ncvalue.encode('utf-8') - s += nonce.encode('utf-8') - s += time.ctime().encode('utf-8') + ncvalue = "{:08x}".format(1) + s = ncvalue.encode("utf-8") + s += nonce.encode("utf-8") + s += time.ctime().encode("utf-8") s += os.urandom(8) - cnonce = (hashlib.sha1(s).hexdigest()[:16]) + cnonce = hashlib.sha1(s).hexdigest()[:16] # MD5-SESS - HA1 = hashlib.md5(f'{HA1}:{nonce}:{cnonce}'.encode('utf-8')).hexdigest() + HA1 = hashlib.md5(f"{HA1}:{nonce}:{cnonce}".encode("utf-8")).hexdigest() - respdig = hashlib.md5(f'{HA1}:{nonce}:{ncvalue}:{cnonce}:auth:{HA2}'.encode('utf-8')).hexdigest() + respdig = hashlib.md5( + f"{HA1}:{nonce}:{ncvalue}:{cnonce}:auth:{HA2}".encode("utf-8") + ).hexdigest() header_value = f'Digest username="{username}", realm="{realm}", nonce="{nonce}", uri="{path}", response="{respdig}", algorithm="MD5-sess", qop="auth", nc={ncvalue}, cnonce="{cnonce}"' headers = self.__transport._extra_headers[:] - headers.append(('Authorization', header_value)) + headers.append(("Authorization", header_value)) - connection.putrequest('POST', self.__handler) - headers.append(('Content-Type', 'application/json')) - headers.append(('Connection', 'keep-alive')) + connection.putrequest("POST", self.__handler) + headers.append(("Content-Type", "application/json")) + headers.append(("Connection", "keep-alive")) self.__transport.send_headers(connection, headers) - self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '') + self.__transport.send_content( + connection, + ( + json.dumps(request_body, default=jsonDecimal).encode("utf-8") + if request_body + else "" + ), + ) resp = connection.getresponse() self.__request_id += 1 @@ -172,57 +215,88 @@ class JsonrpcDigest(): raise -def callrpc_xmr(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rpc', auth=None, timeout=120, transport=None, tag=''): +def callrpc_xmr( + rpc_port, + method, + params=[], + rpc_host="127.0.0.1", + path="json_rpc", + auth=None, + timeout=120, + transport=None, + tag="", +): # auth is a tuple: (username, password) try: - if rpc_host.count('://') > 0: - url = '{}:{}/{}'.format(rpc_host, rpc_port, path) + if rpc_host.count("://") > 0: + url = "{}:{}/{}".format(rpc_host, rpc_port, path) else: - url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, path) + url = "http://{}:{}/{}".format(rpc_host, rpc_port, path) x = JsonrpcDigest(url, transport=transport) request_body = { - 'method': method, - 'params': params, - 'jsonrpc': '2.0', - 'id': x.request_id() + "method": method, + "params": params, + "jsonrpc": "2.0", + "id": x.request_id(), } if auth: - v = x.json_request(request_body, username=auth[0], password=auth[1], timeout=timeout) + v = x.json_request( + request_body, username=auth[0], password=auth[1], timeout=timeout + ) else: v = x.json_request(request_body, timeout=timeout) x.close() - r = json.loads(v.decode('utf-8')) + r = json.loads(v.decode("utf-8")) except Exception as ex: - raise ValueError('{}RPC Server Error: {}'.format(tag, str(ex))) + raise ValueError("{}RPC Server Error: {}".format(tag, str(ex))) - if 'error' in r and r['error'] is not None: - raise ValueError(tag + 'RPC error ' + str(r['error'])) + if "error" in r and r["error"] is not None: + raise ValueError(tag + "RPC error " + str(r["error"])) - return r['result'] + return r["result"] -def callrpc_xmr2(rpc_port: int, method: str, params=None, auth=None, rpc_host='127.0.0.1', timeout=120, transport=None, tag=''): +def callrpc_xmr2( + rpc_port: int, + method: str, + params=None, + auth=None, + rpc_host="127.0.0.1", + timeout=120, + transport=None, + tag="", +): try: - if rpc_host.count('://') > 0: - url = '{}:{}/{}'.format(rpc_host, rpc_port, method) + if rpc_host.count("://") > 0: + url = "{}:{}/{}".format(rpc_host, rpc_port, method) else: - url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, method) + url = "http://{}:{}/{}".format(rpc_host, rpc_port, method) x = JsonrpcDigest(url, transport=transport) if auth: - v = x.json_request(params, username=auth[0], password=auth[1], timeout=timeout) + v = x.json_request( + params, username=auth[0], password=auth[1], timeout=timeout + ) else: v = x.json_request(params, timeout=timeout) x.close() - r = json.loads(v.decode('utf-8')) + r = json.loads(v.decode("utf-8")) except Exception as ex: - raise ValueError('{}RPC Server Error: {}'.format(tag, str(ex))) + raise ValueError("{}RPC Server Error: {}".format(tag, str(ex))) return r -def make_xmr_rpc2_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None, default_timeout=120, tag=''): +def make_xmr_rpc2_func( + port, + auth, + host="127.0.0.1", + proxy_host=None, + proxy_port=None, + default_timeout=120, + tag="", +): port = port auth = auth host = host @@ -236,11 +310,29 @@ def make_xmr_rpc2_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port def rpc_func(method, params=None, wallet=None, timeout=default_timeout): nonlocal port, auth, host, transport, tag - return callrpc_xmr2(port, method, params, auth=auth, rpc_host=host, timeout=timeout, transport=transport, tag=tag) + return callrpc_xmr2( + port, + method, + params, + auth=auth, + rpc_host=host, + timeout=timeout, + transport=transport, + tag=tag, + ) + return rpc_func -def make_xmr_rpc_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None, default_timeout=120, tag=''): +def make_xmr_rpc_func( + port, + auth, + host="127.0.0.1", + proxy_host=None, + proxy_port=None, + default_timeout=120, + tag="", +): port = port auth = auth host = host @@ -254,5 +346,15 @@ def make_xmr_rpc_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port= def rpc_func(method, params=None, wallet=None, timeout=default_timeout): nonlocal port, auth, host, transport, tag - return callrpc_xmr(port, method, params, rpc_host=host, auth=auth, timeout=timeout, transport=transport, tag=tag) + return callrpc_xmr( + port, + method, + params, + rpc_host=host, + auth=auth, + timeout=timeout, + transport=transport, + tag=tag, + ) + return rpc_func diff --git a/basicswap/script.py b/basicswap/script.py index e601236..7db9c54 100644 --- a/basicswap/script.py +++ b/basicswap/script.py @@ -8,23 +8,23 @@ from enum import IntEnum class OpCodes(IntEnum): - OP_0 = 0x00, - OP_PUSHDATA1 = 0x4c, - OP_1 = 0x51, - OP_16 = 0x60, - OP_IF = 0x63, - OP_ELSE = 0x67, - OP_ENDIF = 0x68, - OP_RETURN = 0x6a, - OP_DROP = 0x75, - OP_DUP = 0x76, - OP_SIZE = 0x82, - OP_EQUAL = 0x87, - OP_EQUALVERIFY = 0x88, - OP_SHA256 = 0xa8, - OP_HASH160 = 0xa9, - OP_CHECKSIG = 0xac, - OP_CHECKLOCKTIMEVERIFY = 0xb1, - OP_CHECKSEQUENCEVERIFY = 0xb2, + OP_0 = (0x00,) + OP_PUSHDATA1 = (0x4C,) + OP_1 = (0x51,) + OP_16 = (0x60,) + OP_IF = (0x63,) + OP_ELSE = (0x67,) + OP_ENDIF = (0x68,) + OP_RETURN = (0x6A,) + OP_DROP = (0x75,) + OP_DUP = (0x76,) + OP_SIZE = (0x82,) + OP_EQUAL = (0x87,) + OP_EQUALVERIFY = (0x88,) + OP_SHA256 = (0xA8,) + OP_HASH160 = (0xA9,) + OP_CHECKSIG = (0xAC,) + OP_CHECKLOCKTIMEVERIFY = (0xB1,) + OP_CHECKSEQUENCEVERIFY = (0xB2,) - OP_SHA256_DECRED = 0xc0, + OP_SHA256_DECRED = (0xC0,) diff --git a/basicswap/ui/page_automation.py b/basicswap/ui/page_automation.py index 408a615..00df606 100644 --- a/basicswap/ui/page_automation.py +++ b/basicswap/ui/page_automation.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2022-2023 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -28,33 +29,33 @@ def page_automation_strategies(self, url_split, post_string): summary = swap_client.getSummary() filters = { - 'page_no': 1, - 'limit': PAGE_LIMIT, - 'sort_by': 'created_at', - 'sort_dir': 'desc', + "page_no": 1, + "limit": PAGE_LIMIT, + "sort_by": "created_at", + "sort_dir": "desc", } messages = [] - form_data = self.checkForm(post_string, 'automationstrategies', messages) + form_data = self.checkForm(post_string, "automationstrategies", messages) if form_data: - if have_data_entry(form_data, 'clearfilters'): - swap_client.clearFilters('page_automation_strategies') + if have_data_entry(form_data, "clearfilters"): + swap_client.clearFilters("page_automation_strategies") else: - if have_data_entry(form_data, 'sort_by'): - sort_by = get_data_entry(form_data, 'sort_by') - ensure(sort_by in ['created_at', 'rate'], 'Invalid sort by') - filters['sort_by'] = sort_by - if have_data_entry(form_data, 'sort_dir'): - sort_dir = get_data_entry(form_data, 'sort_dir') - ensure(sort_dir in ['asc', 'desc'], 'Invalid sort dir') - filters['sort_dir'] = sort_dir + if have_data_entry(form_data, "sort_by"): + sort_by = get_data_entry(form_data, "sort_by") + ensure(sort_by in ["created_at", "rate"], "Invalid sort by") + filters["sort_by"] = sort_by + if have_data_entry(form_data, "sort_dir"): + sort_dir = get_data_entry(form_data, "sort_dir") + ensure(sort_dir in ["asc", "desc"], "Invalid sort dir") + filters["sort_dir"] = sort_dir set_pagination_filters(form_data, filters) - if have_data_entry(form_data, 'applyfilters'): - swap_client.setFilters('page_automation_strategies', filters) + if have_data_entry(form_data, "applyfilters"): + swap_client.setFilters("page_automation_strategies", filters) else: - saved_filters = swap_client.getFilters('page_automation_strategies') + saved_filters = swap_client.getFilters("page_automation_strategies") if saved_filters: filters.update(saved_filters) @@ -62,13 +63,16 @@ def page_automation_strategies(self, url_split, post_string): for s in swap_client.listAutomationStrategies(filters): formatted_strategies.append((s[0], s[1], strConcepts(s[2]))) - template = server.env.get_template('automation_strategies.html') - return self.render_template(template, { - 'messages': messages, - 'filters': filters, - 'strategies': formatted_strategies, - 'summary': summary, - }) + template = server.env.get_template("automation_strategies.html") + return self.render_template( + template, + { + "messages": messages, + "filters": filters, + "strategies": formatted_strategies, + "summary": summary, + }, + ) def page_automation_strategy_new(self, url_split, post_string): @@ -78,21 +82,24 @@ def page_automation_strategy_new(self, url_split, post_string): summary = swap_client.getSummary() messages = [] - form_data = self.checkForm(post_string, 'automationstrategynew', messages) + _ = self.checkForm(post_string, "automationstrategynew", messages) - template = server.env.get_template('automation_strategy_new.html') - return self.render_template(template, { - 'messages': messages, - 'summary': summary, - }) + template = server.env.get_template("automation_strategy_new.html") + return self.render_template( + template, + { + "messages": messages, + "summary": summary, + }, + ) def page_automation_strategy(self, url_split, post_string): - ensure(len(url_split) > 2, 'Strategy ID not specified') + ensure(len(url_split) > 2, "Strategy ID not specified") try: strategy_id = int(url_split[2]) except Exception: - raise ValueError('Bad strategy ID') + raise ValueError("Bad strategy ID") server = self.server swap_client = server.swap_client @@ -101,17 +108,17 @@ def page_automation_strategy(self, url_split, post_string): messages = [] err_messages = [] - form_data = self.checkForm(post_string, 'automation_strategy', err_messages) + form_data = self.checkForm(post_string, "automation_strategy", err_messages) show_edit_form = False if form_data: - if have_data_entry(form_data, 'edit'): + if have_data_entry(form_data, "edit"): show_edit_form = True - if have_data_entry(form_data, 'apply'): + if have_data_entry(form_data, "apply"): try: - data = json.loads(get_data_entry_or(form_data, 'data', '')) - note = get_data_entry_or(form_data, 'note', '') + data = json.loads(get_data_entry_or(form_data, "data", "")) + note = get_data_entry_or(form_data, "note", "") swap_client.updateAutomationStrategy(strategy_id, data, note) - messages.append('Updated') + messages.append("Updated") except Exception as e: err_messages.append(str(e)) show_edit_form = True @@ -119,19 +126,24 @@ def page_automation_strategy(self, url_split, post_string): strategy = swap_client.getAutomationStrategy(strategy_id) formatted_strategy = { - 'label': strategy.label, - 'type': strConcepts(strategy.type_ind), - 'only_known_identities': 'True' if strategy.only_known_identities is True else 'False', - 'data': strategy.data.decode('utf-8'), - 'note': '' if not strategy.note else strategy.note, - 'created_at': strategy.created_at, + "label": strategy.label, + "type": strConcepts(strategy.type_ind), + "only_known_identities": ( + "True" if strategy.only_known_identities is True else "False" + ), + "data": strategy.data.decode("utf-8"), + "note": "" if not strategy.note else strategy.note, + "created_at": strategy.created_at, } - template = server.env.get_template('automation_strategy.html') - return self.render_template(template, { - 'messages': messages, - 'err_messages': err_messages, - 'strategy': formatted_strategy, - 'show_edit_form': show_edit_form, - 'summary': summary, - }) + template = server.env.get_template("automation_strategy.html") + return self.render_template( + template, + { + "messages": messages, + "err_messages": err_messages, + "strategy": formatted_strategy, + "show_edit_form": show_edit_form, + "summary": summary, + }, + ) diff --git a/basicswap/ui/page_bids.py b/basicswap/ui/page_bids.py index 91eb587..aea8006 100644 --- a/basicswap/ui/page_bids.py +++ b/basicswap/ui/page_bids.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2022-2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -30,12 +31,12 @@ from basicswap.basicswap_util import ( def page_bid(self, url_split, post_string): - ensure(len(url_split) > 2, 'Bid ID not specified') + ensure(len(url_split) > 2, "Bid ID not specified") try: bid_id = bytes.fromhex(url_split[2]) assert len(bid_id) == 28 except Exception: - raise ValueError('Bad bid ID') + raise ValueError("Bad bid ID") server = self.server swap_client = server.swap_client swap_client.checkSystemStatus() @@ -49,129 +50,163 @@ def page_bid(self, url_split, post_string): show_lock_transfers = False edit_bid = False view_tx_ind = None - form_data = self.checkForm(post_string, 'bid', err_messages) + form_data = self.checkForm(post_string, "bid", err_messages) if form_data: - if b'abandon_bid' in form_data: + if b"abandon_bid" in form_data: try: swap_client.abandonBid(bid_id) - messages.append('Bid abandoned') + messages.append("Bid abandoned") except Exception as ex: - err_messages.append('Abandon failed ' + str(ex)) - elif b'accept_bid' in form_data: + err_messages.append("Abandon failed " + str(ex)) + elif b"accept_bid" in form_data: try: swap_client.acceptBid(bid_id) - messages.append('Bid accepted') + messages.append("Bid accepted") except Exception as ex: - err_messages.append('Accept failed ' + str(ex)) - elif b'show_txns' in form_data: + err_messages.append("Accept failed " + str(ex)) + elif b"show_txns" in form_data: show_txns = True - elif b'show_offerer_seq_diagram' in form_data: + elif b"show_offerer_seq_diagram" in form_data: show_offerer_seq_diagram = True - elif b'show_bidder_seq_diagram' in form_data: + elif b"show_bidder_seq_diagram" in form_data: show_bidder_seq_diagram = True - elif b'edit_bid' in form_data: + elif b"edit_bid" in form_data: edit_bid = True - elif b'edit_bid_submit' in form_data: + elif b"edit_bid_submit" in form_data: data = { - 'bid_state': int(form_data[b'new_state'][0]), - 'bid_action': int(get_data_entry_or(form_data, 'new_action', -1)), - 'debug_ind': int(get_data_entry_or(form_data, 'debugind', -1)), - 'kbs_other': get_data_entry_or(form_data, 'kbs_other', None), + "bid_state": int(form_data[b"new_state"][0]), + "bid_action": int(get_data_entry_or(form_data, "new_action", -1)), + "debug_ind": int(get_data_entry_or(form_data, "debugind", -1)), + "kbs_other": get_data_entry_or(form_data, "kbs_other", None), } try: swap_client.manualBidUpdate(bid_id, data) - messages.append('Bid edited') + messages.append("Bid edited") except Exception as ex: - err_messages.append('Edit failed ' + str(ex)) - elif b'view_tx_submit' in form_data: + err_messages.append("Edit failed " + str(ex)) + elif b"view_tx_submit" in form_data: show_txns = True - view_tx_ind = form_data[b'view_tx'][0].decode('utf-8') + view_tx_ind = form_data[b"view_tx"][0].decode("utf-8") if len(view_tx_ind) != 64: - err_messages.append('Invalid transaction selected.') + err_messages.append("Invalid transaction selected.") view_tx_ind = None - elif b'view_lock_transfers' in form_data: + elif b"view_lock_transfers" in form_data: show_txns = True show_lock_transfers = True bid, xmr_swap, offer, xmr_offer, events = swap_client.getXmrBidAndOffer(bid_id) - ensure(bid, 'Unknown bid ID') + ensure(bid, "Unknown bid ID") - data = describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, events, edit_bid, show_txns, view_tx_ind, show_lock_transfers=show_lock_transfers) + data = describeBid( + swap_client, + bid, + xmr_swap, + offer, + xmr_offer, + events, + edit_bid, + show_txns, + view_tx_ind, + show_lock_transfers=show_lock_transfers, + ) if bid.debug_ind is not None and bid.debug_ind > 0: - messages.append('Debug flag set: {}, {}'.format(bid.debug_ind, DebugTypes(bid.debug_ind).name)) + messages.append( + "Debug flag set: {}, {}".format( + bid.debug_ind, DebugTypes(bid.debug_ind).name + ) + ) - data['show_bidder_seq_diagram'] = show_bidder_seq_diagram - data['show_offerer_seq_diagram'] = show_offerer_seq_diagram + data["show_bidder_seq_diagram"] = show_bidder_seq_diagram + data["show_offerer_seq_diagram"] = show_offerer_seq_diagram old_states = listOldBidStates(bid) - if len(data['addr_from_label']) > 0: - data['addr_from_label'] = '(' + data['addr_from_label'] + ')' - data['can_accept_bid'] = True if bid.state == BidStates.BID_RECEIVED else False + if len(data["addr_from_label"]) > 0: + data["addr_from_label"] = "(" + data["addr_from_label"] + ")" + data["can_accept_bid"] = True if bid.state == BidStates.BID_RECEIVED else False if swap_client.debug_ui: - data['bid_actions'] = [(-1, 'None'), ] + listBidActions() + data["bid_actions"] = [ + (-1, "None"), + ] + listBidActions() - template = server.env.get_template('bid_xmr.html' if offer.swap_type == SwapTypes.XMR_SWAP else 'bid.html') - return self.render_template(template, { - 'bid_id': bid_id.hex(), - 'messages': messages, - 'err_messages': err_messages, - 'data': data, - 'edit_bid': edit_bid, - 'old_states': old_states, - 'summary': summary, - }) + template = server.env.get_template( + "bid_xmr.html" if offer.swap_type == SwapTypes.XMR_SWAP else "bid.html" + ) + return self.render_template( + template, + { + "bid_id": bid_id.hex(), + "messages": messages, + "err_messages": err_messages, + "data": data, + "edit_bid": edit_bid, + "old_states": old_states, + "summary": summary, + }, + ) -def page_bids(self, url_split, post_string, sent=False, available=False, received=False): +def page_bids( + self, url_split, post_string, sent=False, available=False, received=False +): server = self.server swap_client = server.swap_client swap_client.checkSystemStatus() summary = swap_client.getSummary() filters = { - 'page_no': 1, - 'bid_state_ind': -1, - 'with_expired': True, - 'limit': PAGE_LIMIT, - 'sort_by': 'created_at', - 'sort_dir': 'desc', + "page_no": 1, + "bid_state_ind": -1, + "with_expired": True, + "limit": PAGE_LIMIT, + "sort_by": "created_at", + "sort_dir": "desc", } if available: - filters['bid_state_ind'] = BidStates.BID_RECEIVED - filters['with_expired'] = False + filters["bid_state_ind"] = BidStates.BID_RECEIVED + filters["with_expired"] = False - filter_prefix = 'page_bids_sent' if sent else 'page_bids_available' if available else 'page_bids_received' + filter_prefix = ( + "page_bids_sent" + if sent + else "page_bids_available" if available else "page_bids_received" + ) messages = [] - form_data = self.checkForm(post_string, 'bids', messages) + form_data = self.checkForm(post_string, "bids", messages) if form_data: - if have_data_entry(form_data, 'clearfilters'): + if have_data_entry(form_data, "clearfilters"): swap_client.clearFilters(filter_prefix) else: - if have_data_entry(form_data, 'sort_by'): - sort_by = get_data_entry(form_data, 'sort_by') - ensure(sort_by in ['created_at', ], 'Invalid sort by') - filters['sort_by'] = sort_by - if have_data_entry(form_data, 'sort_dir'): - sort_dir = get_data_entry(form_data, 'sort_dir') - ensure(sort_dir in ['asc', 'desc'], 'Invalid sort dir') - filters['sort_dir'] = sort_dir - if have_data_entry(form_data, 'state'): - state_ind = int(get_data_entry(form_data, 'state')) + if have_data_entry(form_data, "sort_by"): + sort_by = get_data_entry(form_data, "sort_by") + ensure( + sort_by + in [ + "created_at", + ], + "Invalid sort by", + ) + filters["sort_by"] = sort_by + if have_data_entry(form_data, "sort_dir"): + sort_dir = get_data_entry(form_data, "sort_dir") + ensure(sort_dir in ["asc", "desc"], "Invalid sort dir") + filters["sort_dir"] = sort_dir + if have_data_entry(form_data, "state"): + state_ind = int(get_data_entry(form_data, "state")) if state_ind != -1: try: - state = BidStates(state_ind) - except Exception: - raise ValueError('Invalid state') - filters['bid_state_ind'] = state_ind - if have_data_entry(form_data, 'with_expired'): - with_expired = toBool(get_data_entry(form_data, 'with_expired')) - filters['with_expired'] = with_expired + _ = BidStates(state_ind) + except Exception as e: # noqa: F841 + raise ValueError("Invalid state") + filters["bid_state_ind"] = state_ind + if have_data_entry(form_data, "with_expired"): + with_expired = toBool(get_data_entry(form_data, "with_expired")) + filters["with_expired"] = with_expired set_pagination_filters(form_data, filters) - if have_data_entry(form_data, 'applyfilters'): + if have_data_entry(form_data, "applyfilters"): swap_client.setFilters(filter_prefix, filters) else: saved_filters = swap_client.getFilters(filter_prefix) @@ -181,21 +216,40 @@ def page_bids(self, url_split, post_string, sent=False, available=False, receive bids = swap_client.listBids(sent=sent, filters=filters) page_data = { - 'bid_states': listBidStates(), + "bid_states": listBidStates(), } - template = server.env.get_template('bids.html') - return self.render_template(template, { - 'page_type_sent': 'Bids Sent' if sent else '', - 'page_type_available': 'Bids Available' if available else '', - 'page_type_received': 'Received Bids' if received else '', - 'page_type_sent_description': 'All the bids you have placed on offers.' if sent else '', - 'page_type_available_description': 'Bids available for you to accept.' if available else '', - 'page_type_received_description': 'All the bids placed on your offers.' if received else '', - 'messages': messages, - 'filters': filters, - 'data': page_data, - 'summary': summary, - 'bids': [(format_timestamp(b[0]), - b[2].hex(), b[3].hex(), strBidState(b[5]), strTxState(b[7]), strTxState(b[8]), b[11]) for b in bids], - 'bids_count': len(bids), - }) + template = server.env.get_template("bids.html") + return self.render_template( + template, + { + "page_type_sent": "Bids Sent" if sent else "", + "page_type_available": "Bids Available" if available else "", + "page_type_received": "Received Bids" if received else "", + "page_type_sent_description": ( + "All the bids you have placed on offers." if sent else "" + ), + "page_type_available_description": ( + "Bids available for you to accept." if available else "" + ), + "page_type_received_description": ( + "All the bids placed on your offers." if received else "" + ), + "messages": messages, + "filters": filters, + "data": page_data, + "summary": summary, + "bids": [ + ( + format_timestamp(b[0]), + b[2].hex(), + b[3].hex(), + strBidState(b[5]), + strTxState(b[7]), + strTxState(b[8]), + b[11], + ) + for b in bids + ], + "bids_count": len(bids), + }, + ) diff --git a/basicswap/ui/page_debug.py b/basicswap/ui/page_debug.py index 542abeb..a4a3d42 100644 --- a/basicswap/ui/page_debug.py +++ b/basicswap/ui/page_debug.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2023 The BSX Developers +# Copyright (c) 2023-2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -25,31 +25,34 @@ def page_debug(self, url_split, post_string): result = None messages = [] err_messages = [] - form_data = self.checkForm(post_string, 'wallets', err_messages) + form_data = self.checkForm(post_string, "wallets", err_messages) if form_data: - if have_data_entry(form_data, 'reinit_xmr'): + if have_data_entry(form_data, "reinit_xmr"): try: swap_client.initialiseWallet(Coins.XMR) - messages.append('Done.') + messages.append("Done.") except Exception as e: - err_messages.append('Failed.') + err_messages.append(f"Failed: {e}.") - if have_data_entry(form_data, 'remove_expired'): + if have_data_entry(form_data, "remove_expired"): try: - swap_client.log.warning('Removing expired data.') + swap_client.log.warning("Removing expired data.") remove_expired_data(swap_client) - messages.append('Done.') + messages.append("Done.") except Exception as e: if swap_client.debug is True: swap_client.log.error(traceback.format_exc()) else: - swap_client.log.error(f'remove_expired_data: {e}') - err_messages.append('Failed.') + swap_client.log.error(f"remove_expired_data: {e}") + err_messages.append("Failed.") - template = server.env.get_template('debug.html') - return self.render_template(template, { - 'messages': messages, - 'err_messages': err_messages, - 'result': result, - 'summary': summary, - }) + template = server.env.get_template("debug.html") + return self.render_template( + template, + { + "messages": messages, + "err_messages": err_messages, + "result": result, + "summary": summary, + }, + ) diff --git a/basicswap/ui/page_encryption.py b/basicswap/ui/page_encryption.py index ae2d8c0..d34a252 100644 --- a/basicswap/ui/page_encryption.py +++ b/basicswap/ui/page_encryption.py @@ -17,47 +17,52 @@ def page_changepassword(self, url_split, post_string): messages = [] err_messages = [] - form_data = self.checkForm(post_string, 'changepassword', err_messages) + form_data = self.checkForm(post_string, "changepassword", err_messages) if form_data: - old_password = get_data_entry_or(form_data, 'oldpassword', '') - new_password = get_data_entry_or(form_data, 'newpassword', '') - confirm_password = get_data_entry_or(form_data, 'confirmpassword', '') + old_password = get_data_entry_or(form_data, "oldpassword", "") + new_password = get_data_entry_or(form_data, "newpassword", "") + confirm_password = get_data_entry_or(form_data, "confirmpassword", "") try: - if new_password == '': - raise ValueError('New password must be entered.') + if new_password == "": + raise ValueError("New password must be entered.") if new_password != confirm_password: - raise ValueError('New password and confirm password must match.') + raise ValueError("New password and confirm password must match.") swap_client.changeWalletPasswords(old_password, new_password) - messages.append('Password changed') + messages.append("Password changed") except Exception as e: err_messages.append(str(e)) - template = server.env.get_template('changepassword.html') - return self.render_template(template, { - 'messages': messages, - 'err_messages': err_messages, - 'summary': swap_client.getSummary(), - }) + template = server.env.get_template("changepassword.html") + return self.render_template( + template, + { + "messages": messages, + "err_messages": err_messages, + "summary": swap_client.getSummary(), + }, + ) def page_unlock(self, url_split, post_string): server = self.server swap_client = server.swap_client - messages = ['Warning: This will unlock the system for all users!', ] + messages = [ + "Warning: This will unlock the system for all users!", + ] err_messages = [] - form_data = self.checkForm(post_string, 'unlock', err_messages) + form_data = self.checkForm(post_string, "unlock", err_messages) if form_data: - password = get_data_entry_or(form_data, 'password', '') + password = get_data_entry_or(form_data, "password", "") try: - if password == '': - raise ValueError('Password must be entered.') + if password == "": + raise ValueError("Password must be entered.") swap_client.unlockWallets(password) self.send_response(302) - self.send_header('Location', '/') + self.send_header("Location", "/") self.end_headers() return bytes() except Exception as e: @@ -65,11 +70,14 @@ def page_unlock(self, url_split, post_string): swap_client.log.error(str(e)) err_messages.append(str(e)) - template = server.env.get_template('unlock.html') - return self.render_template(template, { - 'messages': messages, - 'err_messages': err_messages, - }) + template = server.env.get_template("unlock.html") + return self.render_template( + template, + { + "messages": messages, + "err_messages": err_messages, + }, + ) def page_lock(self, url_split, post_string): @@ -79,6 +87,6 @@ def page_lock(self, url_split, post_string): swap_client.lockWallets() self.send_response(302) - self.send_header('Location', '/') + self.send_header("Location", "/") self.end_headers() return bytes() diff --git a/basicswap/ui/page_identity.py b/basicswap/ui/page_identity.py index db15dad..8b7cdc3 100644 --- a/basicswap/ui/page_identity.py +++ b/basicswap/ui/page_identity.py @@ -25,54 +25,71 @@ def page_identity(self, url_split, post_string): swap_client.checkSystemStatus() summary = swap_client.getSummary() - ensure(len(url_split) > 2, 'Address not specified') + ensure(len(url_split) > 2, "Address not specified") identity_address = url_split[2] - page_data = {'identity_address': identity_address} + page_data = {"identity_address": identity_address} messages = [] err_messages = [] - form_data = self.checkForm(post_string, 'identity', err_messages) + form_data = self.checkForm(post_string, "identity", err_messages) if form_data: - if have_data_entry(form_data, 'edit'): - page_data['show_edit_form'] = True - if have_data_entry(form_data, 'apply'): + if have_data_entry(form_data, "edit"): + page_data["show_edit_form"] = True + if have_data_entry(form_data, "apply"): try: data = { - 'label': get_data_entry_or(form_data, 'label', ''), - 'note': get_data_entry_or(form_data, 'note', ''), - 'automation_override': get_data_entry(form_data, 'automation_override'), + "label": get_data_entry_or(form_data, "label", ""), + "note": get_data_entry_or(form_data, "note", ""), + "automation_override": get_data_entry( + form_data, "automation_override" + ), } - swap_client.setIdentityData({'address': identity_address}, data) - messages.append('Updated') + swap_client.setIdentityData({"address": identity_address}, data) + messages.append("Updated") except Exception as e: err_messages.append(str(e)) try: identity = swap_client.getIdentity(identity_address) if identity is None: - raise ValueError('Unknown address') + raise ValueError("Unknown address") automation_override = zeroIfNone(identity.automation_override) - page_data.update({ - 'label': '' if identity.label is None else identity.label, - 'num_sent_bids_successful': zeroIfNone(identity.num_sent_bids_successful), - 'num_recv_bids_successful': zeroIfNone(identity.num_recv_bids_successful), - 'num_sent_bids_rejected': zeroIfNone(identity.num_sent_bids_rejected), - 'num_recv_bids_rejected': zeroIfNone(identity.num_recv_bids_rejected), - 'num_sent_bids_failed': zeroIfNone(identity.num_sent_bids_failed), - 'num_recv_bids_failed': zeroIfNone(identity.num_recv_bids_failed), - 'automation_override': automation_override, - 'str_automation_override': strAutomationOverrideOption(automation_override), - 'note': '' if identity.note is None else identity.note, - }) + page_data.update( + { + "label": "" if identity.label is None else identity.label, + "num_sent_bids_successful": zeroIfNone( + identity.num_sent_bids_successful + ), + "num_recv_bids_successful": zeroIfNone( + identity.num_recv_bids_successful + ), + "num_sent_bids_rejected": zeroIfNone(identity.num_sent_bids_rejected), + "num_recv_bids_rejected": zeroIfNone(identity.num_recv_bids_rejected), + "num_sent_bids_failed": zeroIfNone(identity.num_sent_bids_failed), + "num_recv_bids_failed": zeroIfNone(identity.num_recv_bids_failed), + "automation_override": automation_override, + "str_automation_override": strAutomationOverrideOption( + automation_override + ), + "note": "" if identity.note is None else identity.note, + } + ) except Exception as e: err_messages.append(e) - template = server.env.get_template('identity.html') - return self.render_template(template, { - 'messages': messages, - 'err_messages': err_messages, - 'data': page_data, - 'automation_override_options': [(int(opt), strAutomationOverrideOption(opt)) for opt in AutomationOverrideOptions if opt > 0], - 'summary': summary, - }) + template = server.env.get_template("identity.html") + return self.render_template( + template, + { + "messages": messages, + "err_messages": err_messages, + "data": page_data, + "automation_override_options": [ + (int(opt), strAutomationOverrideOption(opt)) + for opt in AutomationOverrideOptions + if opt > 0 + ], + "summary": summary, + }, + ) diff --git a/basicswap/ui/page_offers.py b/basicswap/ui/page_offers.py index 5c795c1..95ab258 100644 --- a/basicswap/ui/page_offers.py +++ b/basicswap/ui/page_offers.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2022-2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -41,12 +42,14 @@ from basicswap.chainparams import ( Coins, ) -default_chart_api_key = '95dd900af910656e0e17c41f2ddc5dba77d01bf8b0e7d2787634a16bd976c553' -default_coingecko_api_key = 'CG-8hm3r9iLfpEXv4ied8oLbeUj' +default_chart_api_key = ( + "95dd900af910656e0e17c41f2ddc5dba77d01bf8b0e7d2787634a16bd976c553" +) +default_coingecko_api_key = "CG-8hm3r9iLfpEXv4ied8oLbeUj" def value_or_none(v): - if v == -1 or v == '-1': + if v == -1 or v == "-1": return None return v @@ -54,209 +57,256 @@ def value_or_none(v): def decode_offer_id(v): try: offer_id = bytes.fromhex(v) - ensure(len(offer_id) == 28, 'Bad offer ID') + ensure(len(offer_id) == 28, "Bad offer ID") return offer_id except Exception: - raise ValueError('Bad offer ID') + raise ValueError("Bad offer ID") def swap_type_from_string(str_swap_type: str) -> SwapTypes: - if str_swap_type == 'seller_first' or str_swap_type == 'secret_hash': + if str_swap_type == "seller_first" or str_swap_type == "secret_hash": return SwapTypes.SELLER_FIRST - elif str_swap_type == 'xmr_swap' or str_swap_type == 'adaptor_sig': + elif str_swap_type == "xmr_swap" or str_swap_type == "adaptor_sig": return SwapTypes.XMR_SWAP else: - raise ValueError('Unknown swap type') + raise ValueError("Unknown swap type") def parseOfferFormData(swap_client, form_data, page_data, options={}): errors = [] parsed_data = {} - if have_data_entry(form_data, 'addr_to'): - page_data['addr_to'] = get_data_entry(form_data, 'addr_to') - addr_to = value_or_none(page_data['addr_to']) + if have_data_entry(form_data, "addr_to"): + page_data["addr_to"] = get_data_entry(form_data, "addr_to") + addr_to = value_or_none(page_data["addr_to"]) if addr_to is not None: - parsed_data['addr_to'] = addr_to + parsed_data["addr_to"] = addr_to - if have_data_entry(form_data, 'addr_from'): - page_data['addr_from'] = get_data_entry(form_data, 'addr_from') - parsed_data['addr_from'] = value_or_none(page_data['addr_from']) + if have_data_entry(form_data, "addr_from"): + page_data["addr_from"] = get_data_entry(form_data, "addr_from") + parsed_data["addr_from"] = value_or_none(page_data["addr_from"]) else: - parsed_data['addr_from'] = None + parsed_data["addr_from"] = None try: - page_data['coin_from'] = getCoinType(get_data_entry(form_data, 'coin_from')) - coin_from = Coins(page_data['coin_from']) + page_data["coin_from"] = getCoinType(get_data_entry(form_data, "coin_from")) + coin_from = Coins(page_data["coin_from"]) ci_from = swap_client.ci(coin_from) if coin_from not in (Coins.XMR, Coins.WOW): - page_data['fee_from_conf'] = ci_from._conf_target # Set default value - parsed_data['coin_from'] = coin_from + page_data["fee_from_conf"] = ci_from._conf_target # Set default value + parsed_data["coin_from"] = coin_from except Exception: - errors.append('Unknown Coin From') + errors.append("Unknown Coin From") try: - page_data['coin_to'] = getCoinType(get_data_entry(form_data, 'coin_to')) - coin_to = Coins(page_data['coin_to']) + page_data["coin_to"] = getCoinType(get_data_entry(form_data, "coin_to")) + coin_to = Coins(page_data["coin_to"]) ci_to = swap_client.ci(coin_to) if coin_to not in (Coins.XMR, Coins.WOW): - page_data['fee_to_conf'] = ci_to._conf_target # Set default value - parsed_data['coin_to'] = coin_to + page_data["fee_to_conf"] = ci_to._conf_target # Set default value + parsed_data["coin_to"] = coin_to except Exception: - errors.append('Unknown Coin To') + errors.append("Unknown Coin To") if coin_from == coin_to: - errors.append('Coins from and to must be different.') + errors.append("Coins from and to must be different.") try: - page_data['amt_from'] = get_data_entry(form_data, 'amt_from') - parsed_data['amt_from'] = inputAmount(page_data['amt_from'], ci_from) + page_data["amt_from"] = get_data_entry(form_data, "amt_from") + parsed_data["amt_from"] = inputAmount(page_data["amt_from"], ci_from) except Exception: - errors.append('Amount From') + errors.append("Amount From") try: - if have_data_entry(form_data, 'amt_bid_min') is False: - if options.get('add_min_bid_amt', False) is True: - parsed_data['amt_bid_min'] = ci_from.chainparams_network()['min_amount'] + if have_data_entry(form_data, "amt_bid_min") is False: + if options.get("add_min_bid_amt", False) is True: + parsed_data["amt_bid_min"] = ci_from.chainparams_network()["min_amount"] else: - raise ValueError('missing') + raise ValueError("missing") else: - page_data['amt_bid_min'] = get_data_entry(form_data, 'amt_bid_min') - parsed_data['amt_bid_min'] = inputAmount(page_data['amt_bid_min'], ci_from) + page_data["amt_bid_min"] = get_data_entry(form_data, "amt_bid_min") + parsed_data["amt_bid_min"] = inputAmount(page_data["amt_bid_min"], ci_from) - if parsed_data['amt_bid_min'] < 0 or parsed_data['amt_bid_min'] > parsed_data['amt_from']: - errors.append('Minimum Bid Amount out of range') + if ( + parsed_data["amt_bid_min"] < 0 + or parsed_data["amt_bid_min"] > parsed_data["amt_from"] + ): + errors.append("Minimum Bid Amount out of range") except Exception: - errors.append('Minimum Bid Amount') + errors.append("Minimum Bid Amount") - if (have_data_entry(form_data, 'rate') and not have_data_entry(form_data, 'amt_to')): - parsed_data['rate'] = ci_to.make_int(form_data['rate'], r=1) - page_data['rate'] = ci_to.format_amount(parsed_data['rate']) + if have_data_entry(form_data, "rate") and not have_data_entry(form_data, "amt_to"): + parsed_data["rate"] = ci_to.make_int(form_data["rate"], r=1) + page_data["rate"] = ci_to.format_amount(parsed_data["rate"]) else: try: - page_data['amt_to'] = get_data_entry(form_data, 'amt_to') - parsed_data['amt_to'] = inputAmount(page_data['amt_to'], ci_to) + page_data["amt_to"] = get_data_entry(form_data, "amt_to") + parsed_data["amt_to"] = inputAmount(page_data["amt_to"], ci_to) except Exception: - errors.append('Amount To') + errors.append("Amount To") - if 'amt_to' in parsed_data and 'amt_from' in parsed_data: - parsed_data['rate'] = ci_from.make_int(parsed_data['amt_to'] / parsed_data['amt_from'], r=1) - page_data['rate'] = ci_to.format_amount(parsed_data['rate']) + if "amt_to" in parsed_data and "amt_from" in parsed_data: + parsed_data["rate"] = ci_from.make_int( + parsed_data["amt_to"] / parsed_data["amt_from"], r=1 + ) + page_data["rate"] = ci_to.format_amount(parsed_data["rate"]) - if 'amt_to' not in parsed_data and 'rate' in parsed_data and 'amt_from' in parsed_data: - parsed_data['amt_to'] = int((parsed_data['amt_from'] * parsed_data['rate']) // ci_from.COIN()) + if ( + "amt_to" not in parsed_data + and "rate" in parsed_data + and "amt_from" in parsed_data + ): + parsed_data["amt_to"] = int( + (parsed_data["amt_from"] * parsed_data["rate"]) // ci_from.COIN() + ) - page_data['amt_var'] = True if have_data_entry(form_data, 'amt_var') else False - parsed_data['amt_var'] = page_data['amt_var'] - page_data['rate_var'] = True if have_data_entry(form_data, 'rate_var') else False - parsed_data['rate_var'] = page_data['rate_var'] + page_data["amt_var"] = True if have_data_entry(form_data, "amt_var") else False + parsed_data["amt_var"] = page_data["amt_var"] + page_data["rate_var"] = True if have_data_entry(form_data, "rate_var") else False + parsed_data["rate_var"] = page_data["rate_var"] - page_data['automation_strat_id'] = int(get_data_entry_or(form_data, 'automation_strat_id', -1)) - parsed_data['automation_strat_id'] = page_data['automation_strat_id'] + page_data["automation_strat_id"] = int( + get_data_entry_or(form_data, "automation_strat_id", -1) + ) + parsed_data["automation_strat_id"] = page_data["automation_strat_id"] swap_type = -1 - if have_data_entry(form_data, 'subfee'): - parsed_data['subfee'] = True - if have_data_entry(form_data, 'swap_type'): - page_data['swap_type'] = get_data_entry(form_data, 'swap_type') - parsed_data['swap_type'] = page_data['swap_type'] - swap_type = swap_type_from_string(parsed_data['swap_type']) - elif parsed_data['coin_from'] in swap_client.adaptor_swap_only_coins or parsed_data['coin_to'] in swap_client.adaptor_swap_only_coins: - parsed_data['swap_type'] = strSwapType(SwapTypes.XMR_SWAP) + if have_data_entry(form_data, "subfee"): + parsed_data["subfee"] = True + if have_data_entry(form_data, "swap_type"): + page_data["swap_type"] = get_data_entry(form_data, "swap_type") + parsed_data["swap_type"] = page_data["swap_type"] + swap_type = swap_type_from_string(parsed_data["swap_type"]) + elif ( + parsed_data["coin_from"] in swap_client.adaptor_swap_only_coins + or parsed_data["coin_to"] in swap_client.adaptor_swap_only_coins + ): + parsed_data["swap_type"] = strSwapType(SwapTypes.XMR_SWAP) swap_type = SwapTypes.XMR_SWAP else: - parsed_data['swap_type'] = strSwapType(SwapTypes.SELLER_FIRST) + parsed_data["swap_type"] = strSwapType(SwapTypes.SELLER_FIRST) swap_type = SwapTypes.SELLER_FIRST if swap_type == SwapTypes.XMR_SWAP: - page_data['swap_style'] = 'xmr' + page_data["swap_style"] = "xmr" else: - page_data['swap_style'] = 'atomic' + page_data["swap_style"] = "atomic" - if 'swap_type' in parsed_data: + if "swap_type" in parsed_data: try: swap_client.validateSwapType(coin_from, coin_to, swap_type) except Exception as e: - errors.append(f'{e}') + errors.append(f"{e}") - if have_data_entry(form_data, 'step1'): - if len(errors) == 0 and have_data_entry(form_data, 'continue'): - page_data['step2'] = True + if have_data_entry(form_data, "step1"): + if len(errors) == 0 and have_data_entry(form_data, "continue"): + page_data["step2"] = True return parsed_data, errors - page_data['step2'] = True + page_data["step2"] = True - if have_data_entry(form_data, 'fee_from_conf'): - page_data['fee_from_conf'] = int(get_data_entry(form_data, 'fee_from_conf')) - parsed_data['fee_from_conf'] = page_data['fee_from_conf'] + if have_data_entry(form_data, "fee_from_conf"): + page_data["fee_from_conf"] = int(get_data_entry(form_data, "fee_from_conf")) + parsed_data["fee_from_conf"] = page_data["fee_from_conf"] - if have_data_entry(form_data, 'fee_from_extra'): - page_data['fee_from_extra'] = int(get_data_entry(form_data, 'fee_from_extra')) - parsed_data['fee_from_extra'] = page_data['fee_from_extra'] + if have_data_entry(form_data, "fee_from_extra"): + page_data["fee_from_extra"] = int(get_data_entry(form_data, "fee_from_extra")) + parsed_data["fee_from_extra"] = page_data["fee_from_extra"] - if have_data_entry(form_data, 'fee_to_conf'): - page_data['fee_to_conf'] = int(get_data_entry(form_data, 'fee_to_conf')) - parsed_data['fee_to_conf'] = page_data['fee_to_conf'] + if have_data_entry(form_data, "fee_to_conf"): + page_data["fee_to_conf"] = int(get_data_entry(form_data, "fee_to_conf")) + parsed_data["fee_to_conf"] = page_data["fee_to_conf"] - if have_data_entry(form_data, 'fee_to_extra'): - page_data['fee_to_extra'] = int(get_data_entry(form_data, 'fee_to_extra')) - parsed_data['fee_to_extra'] = page_data['fee_to_extra'] + if have_data_entry(form_data, "fee_to_extra"): + page_data["fee_to_extra"] = int(get_data_entry(form_data, "fee_to_extra")) + parsed_data["fee_to_extra"] = page_data["fee_to_extra"] - if have_data_entry(form_data, 'check_offer'): - page_data['check_offer'] = True - if have_data_entry(form_data, 'submit_offer'): - page_data['submit_offer'] = True + if have_data_entry(form_data, "check_offer"): + page_data["check_offer"] = True + if have_data_entry(form_data, "submit_offer"): + page_data["submit_offer"] = True - if have_data_entry(form_data, 'lockhrs'): - page_data['lockhrs'] = int(get_data_entry(form_data, 'lockhrs')) - parsed_data['lock_seconds'] = page_data['lockhrs'] * 60 * 60 - elif have_data_entry(form_data, 'lockmins'): - page_data['lockmins'] = int(get_data_entry(form_data, 'lockmins')) - parsed_data['lock_seconds'] = page_data['lockmins'] * 60 - elif have_data_entry(form_data, 'lockseconds'): - parsed_data['lock_seconds'] = int(get_data_entry(form_data, 'lockseconds')) + if have_data_entry(form_data, "lockhrs"): + page_data["lockhrs"] = int(get_data_entry(form_data, "lockhrs")) + parsed_data["lock_seconds"] = page_data["lockhrs"] * 60 * 60 + elif have_data_entry(form_data, "lockmins"): + page_data["lockmins"] = int(get_data_entry(form_data, "lockmins")) + parsed_data["lock_seconds"] = page_data["lockmins"] * 60 + elif have_data_entry(form_data, "lockseconds"): + parsed_data["lock_seconds"] = int(get_data_entry(form_data, "lockseconds")) - if have_data_entry(form_data, 'validhrs'): - page_data['validhrs'] = int(get_data_entry(form_data, 'validhrs')) - parsed_data['valid_for_seconds'] = page_data['validhrs'] * 60 * 60 - elif have_data_entry(form_data, 'valid_for_seconds'): - parsed_data['valid_for_seconds'] = int(get_data_entry(form_data, 'valid_for_seconds')) + if have_data_entry(form_data, "validhrs"): + page_data["validhrs"] = int(get_data_entry(form_data, "validhrs")) + parsed_data["valid_for_seconds"] = page_data["validhrs"] * 60 * 60 + elif have_data_entry(form_data, "valid_for_seconds"): + parsed_data["valid_for_seconds"] = int( + get_data_entry(form_data, "valid_for_seconds") + ) try: - if len(errors) == 0 and page_data['swap_style'] == 'xmr': + if len(errors) == 0 and page_data["swap_style"] == "xmr": reverse_bid: bool = swap_client.is_reverse_ads_bid(coin_from, coin_to) - ci_leader = ci_to if reverse_bid else ci_from - ci_follower = ci_from if reverse_bid else ci_to - if have_data_entry(form_data, 'fee_rate_from'): - page_data['from_fee_override'] = get_data_entry(form_data, 'fee_rate_from') - parsed_data['from_fee_override'] = page_data['from_fee_override'] + if have_data_entry(form_data, "fee_rate_from"): + page_data["from_fee_override"] = get_data_entry( + form_data, "fee_rate_from" + ) + parsed_data["from_fee_override"] = page_data["from_fee_override"] else: - from_fee_override, page_data['from_fee_src'] = swap_client.getFeeRateForCoin(parsed_data['coin_from'], page_data['fee_from_conf']) - if page_data['fee_from_extra'] > 0: - from_fee_override += from_fee_override * (float(page_data['fee_from_extra']) / 100.0) - page_data['from_fee_override'] = ci_from.format_amount(ci_from.make_int(from_fee_override, r=1)) - parsed_data['from_fee_override'] = page_data['from_fee_override'] + from_fee_override, page_data["from_fee_src"] = ( + swap_client.getFeeRateForCoin( + parsed_data["coin_from"], page_data["fee_from_conf"] + ) + ) + if page_data["fee_from_extra"] > 0: + from_fee_override += from_fee_override * ( + float(page_data["fee_from_extra"]) / 100.0 + ) + page_data["from_fee_override"] = ci_from.format_amount( + ci_from.make_int(from_fee_override, r=1) + ) + parsed_data["from_fee_override"] = page_data["from_fee_override"] - lock_spend_tx_vsize = ci_from.xmr_swap_b_lock_spend_tx_vsize() if reverse_bid else ci_from.xmr_swap_a_lock_spend_tx_vsize() - lock_spend_tx_fee = ci_from.make_int(ci_from.make_int(from_fee_override, r=1) * lock_spend_tx_vsize / 1000, r=1) - page_data['amt_from_lock_spend_tx_fee'] = ci_from.format_amount(lock_spend_tx_fee // ci_from.COIN()) - page_data['tla_from'] = ci_from.ticker() + lock_spend_tx_vsize = ( + ci_from.xmr_swap_b_lock_spend_tx_vsize() + if reverse_bid + else ci_from.xmr_swap_a_lock_spend_tx_vsize() + ) + lock_spend_tx_fee = ci_from.make_int( + ci_from.make_int(from_fee_override, r=1) + * lock_spend_tx_vsize + / 1000, + r=1, + ) + page_data["amt_from_lock_spend_tx_fee"] = ci_from.format_amount( + lock_spend_tx_fee // ci_from.COIN() + ) + page_data["tla_from"] = ci_from.ticker() if ci_to in (Coins.XMR, Coins.WOW): - if have_data_entry(form_data, 'fee_rate_to'): - page_data['to_fee_override'] = get_data_entry(form_data, 'fee_rate_to') - parsed_data['to_fee_override'] = page_data['to_fee_override'] + if have_data_entry(form_data, "fee_rate_to"): + page_data["to_fee_override"] = get_data_entry( + form_data, "fee_rate_to" + ) + parsed_data["to_fee_override"] = page_data["to_fee_override"] else: - to_fee_override, page_data['to_fee_src'] = swap_client.getFeeRateForCoin(parsed_data['coin_to'], page_data['fee_to_conf']) - if page_data['fee_to_extra'] > 0: - to_fee_override += to_fee_override * (float(page_data['fee_to_extra']) / 100.0) - page_data['to_fee_override'] = ci_to.format_amount(ci_to.make_int(to_fee_override, r=1)) - parsed_data['to_fee_override'] = page_data['to_fee_override'] + to_fee_override, page_data["to_fee_src"] = ( + swap_client.getFeeRateForCoin( + parsed_data["coin_to"], page_data["fee_to_conf"] + ) + ) + if page_data["fee_to_extra"] > 0: + to_fee_override += to_fee_override * ( + float(page_data["fee_to_extra"]) / 100.0 + ) + page_data["to_fee_override"] = ci_to.format_amount( + ci_to.make_int(to_fee_override, r=1) + ) + parsed_data["to_fee_override"] = page_data["to_fee_override"] except Exception as e: # Error is expected if missing fields if swap_client.debug is True: - swap_client.log.warning(f'parseOfferFormData failed to set fee: Error {e}') + swap_client.log.warning(f"parseOfferFormData failed to set fee: Error {e}") return parsed_data, errors @@ -264,91 +314,100 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}): def postNewOfferFromParsed(swap_client, parsed_data): swap_type = SwapTypes.SELLER_FIRST - if 'swap_type' in parsed_data: - str_swap_type = parsed_data['swap_type'].lower() + if "swap_type" in parsed_data: + str_swap_type = parsed_data["swap_type"].lower() swap_type = swap_type_from_string(str_swap_type) - elif parsed_data['coin_from'] in swap_client.scriptless_coins or parsed_data['coin_to'] in swap_client.scriptless_coins: + elif ( + parsed_data["coin_from"] in swap_client.scriptless_coins + or parsed_data["coin_to"] in swap_client.scriptless_coins + ): swap_type = SwapTypes.XMR_SWAP if swap_type == SwapTypes.XMR_SWAP: # All coins capable of segwit should be capable of csv lock_type = TxLockTypes.SEQUENCE_LOCK_TIME else: - if swap_client.coin_clients[parsed_data['coin_from']]['use_csv'] and swap_client.coin_clients[parsed_data['coin_to']]['use_csv']: + if ( + swap_client.coin_clients[parsed_data["coin_from"]]["use_csv"] + and swap_client.coin_clients[parsed_data["coin_to"]]["use_csv"] + ): lock_type = TxLockTypes.SEQUENCE_LOCK_TIME else: lock_type = TxLockTypes.ABS_LOCK_TIME extra_options = {} - if 'fee_from_conf' in parsed_data: - extra_options['from_fee_conf_target'] = parsed_data['fee_from_conf'] - if 'from_fee_multiplier_percent' in parsed_data: - extra_options['from_fee_multiplier_percent'] = parsed_data['fee_from_extra'] - if 'from_fee_override' in parsed_data: - extra_options['from_fee_override'] = parsed_data['from_fee_override'] + if "fee_from_conf" in parsed_data: + extra_options["from_fee_conf_target"] = parsed_data["fee_from_conf"] + if "from_fee_multiplier_percent" in parsed_data: + extra_options["from_fee_multiplier_percent"] = parsed_data["fee_from_extra"] + if "from_fee_override" in parsed_data: + extra_options["from_fee_override"] = parsed_data["from_fee_override"] - if 'fee_to_conf' in parsed_data: - extra_options['to_fee_conf_target'] = parsed_data['fee_to_conf'] - if 'to_fee_multiplier_percent' in parsed_data: - extra_options['to_fee_multiplier_percent'] = parsed_data['fee_to_extra'] - if 'to_fee_override' in parsed_data: - extra_options['to_fee_override'] = parsed_data['to_fee_override'] - if 'valid_for_seconds' in parsed_data: - extra_options['valid_for_seconds'] = parsed_data['valid_for_seconds'] + if "fee_to_conf" in parsed_data: + extra_options["to_fee_conf_target"] = parsed_data["fee_to_conf"] + if "to_fee_multiplier_percent" in parsed_data: + extra_options["to_fee_multiplier_percent"] = parsed_data["fee_to_extra"] + if "to_fee_override" in parsed_data: + extra_options["to_fee_override"] = parsed_data["to_fee_override"] + if "valid_for_seconds" in parsed_data: + extra_options["valid_for_seconds"] = parsed_data["valid_for_seconds"] - if 'addr_to' in parsed_data: - extra_options['addr_send_to'] = parsed_data['addr_to'] + if "addr_to" in parsed_data: + extra_options["addr_send_to"] = parsed_data["addr_to"] - if parsed_data.get('amt_var', False): - extra_options['amount_negotiable'] = parsed_data['amt_var'] - if parsed_data.get('rate_var', False): - extra_options['rate_negotiable'] = parsed_data['rate_var'] + if parsed_data.get("amt_var", False): + extra_options["amount_negotiable"] = parsed_data["amt_var"] + if parsed_data.get("rate_var", False): + extra_options["rate_negotiable"] = parsed_data["rate_var"] - if parsed_data.get('rate_var', None) is not None: - extra_options['rate_negotiable'] = parsed_data['rate_var'] + if parsed_data.get("rate_var", None) is not None: + extra_options["rate_negotiable"] = parsed_data["rate_var"] - if parsed_data.get('automation_strat_id', None) is not None: - extra_options['automation_id'] = parsed_data['automation_strat_id'] + if parsed_data.get("automation_strat_id", None) is not None: + extra_options["automation_id"] = parsed_data["automation_strat_id"] - swap_value = parsed_data['amt_from'] - if parsed_data.get('amt_to', None) is not None: - extra_options['amount_to'] = parsed_data['amt_to'] - if parsed_data.get('subfee', False): - ci_from = swap_client.ci(parsed_data['coin_from']) + swap_value = parsed_data["amt_from"] + if parsed_data.get("amt_to", None) is not None: + extra_options["amount_to"] = parsed_data["amt_to"] + if parsed_data.get("subfee", False): + ci_from = swap_client.ci(parsed_data["coin_from"]) pi = swap_client.pi(swap_type) itx = pi.getFundedInitiateTxTemplate(ci_from, swap_value, True) itx_decoded = ci_from.describeTx(itx.hex()) n = pi.findMockVout(ci_from, itx_decoded) - swap_value = ci_from.make_int(itx_decoded['vout'][n]['value']) - extra_options = {'prefunded_itx': itx} + swap_value = ci_from.make_int(itx_decoded["vout"][n]["value"]) + extra_options = {"prefunded_itx": itx} offer_id = swap_client.postOffer( - parsed_data['coin_from'], - parsed_data['coin_to'], + parsed_data["coin_from"], + parsed_data["coin_to"], swap_value, - parsed_data['rate'], - parsed_data['amt_bid_min'], + parsed_data["rate"], + parsed_data["amt_bid_min"], swap_type, lock_type=lock_type, - lock_value=parsed_data['lock_seconds'], - addr_send_from=parsed_data['addr_from'], - extra_options=extra_options) + lock_value=parsed_data["lock_seconds"], + addr_send_from=parsed_data["addr_from"], + extra_options=extra_options, + ) return offer_id def postNewOffer(swap_client, form_data): page_data = {} - parsed_data, errors = parseOfferFormData(swap_client, form_data, page_data, options={'add_min_bid_amt': True}) + parsed_data, errors = parseOfferFormData( + swap_client, form_data, page_data, options={"add_min_bid_amt": True} + ) if len(errors) > 0: - raise ValueError('Parse errors: ' + ' '.join(errors)) + raise ValueError("Parse errors: " + " ".join(errors)) return postNewOfferFromParsed(swap_client, parsed_data) def offer_to_post_string(self, swap_client, offer_id): offer, xmr_offer = swap_client.getXmrOffer(offer_id) - ensure(offer, 'Unknown offer ID') + ensure(offer, "Unknown offer ID") ci_from = swap_client.ci(offer.coin_from) ci_to = swap_client.ci(offer.coin_to) @@ -357,35 +416,38 @@ def offer_to_post_string(self, swap_client, offer_id): if amount_to is None: amount_to = (offer.amount_from * offer.rate) // ci_from.COIN() offer_data = { - 'formid': self.generate_form_id(), - 'addr_to': offer.addr_to, - 'addr_from': offer.addr_from, - 'coin_from': offer.coin_from, - 'coin_to': offer.coin_to, + "formid": self.generate_form_id(), + "addr_to": offer.addr_to, + "addr_from": offer.addr_from, + "coin_from": offer.coin_from, + "coin_to": offer.coin_to, # TODO store fee conf, or pass directly # 'fee_from_conf' # 'fee_to_conf' - 'amt_from': ci_from.format_amount(offer.amount_from), - 'amt_bid_min': ci_from.format_amount(offer.min_bid_amount), - 'rate': ci_to.format_amount(offer.rate), - 'amt_to': ci_to.format_amount(amount_to), - 'validhrs': offer.time_valid // (60 * 60), - 'swap_type': strSwapType(offer.swap_type), + "amt_from": ci_from.format_amount(offer.amount_from), + "amt_bid_min": ci_from.format_amount(offer.min_bid_amount), + "rate": ci_to.format_amount(offer.rate), + "amt_to": ci_to.format_amount(amount_to), + "validhrs": offer.time_valid // (60 * 60), + "swap_type": strSwapType(offer.swap_type), } if offer.amount_negotiable: - offer_data['amt_var'] = True + offer_data["amt_var"] = True if offer.rate_negotiable: - offer_data['rate_var'] = True + offer_data["rate_var"] = True - if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME or offer.lock_type == TxLockTypes.ABS_LOCK_TIME: + if ( + offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME + or offer.lock_type == TxLockTypes.ABS_LOCK_TIME + ): if offer.lock_value > 60 * 60: - offer_data['lockhrs'] = offer.lock_value // (60 * 60) + offer_data["lockhrs"] = offer.lock_value // (60 * 60) else: - offer_data['lockhrs'] = offer.lock_value // 60 + offer_data["lockhrs"] = offer.lock_value // 60 try: strategy = swap_client.getLinkedStrategy(Concepts.OFFER, offer.offer_id) - offer_data['automation_strat_id'] = strategy[0] + offer_data["automation_strat_id"] = strategy[0] except Exception: pass # None found @@ -402,25 +464,25 @@ def page_newoffer(self, url_split, post_string): err_messages = [] page_data = { # Set defaults - 'addr_to': -1, - 'fee_from_conf': 2, - 'fee_to_conf': 2, - 'validhrs': 1, - 'lockhrs': 32, - 'lockmins': 30, # used in debug mode - 'debug_ui': swap_client.debug_ui, - 'automation_strat_id': -1, - 'amt_bid_min': format_amount(1000, 8), - 'swap_type': strSwapType(SwapTypes.SELLER_FIRST), + "addr_to": -1, + "fee_from_conf": 2, + "fee_to_conf": 2, + "validhrs": 1, + "lockhrs": 32, + "lockmins": 30, # used in debug mode + "debug_ui": swap_client.debug_ui, + "automation_strat_id": -1, + "amt_bid_min": format_amount(1000, 8), + "swap_type": strSwapType(SwapTypes.SELLER_FIRST), } post_data = parse.parse_qs(post_string) - if 'offer_from' in post_data: - offer_from_id_hex = post_data['offer_from'][0] + if "offer_from" in post_data: + offer_from_id_hex = post_data["offer_from"][0] offer_from_id = decode_offer_id(offer_from_id_hex) post_string = offer_to_post_string(self, swap_client, offer_from_id) - form_data = self.checkForm(post_string, 'newoffer', err_messages) + form_data = self.checkForm(post_string, "newoffer", err_messages) if form_data: try: @@ -432,79 +494,96 @@ def page_newoffer(self, url_split, post_string): swap_client.log.error(traceback.format_exc()) err_messages.append(str(e)) - if len(err_messages) == 0 and 'submit_offer' in page_data: + if len(err_messages) == 0 and "submit_offer" in page_data: try: offer_id = postNewOfferFromParsed(swap_client, parsed_data) - messages.append('Sent Offer {}'.format(offer_id.hex())) + messages.append( + 'Sent Offer {}'.format(offer_id.hex()) + ) page_data = {} except Exception as e: if swap_client.debug is True: swap_client.log.error(traceback.format_exc()) err_messages.append(str(e)) - if len(err_messages) == 0 and 'check_offer' in page_data: - template = server.env.get_template('offer_confirm.html') - elif 'step2' in page_data: - template = server.env.get_template('offer_new_2.html') + if len(err_messages) == 0 and "check_offer" in page_data: + template = server.env.get_template("offer_confirm.html") + elif "step2" in page_data: + template = server.env.get_template("offer_new_2.html") else: - template = server.env.get_template('offer_new_1.html') + template = server.env.get_template("offer_new_1.html") if swap_client.debug_ui: - messages.append('Debug mode active.') + messages.append("Debug mode active.") coins_from, coins_to = listAvailableCoins(swap_client, split_from=True) - automation_filters = {'type_ind': Concepts.OFFER, 'sort_by': 'label'} + automation_filters = {"type_ind": Concepts.OFFER, "sort_by": "label"} automation_strategies = swap_client.listAutomationStrategies(automation_filters) - chart_api_key = swap_client.settings.get('chart_api_key', '') - if chart_api_key == '': - chart_api_key_enc = swap_client.settings.get('chart_api_key_enc', '') - chart_api_key = default_chart_api_key if chart_api_key_enc == '' else bytes.fromhex(chart_api_key_enc).decode('utf-8') + chart_api_key = swap_client.settings.get("chart_api_key", "") + if chart_api_key == "": + chart_api_key_enc = swap_client.settings.get("chart_api_key_enc", "") + chart_api_key = ( + default_chart_api_key + if chart_api_key_enc == "" + else bytes.fromhex(chart_api_key_enc).decode("utf-8") + ) - coingecko_api_key = swap_client.settings.get('coingecko_api_key', '') - if coingecko_api_key == '': - coingecko_api_key_enc = swap_client.settings.get('coingecko_api_key_enc', '') - coingecko_api_key = default_coingecko_api_key if coingecko_api_key_enc == '' else bytes.fromhex(coingecko_api_key_enc).decode('utf-8') + coingecko_api_key = swap_client.settings.get("coingecko_api_key", "") + if coingecko_api_key == "": + coingecko_api_key_enc = swap_client.settings.get("coingecko_api_key_enc", "") + coingecko_api_key = ( + default_coingecko_api_key + if coingecko_api_key_enc == "" + else bytes.fromhex(coingecko_api_key_enc).decode("utf-8") + ) - return self.render_template(template, { - 'messages': messages, - 'err_messages': err_messages, - 'coins_from': coins_from, - 'coins': coins_to, - 'addrs': swap_client.listSMSGAddresses('offer_send_from'), - 'addrs_to': swap_client.listSMSGAddresses('offer_send_to'), - 'data': page_data, - 'automation_strategies': automation_strategies, - 'summary': summary, - 'swap_types': [(strSwapType(x), strSwapDesc(x)) for x in SwapTypes if strSwapType(x)], - 'show_chart': swap_client.settings.get('show_chart', True), - 'chart_api_key': chart_api_key, - 'coingecko_api_key': coingecko_api_key, - }) + return self.render_template( + template, + { + "messages": messages, + "err_messages": err_messages, + "coins_from": coins_from, + "coins": coins_to, + "addrs": swap_client.listSMSGAddresses("offer_send_from"), + "addrs_to": swap_client.listSMSGAddresses("offer_send_to"), + "data": page_data, + "automation_strategies": automation_strategies, + "summary": summary, + "swap_types": [ + (strSwapType(x), strSwapDesc(x)) for x in SwapTypes if strSwapType(x) + ], + "show_chart": swap_client.settings.get("show_chart", True), + "chart_api_key": chart_api_key, + "coingecko_api_key": coingecko_api_key, + }, + ) def page_offer(self, url_split, post_string): - ensure(len(url_split) > 2, 'Offer ID not specified') + ensure(len(url_split) > 2, "Offer ID not specified") offer_id = decode_offer_id(url_split[2]) server = self.server swap_client = server.swap_client swap_client.checkSystemStatus() summary = swap_client.getSummary() offer, xmr_offer = swap_client.getXmrOffer(offer_id) - ensure(offer, 'Unknown offer ID') + ensure(offer, "Unknown offer ID") extend_data = { # Defaults - 'nb_validmins': 10, + "nb_validmins": 10, } messages = [] err_messages = [] if swap_client.debug_ui: - messages.append('Debug mode active.') + messages.append("Debug mode active.") sent_bid_id = None show_bid_form = None show_edit_form = None - form_data = self.checkForm(post_string, 'offer', err_messages) + form_data = self.checkForm(post_string, "offer", err_messages) ci_from = swap_client.ci(Coins(offer.coin_from)) ci_to = swap_client.ci(Coins(offer.coin_to)) @@ -517,66 +596,75 @@ def page_offer(self, url_split, post_string): bid_rate = ci_to.format_amount(offer.rate) if form_data: - if b'archive_offer' in form_data: + if b"archive_offer" in form_data: try: swap_client.archiveOffer(offer_id) - messages.append('Offer archived') + messages.append("Offer archived") except Exception as ex: - err_messages.append('Archive offer failed: ' + str(ex)) - if b'revoke_offer' in form_data: + err_messages.append("Archive offer failed: " + str(ex)) + if b"revoke_offer" in form_data: try: swap_client.revokeOffer(offer_id) - messages.append('Offer revoked') + messages.append("Offer revoked") except Exception as ex: - err_messages.append('Revoke offer failed: ' + str(ex)) - elif b'repeat_offer' in form_data: + err_messages.append("Revoke offer failed: " + str(ex)) + elif b"repeat_offer" in form_data: # Can't set the post data here as browsers will always resend the original post data when responding to redirects self.send_response(302) - self.send_header('Location', '/newoffer?offer_from=' + offer_id.hex()) + self.send_header("Location", "/newoffer?offer_from=" + offer_id.hex()) self.end_headers() return bytes() - elif b'edit_offer' in form_data: + elif b"edit_offer" in form_data: show_edit_form = True - automation_filters = {'type_ind': Concepts.OFFER, 'sort_by': 'label'} - extend_data['automation_strategies'] = swap_client.listAutomationStrategies(automation_filters) - elif b'edit_offer_submit' in form_data: + automation_filters = {"type_ind": Concepts.OFFER, "sort_by": "label"} + extend_data["automation_strategies"] = swap_client.listAutomationStrategies( + automation_filters + ) + elif b"edit_offer_submit" in form_data: change_data = {} - change_data['automation_strat_id'] = int(get_data_entry_or(form_data, 'automation_strat_id', -1)) + change_data["automation_strat_id"] = int( + get_data_entry_or(form_data, "automation_strat_id", -1) + ) swap_client.editOffer(offer_id, change_data) - elif b'newbid' in form_data: + elif b"newbid" in form_data: show_bid_form = True - elif b'sendbid' in form_data: + elif b"sendbid" in form_data: try: - addr_from = form_data[b'addr_from'][0].decode('utf-8') - extend_data['nb_addr_from'] = addr_from - if addr_from == '-1': + addr_from = form_data[b"addr_from"][0].decode("utf-8") + extend_data["nb_addr_from"] = addr_from + if addr_from == "-1": addr_from = None - minutes_valid = int(form_data[b'validmins'][0].decode('utf-8')) - extend_data['nb_validmins'] = minutes_valid + minutes_valid = int(form_data[b"validmins"][0].decode("utf-8")) + extend_data["nb_validmins"] = minutes_valid extra_options = { - 'valid_for_seconds': minutes_valid * 60, + "valid_for_seconds": minutes_valid * 60, } - if have_data_entry(form_data, 'bid_rate'): - bid_rate = get_data_entry(form_data, 'bid_rate') - extra_options['bid_rate'] = ci_to.make_int(bid_rate, r=1) + if have_data_entry(form_data, "bid_rate"): + bid_rate = get_data_entry(form_data, "bid_rate") + extra_options["bid_rate"] = ci_to.make_int(bid_rate, r=1) - if have_data_entry(form_data, 'bid_amount'): - bid_amount = get_data_entry(form_data, 'bid_amount') + if have_data_entry(form_data, "bid_amount"): + bid_amount = get_data_entry(form_data, "bid_amount") amount_from = inputAmount(bid_amount, ci_from) else: amount_from = offer.amount_from - debugind = int(get_data_entry_or(form_data, 'debugind', -1)) + debugind = int(get_data_entry_or(form_data, "debugind", -1)) - sent_bid_id = swap_client.postBid(offer_id, amount_from, addr_send_from=addr_from, extra_options=extra_options).hex() + sent_bid_id = swap_client.postBid( + offer_id, + amount_from, + addr_send_from=addr_from, + extra_options=extra_options, + ).hex() if debugind > -1: swap_client.setBidDebugInd(bytes.fromhex(sent_bid_id), debugind) except Exception as ex: if self.server.swap_client.debug is True: self.server.swap_client.log.error(traceback.format_exc()) - err_messages.append('Send bid failed: ' + str(ex)) + err_messages.append("Send bid failed: " + str(ex)) show_bid_form = True amount_to: int = offer.amount_to @@ -584,78 +672,94 @@ def page_offer(self, url_split, post_string): amount_to = (offer.amount_from * offer.rate) // ci_from.COIN() now: int = swap_client.getTime() data = { - 'tla_from': ci_from.ticker(), - 'tla_to': ci_to.ticker(), - 'state': strOfferState(offer.state), - 'coin_from': ci_from.coin_name(), - 'coin_to': ci_to.coin_name(), - 'coin_from_ind': int(ci_from.coin_type()), - 'coin_to_ind': int(ci_to.coin_type()), - 'amt_from': ci_from.format_amount(offer.amount_from), - 'amt_to': ci_to.format_amount(amount_to), - 'amt_bid_min': ci_from.format_amount(offer.min_bid_amount), - 'rate': ci_to.format_amount(offer.rate), - 'lock_type': getLockName(offer.lock_type), - 'lock_value': offer.lock_value, - 'addr_from': offer.addr_from, - 'addr_to': 'Public' if offer.addr_to == swap_client.network_addr else offer.addr_to, - 'created_at': offer.created_at, - 'expired_at': offer.expire_at, - 'sent': offer.was_sent, - 'was_revoked': True if offer.active_ind == 2 else False, - 'show_bid_form': show_bid_form, - 'show_edit_form': show_edit_form, - 'amount_negotiable': offer.amount_negotiable, - 'rate_negotiable': offer.rate_negotiable, - 'bid_amount': bid_amount, - 'bid_rate': bid_rate, - 'debug_ui': swap_client.debug_ui, - 'automation_strat_id': -1, - 'is_expired': offer.expire_at <= now, - 'active_ind': offer.active_ind, - 'swap_type': strSwapDesc(offer.swap_type), - 'reverse': reverse_bid, + "tla_from": ci_from.ticker(), + "tla_to": ci_to.ticker(), + "state": strOfferState(offer.state), + "coin_from": ci_from.coin_name(), + "coin_to": ci_to.coin_name(), + "coin_from_ind": int(ci_from.coin_type()), + "coin_to_ind": int(ci_to.coin_type()), + "amt_from": ci_from.format_amount(offer.amount_from), + "amt_to": ci_to.format_amount(amount_to), + "amt_bid_min": ci_from.format_amount(offer.min_bid_amount), + "rate": ci_to.format_amount(offer.rate), + "lock_type": getLockName(offer.lock_type), + "lock_value": offer.lock_value, + "addr_from": offer.addr_from, + "addr_to": ( + "Public" if offer.addr_to == swap_client.network_addr else offer.addr_to + ), + "created_at": offer.created_at, + "expired_at": offer.expire_at, + "sent": offer.was_sent, + "was_revoked": True if offer.active_ind == 2 else False, + "show_bid_form": show_bid_form, + "show_edit_form": show_edit_form, + "amount_negotiable": offer.amount_negotiable, + "rate_negotiable": offer.rate_negotiable, + "bid_amount": bid_amount, + "bid_rate": bid_rate, + "debug_ui": swap_client.debug_ui, + "automation_strat_id": -1, + "is_expired": offer.expire_at <= now, + "active_ind": offer.active_ind, + "swap_type": strSwapDesc(offer.swap_type), + "reverse": reverse_bid, } data.update(extend_data) - if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME or offer.lock_type == TxLockTypes.ABS_LOCK_TIME: + if ( + offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME + or offer.lock_type == TxLockTypes.ABS_LOCK_TIME + ): if offer.lock_value > 60 * 60: - data['lock_value_hr'] = ' ({} hours)'.format(offer.lock_value / (60 * 60)) + data["lock_value_hr"] = " ({} hours)".format(offer.lock_value / (60 * 60)) else: - data['lock_value_hr'] = ' ({} minutes)'.format(offer.lock_value / 60) + data["lock_value_hr"] = " ({} minutes)".format(offer.lock_value / 60) - addr_from_label, addr_to_label = swap_client.getAddressLabel([offer.addr_from, offer.addr_to]) + addr_from_label, addr_to_label = swap_client.getAddressLabel( + [offer.addr_from, offer.addr_to] + ) if len(addr_from_label) > 0: - data['addr_from_label'] = '(' + addr_from_label + ')' + data["addr_from_label"] = "(" + addr_from_label + ")" if len(addr_to_label) > 0: - data['addr_to_label'] = '(' + addr_to_label + ')' + data["addr_to_label"] = "(" + addr_to_label + ")" if swap_client.debug_ui: - data['debug_ind'] = debugind - data['debug_options'] = [(int(t), t.name) for t in DebugTypes] + data["debug_ind"] = debugind + data["debug_options"] = [(int(t), t.name) for t in DebugTypes] ci_leader = ci_to if reverse_bid else ci_from - ci_follower = ci_from if reverse_bid else ci_to if xmr_offer: int_fee_rate_now, fee_source = ci_leader.get_fee_rate() - data['xmr_type'] = True - data['a_fee_rate'] = ci_from.format_amount(xmr_offer.a_fee_rate) - data['a_fee_rate_verify'] = ci_leader.format_amount(int_fee_rate_now, conv_int=True) - data['a_fee_rate_verify_src'] = fee_source - data['a_fee_warn'] = xmr_offer.a_fee_rate < int_fee_rate_now + data["xmr_type"] = True + data["a_fee_rate"] = ci_leader.format_amount(xmr_offer.a_fee_rate) + data["a_fee_rate_verify"] = ci_leader.format_amount( + int_fee_rate_now, conv_int=True + ) + data["a_fee_rate_verify_src"] = fee_source + data["a_fee_warn"] = xmr_offer.a_fee_rate < int_fee_rate_now from_fee_rate = xmr_offer.b_fee_rate if reverse_bid else xmr_offer.a_fee_rate - lock_spend_tx_vsize = ci_from.xmr_swap_b_lock_spend_tx_vsize() if reverse_bid else ci_from.xmr_swap_a_lock_spend_tx_vsize() - lock_spend_tx_fee = ci_from.make_int(from_fee_rate * lock_spend_tx_vsize / 1000, r=1) - data['amt_from_lock_spend_tx_fee'] = ci_from.format_amount(lock_spend_tx_fee // ci_from.COIN()) + lock_spend_tx_vsize = ( + ci_from.xmr_swap_b_lock_spend_tx_vsize() + if reverse_bid + else ci_from.xmr_swap_a_lock_spend_tx_vsize() + ) + lock_spend_tx_fee = ci_from.make_int( + from_fee_rate * lock_spend_tx_vsize / 1000, r=1 + ) + data["amt_from_lock_spend_tx_fee"] = ci_from.format_amount( + lock_spend_tx_fee // ci_from.COIN() + ) if offer.was_sent: try: strategy = swap_client.getLinkedStrategy(Concepts.OFFER, offer_id) - data['automation_strat_id'] = strategy[0] - data['automation_strat_label'] = strategy[1] + data["automation_strat_id"] = strategy[0] + data["automation_strat_label"] = strategy[1] except Exception: pass # None found @@ -666,20 +770,33 @@ def page_offer(self, url_split, post_string): amount_from = b[4] rate = b[10] amt_swapped += amount_from - formatted_bids.append((b[2].hex(), ci_from.format_amount(amount_from), strBidState(b[5]), ci_to.format_amount(rate), b[11])) - data['amt_swapped'] = ci_from.format_amount(amt_swapped) + formatted_bids.append( + ( + b[2].hex(), + ci_from.format_amount(amount_from), + strBidState(b[5]), + ci_to.format_amount(rate), + b[11], + ) + ) + data["amt_swapped"] = ci_from.format_amount(amt_swapped) - template = server.env.get_template('offer.html') - return self.render_template(template, { - 'offer_id': offer_id.hex(), - 'sent_bid_id': sent_bid_id, - 'messages': messages, - 'err_messages': err_messages, - 'data': data, - 'bids': formatted_bids, - 'addrs': None if show_bid_form is None else swap_client.listSMSGAddresses('bid'), - 'summary': summary, - }) + template = server.env.get_template("offer.html") + return self.render_template( + template, + { + "offer_id": offer_id.hex(), + "sent_bid_id": sent_bid_id, + "messages": messages, + "err_messages": err_messages, + "data": data, + "bids": formatted_bids, + "addrs": ( + None if show_bid_form is None else swap_client.listSMSGAddresses("bid") + ), + "summary": summary, + }, + ) def format_timestamp(timestamp, with_ago=True, is_expired=False): @@ -705,14 +822,20 @@ def format_timestamp(timestamp, with_ago=True, is_expired=False): if minutes_ago == 0: return "1h ago" if with_ago else "1h" else: - return f"1h {minutes_ago}min ago" if with_ago else f"1h {minutes_ago}min" + return ( + f"1h {minutes_ago}min ago" if with_ago else f"1h {minutes_ago}min" + ) else: if minutes_ago == 0: return f"{int(hours_ago)}h ago" if with_ago else f"{int(hours_ago)}h" else: - return f"{int(hours_ago)}h {minutes_ago}min ago" if with_ago else f"{int(hours_ago)}h {minutes_ago}min" + return ( + f"{int(hours_ago)}h {minutes_ago}min ago" + if with_ago + else f"{int(hours_ago)}h {minutes_ago}min" + ) else: - return time.strftime('%Y-%m-%d', time.localtime(timestamp)) + return time.strftime("%Y-%m-%d", time.localtime(timestamp)) def page_offers(self, url_split, post_string, sent=False): @@ -722,52 +845,56 @@ def page_offers(self, url_split, post_string, sent=False): summary = swap_client.getSummary() filters = { - 'coin_from': -1, - 'coin_to': -1, - 'page_no': 1, - 'limit': PAGE_LIMIT, - 'sort_by': 'created_at', - 'sort_dir': 'desc', - 'sent_from': 'any' if sent is False else 'only', - 'active': 'any', + "coin_from": -1, + "coin_to": -1, + "page_no": 1, + "limit": PAGE_LIMIT, + "sort_by": "created_at", + "sort_dir": "desc", + "sent_from": "any" if sent is False else "only", + "active": "any", } - filter_prefix = 'page_offers_sent' if sent else 'page_offers' + filter_prefix = "page_offers_sent" if sent else "page_offers" messages = [] - form_data = self.checkForm(post_string, 'offers', messages) + form_data = self.checkForm(post_string, "offers", messages) if form_data: - if have_data_entry(form_data, 'clearfilters'): + if have_data_entry(form_data, "clearfilters"): swap_client.clearFilters(filter_prefix) else: - filters['coin_from'] = setCoinFilter(form_data, 'coin_from') - filters['coin_to'] = setCoinFilter(form_data, 'coin_to') + filters["coin_from"] = setCoinFilter(form_data, "coin_from") + filters["coin_to"] = setCoinFilter(form_data, "coin_to") - if have_data_entry(form_data, 'sort_by'): - sort_by = get_data_entry(form_data, 'sort_by') - ensure(sort_by in ['created_at', 'rate'], 'Invalid sort by') - filters['sort_by'] = sort_by - if have_data_entry(form_data, 'sort_dir'): - sort_dir = get_data_entry(form_data, 'sort_dir') - ensure(sort_dir in ['asc', 'desc'], 'Invalid sort dir') - filters['sort_dir'] = sort_dir - if have_data_entry(form_data, 'sent_from'): - sent_from = get_data_entry(form_data, 'sent_from') - ensure(sent_from in ['any', 'only'], 'Invalid sent filter') - filters['sent_from'] = sent_from - if have_data_entry(form_data, 'active'): - active_filter = get_data_entry(form_data, 'active') - ensure(active_filter in ['any', 'active', 'expired', 'revoked', 'archived'], 'Invalid active filter') - filters['active'] = active_filter + if have_data_entry(form_data, "sort_by"): + sort_by = get_data_entry(form_data, "sort_by") + ensure(sort_by in ["created_at", "rate"], "Invalid sort by") + filters["sort_by"] = sort_by + if have_data_entry(form_data, "sort_dir"): + sort_dir = get_data_entry(form_data, "sort_dir") + ensure(sort_dir in ["asc", "desc"], "Invalid sort dir") + filters["sort_dir"] = sort_dir + if have_data_entry(form_data, "sent_from"): + sent_from = get_data_entry(form_data, "sent_from") + ensure(sent_from in ["any", "only"], "Invalid sent filter") + filters["sent_from"] = sent_from + if have_data_entry(form_data, "active"): + active_filter = get_data_entry(form_data, "active") + ensure( + active_filter + in ["any", "active", "expired", "revoked", "archived"], + "Invalid active filter", + ) + filters["active"] = active_filter set_pagination_filters(form_data, filters) - if have_data_entry(form_data, 'applyfilters'): + if have_data_entry(form_data, "applyfilters"): swap_client.setFilters(filter_prefix, filters) else: saved_filters = swap_client.getFilters(filter_prefix) if saved_filters: filters.update(saved_filters) - if filters['sent_from'] == 'only': + if filters["sent_from"] == "only": sent = True else: sent = False @@ -785,52 +912,64 @@ def page_offers(self, url_split, post_string, sent=False): is_expired = o.expire_at <= now amount_negotiable = "Yes" if o.amount_negotiable else "No" formatted_created_at = format_timestamp(o.created_at, with_ago=True) - formatted_expired_at = format_timestamp(o.expire_at, with_ago=False, is_expired=True) + formatted_expired_at = format_timestamp( + o.expire_at, with_ago=False, is_expired=True + ) tla_from = ci_from.ticker() tla_to = ci_to.ticker() amount_to: int = o.amount_to if amount_to is None: amount_to = (o.amount_from * o.rate) // ci_from.COIN() - formatted_offers.append(( - formatted_created_at, - o.offer_id.hex(), - ci_from.coin_name(), - ci_to.coin_name(), - ci_from.format_amount(o.amount_from), - ci_to.format_amount(amount_to), - ci_to.format_amount(o.rate), - 'Public' if o.addr_to == swap_client.network_addr else o.addr_to, - o.addr_from, - o.was_sent, - ci_from.format_amount(completed_amount), - is_expired, - o.active_ind, - formatted_expired_at, - strSwapDesc(o.swap_type), - amount_negotiable, - tla_from, - tla_to - )) + formatted_offers.append( + ( + formatted_created_at, + o.offer_id.hex(), + ci_from.coin_name(), + ci_to.coin_name(), + ci_from.format_amount(o.amount_from), + ci_to.format_amount(amount_to), + ci_to.format_amount(o.rate), + "Public" if o.addr_to == swap_client.network_addr else o.addr_to, + o.addr_from, + o.was_sent, + ci_from.format_amount(completed_amount), + is_expired, + o.active_ind, + formatted_expired_at, + strSwapDesc(o.swap_type), + amount_negotiable, + tla_from, + tla_to, + ) + ) coins_from, coins_to = listAvailableCoins(swap_client, split_from=True) - chart_api_key = swap_client.settings.get('chart_api_key', '') - if chart_api_key == '': - chart_api_key_enc = swap_client.settings.get('chart_api_key_enc', '') - chart_api_key = default_chart_api_key if chart_api_key_enc == '' else bytes.fromhex(chart_api_key_enc).decode('utf-8') + chart_api_key = swap_client.settings.get("chart_api_key", "") + if chart_api_key == "": + chart_api_key_enc = swap_client.settings.get("chart_api_key_enc", "") + chart_api_key = ( + default_chart_api_key + if chart_api_key_enc == "" + else bytes.fromhex(chart_api_key_enc).decode("utf-8") + ) - coingecko_api_key = swap_client.settings.get('coingecko_api_key', '') - if coingecko_api_key == '': - coingecko_api_key_enc = swap_client.settings.get('coingecko_api_key_enc', '') - coingecko_api_key = default_coingecko_api_key if coingecko_api_key_enc == '' else bytes.fromhex(coingecko_api_key_enc).decode('utf-8') + coingecko_api_key = swap_client.settings.get("coingecko_api_key", "") + if coingecko_api_key == "": + coingecko_api_key_enc = swap_client.settings.get("coingecko_api_key_enc", "") + coingecko_api_key = ( + default_coingecko_api_key + if coingecko_api_key_enc == "" + else bytes.fromhex(coingecko_api_key_enc).decode("utf-8") + ) offers_count = len(formatted_offers) enabled_chart_coins = [] - enabled_chart_coins_setting = swap_client.settings.get('enabled_chart_coins', '') - if enabled_chart_coins_setting.lower() == 'all': + enabled_chart_coins_setting = swap_client.settings.get("enabled_chart_coins", "") + if enabled_chart_coins_setting.lower() == "all": enabled_chart_coins = known_chart_coins - elif enabled_chart_coins_setting.strip() == '': + elif enabled_chart_coins_setting.strip() == "": for coin_id in swap_client.coin_clients: if not swap_client.isCoinActive(coin_id): continue @@ -838,33 +977,47 @@ def page_offers(self, url_split, post_string, sent=False): enabled_ticker = swap_client.ci(coin_id).ticker_mainnet() except Exception: continue - if enabled_ticker not in enabled_chart_coins and enabled_ticker in known_chart_coins: + if ( + enabled_ticker not in enabled_chart_coins + and enabled_ticker in known_chart_coins + ): enabled_chart_coins.append(enabled_ticker) else: - for ticker in enabled_chart_coins_setting.split(','): + for ticker in enabled_chart_coins_setting.split(","): upcased_ticker = ticker.strip().upper() - if upcased_ticker not in enabled_chart_coins and upcased_ticker in known_chart_coins: + if ( + upcased_ticker not in enabled_chart_coins + and upcased_ticker in known_chart_coins + ): enabled_chart_coins.append(upcased_ticker) - template = server.env.get_template('offers.html') - return self.render_template(template, { - 'page_type': 'Your Offers' if sent else 'Network Order Book', - 'page_button': 'hidden' if sent or offers_count <= 30 else '', - 'page_type_description': 'Your entire offer history.' if sent else 'Consult available offers in the order book and initiate a coin swap.', - 'messages': messages, - 'show_chart': False if sent else swap_client.settings.get('show_chart', True), - 'chart_api_key': chart_api_key, - 'coingecko_api_key': coingecko_api_key, - 'coins_from': coins_from, - 'coins': coins_to, - 'messages': messages, - 'filters': filters, - 'offers': formatted_offers, - 'summary': summary, - 'sent_offers': sent, - 'offers_count': offers_count, - 'tla_from': tla_from, - 'tla_to': tla_to, - 'enabled_chart_coins': enabled_chart_coins, - }) + template = server.env.get_template("offers.html") + return self.render_template( + template, + { + "page_type": "Your Offers" if sent else "Network Order Book", + "page_button": "hidden" if sent or offers_count <= 30 else "", + "page_type_description": ( + "Your entire offer history." + if sent + else "Consult available offers in the order book and initiate a coin swap." + ), + "show_chart": ( + False if sent else swap_client.settings.get("show_chart", True) + ), + "chart_api_key": chart_api_key, + "coingecko_api_key": coingecko_api_key, + "coins_from": coins_from, + "coins": coins_to, + "messages": messages, + "filters": filters, + "offers": formatted_offers, + "summary": summary, + "sent_offers": sent, + "offers_count": offers_count, + "tla_from": tla_from, + "tla_to": tla_to, + "enabled_chart_coins": enabled_chart_coins, + }, + ) diff --git a/basicswap/ui/page_settings.py b/basicswap/ui/page_settings.py index 50e538e..b8f5f74 100644 --- a/basicswap/ui/page_settings.py +++ b/basicswap/ui/page_settings.py @@ -28,140 +28,195 @@ def page_settings(self, url_split, post_string): messages = [] err_messages = [] - active_tab = 'default' - form_data = self.checkForm(post_string, 'settings', err_messages) + active_tab = "default" + form_data = self.checkForm(post_string, "settings", err_messages) if form_data: try: - if have_data_entry(form_data, 'apply_general'): - active_tab = 'general' + if have_data_entry(form_data, "apply_general"): + active_tab = "general" data = { - 'debug': toBool(get_data_entry(form_data, 'debugmode')), - 'debug_ui': toBool(get_data_entry(form_data, 'debugui')), - 'expire_db_records': toBool(get_data_entry(form_data, 'expire_db_records')), + "debug": toBool(get_data_entry(form_data, "debugmode")), + "debug_ui": toBool(get_data_entry(form_data, "debugui")), + "expire_db_records": toBool( + get_data_entry(form_data, "expire_db_records") + ), } swap_client.editGeneralSettings(data) - elif have_data_entry(form_data, 'apply_chart'): - active_tab = 'general' + elif have_data_entry(form_data, "apply_chart"): + active_tab = "general" data = { - 'show_chart': toBool(get_data_entry(form_data, 'showchart')), - 'chart_api_key': html.unescape(get_data_entry_or(form_data, 'chartapikey', '')), - 'coingecko_api_key': html.unescape(get_data_entry_or(form_data, 'coingeckoapikey', '')), - 'enabled_chart_coins': get_data_entry_or(form_data, 'enabledchartcoins', ''), + "show_chart": toBool(get_data_entry(form_data, "showchart")), + "chart_api_key": html.unescape( + get_data_entry_or(form_data, "chartapikey", "") + ), + "coingecko_api_key": html.unescape( + get_data_entry_or(form_data, "coingeckoapikey", "") + ), + "enabled_chart_coins": get_data_entry_or( + form_data, "enabledchartcoins", "" + ), } swap_client.editGeneralSettings(data) - elif have_data_entry(form_data, 'apply_tor'): - active_tab = 'tor' + elif have_data_entry(form_data, "apply_tor"): + active_tab = "tor" # TODO: Detect if running in docker - raise ValueError('TODO: If running in docker see doc/tor.md to enable/disable tor.') + raise ValueError( + "TODO: If running in docker see doc/tor.md to enable/disable tor." + ) - for name, c in swap_client.settings['chainclients'].items(): - if have_data_entry(form_data, 'apply_' + name): - data = {'lookups': get_data_entry(form_data, 'lookups_' + name)} - if name in ('monero', 'wownero'): - data['fee_priority'] = int(get_data_entry(form_data, 'fee_priority_' + name)) - data['manage_daemon'] = True if get_data_entry(form_data, 'managedaemon_' + name) == 'true' else False - data['rpchost'] = get_data_entry(form_data, 'rpchost_' + name) - data['rpcport'] = int(get_data_entry(form_data, 'rpcport_' + name)) - data['remotedaemonurls'] = get_data_entry_or(form_data, 'remotedaemonurls_' + name, '') - data['automatically_select_daemon'] = True if get_data_entry(form_data, 'autosetdaemon_' + name) == 'true' else False + for name, c in swap_client.settings["chainclients"].items(): + if have_data_entry(form_data, "apply_" + name): + data = {"lookups": get_data_entry(form_data, "lookups_" + name)} + if name in ("monero", "wownero"): + data["fee_priority"] = int( + get_data_entry(form_data, "fee_priority_" + name) + ) + data["manage_daemon"] = ( + True + if get_data_entry(form_data, "managedaemon_" + name) + == "true" + else False + ) + data["rpchost"] = get_data_entry(form_data, "rpchost_" + name) + data["rpcport"] = int( + get_data_entry(form_data, "rpcport_" + name) + ) + data["remotedaemonurls"] = get_data_entry_or( + form_data, "remotedaemonurls_" + name, "" + ) + data["automatically_select_daemon"] = ( + True + if get_data_entry(form_data, "autosetdaemon_" + name) + == "true" + else False + ) else: - data['conf_target'] = int(get_data_entry(form_data, 'conf_target_' + name)) - if name == 'particl': - data['anon_tx_ring_size'] = int(get_data_entry(form_data, 'rct_ring_size_' + name)) + data["conf_target"] = int( + get_data_entry(form_data, "conf_target_" + name) + ) + if name == "particl": + data["anon_tx_ring_size"] = int( + get_data_entry(form_data, "rct_ring_size_" + name) + ) - settings_changed, suggest_reboot = swap_client.editSettings(name, data) + settings_changed, suggest_reboot = swap_client.editSettings( + name, data + ) if settings_changed is True: - messages.append('Settings applied.') + messages.append("Settings applied.") if suggest_reboot is True: - messages.append('Please restart BasicSwap.') - elif have_data_entry(form_data, 'enable_' + name): + messages.append("Please restart BasicSwap.") + elif have_data_entry(form_data, "enable_" + name): swap_client.enableCoin(name) display_name = getCoinName(swap_client.getCoinIdFromName(name)) - messages.append(display_name + ' enabled, shutting down.') + messages.append(display_name + " enabled, shutting down.") swap_client.stopRunning() - elif have_data_entry(form_data, 'disable_' + name): + elif have_data_entry(form_data, "disable_" + name): swap_client.disableCoin(name) display_name = getCoinName(swap_client.getCoinIdFromName(name)) - messages.append(display_name + ' disabled, shutting down.') + messages.append(display_name + " disabled, shutting down.") swap_client.stopRunning() except InactiveCoin as ex: - err_messages.append('InactiveCoin {}'.format(Coins(ex.coinid).name)) + err_messages.append("InactiveCoin {}".format(Coins(ex.coinid).name)) except Exception as e: err_messages.append(str(e)) chains_formatted = [] - sorted_names = sorted(swap_client.settings['chainclients'].keys()) + sorted_names = sorted(swap_client.settings["chainclients"].keys()) for name in sorted_names: - c = swap_client.settings['chainclients'][name] + c = swap_client.settings["chainclients"][name] try: display_name = getCoinName(swap_client.getCoinIdFromName(name)) except Exception: display_name = name - chains_formatted.append({ - 'name': name, - 'display_name': display_name, - 'lookups': c.get('chain_lookups', 'local'), - 'manage_daemon': c.get('manage_daemon', 'Unknown'), - 'connection_type': c.get('connection_type', 'Unknown'), - }) - if name in ('monero', 'wownero'): - chains_formatted[-1]['fee_priority'] = c.get('fee_priority', 0) - chains_formatted[-1]['manage_wallet_daemon'] = c.get('manage_wallet_daemon', 'Unknown') - chains_formatted[-1]['rpchost'] = c.get('rpchost', 'localhost') - chains_formatted[-1]['rpcport'] = int(c.get('rpcport', 18081)) - chains_formatted[-1]['remotedaemonurls'] = '\n'.join(c.get('remote_daemon_urls', [])) - chains_formatted[-1]['autosetdaemon'] = c.get('automatically_select_daemon', False) + chains_formatted.append( + { + "name": name, + "display_name": display_name, + "lookups": c.get("chain_lookups", "local"), + "manage_daemon": c.get("manage_daemon", "Unknown"), + "connection_type": c.get("connection_type", "Unknown"), + } + ) + if name in ("monero", "wownero"): + chains_formatted[-1]["fee_priority"] = c.get("fee_priority", 0) + chains_formatted[-1]["manage_wallet_daemon"] = c.get( + "manage_wallet_daemon", "Unknown" + ) + chains_formatted[-1]["rpchost"] = c.get("rpchost", "localhost") + chains_formatted[-1]["rpcport"] = int(c.get("rpcport", 18081)) + chains_formatted[-1]["remotedaemonurls"] = "\n".join( + c.get("remote_daemon_urls", []) + ) + chains_formatted[-1]["autosetdaemon"] = c.get( + "automatically_select_daemon", False + ) else: - chains_formatted[-1]['conf_target'] = c.get('conf_target', 2) + chains_formatted[-1]["conf_target"] = c.get("conf_target", 2) - if name == 'particl': - chains_formatted[-1]['anon_tx_ring_size'] = c.get('anon_tx_ring_size', 12) + if name == "particl": + chains_formatted[-1]["anon_tx_ring_size"] = c.get("anon_tx_ring_size", 12) else: - if c.get('connection_type', 'Unknown') == 'none': - if 'connection_type_prev' in c: - chains_formatted[-1]['can_reenable'] = True + if c.get("connection_type", "Unknown") == "none": + if "connection_type_prev" in c: + chains_formatted[-1]["can_reenable"] = True else: - chains_formatted[-1]['can_disable'] = True + chains_formatted[-1]["can_disable"] = True general_settings = { - 'debug': swap_client.debug, - 'debug_ui': swap_client.debug_ui, - 'expire_db_records': swap_client._expire_db_records, + "debug": swap_client.debug, + "debug_ui": swap_client.debug_ui, + "expire_db_records": swap_client._expire_db_records, } - if 'chart_api_key_enc' in swap_client.settings: - chart_api_key = html.escape(bytes.fromhex(swap_client.settings.get('chart_api_key_enc', '')).decode('utf-8')) + if "chart_api_key_enc" in swap_client.settings: + chart_api_key = html.escape( + bytes.fromhex(swap_client.settings.get("chart_api_key_enc", "")).decode( + "utf-8" + ) + ) else: - chart_api_key = swap_client.settings.get('chart_api_key', '') + chart_api_key = swap_client.settings.get("chart_api_key", "") - if 'coingecko_api_key_enc' in swap_client.settings: - coingecko_api_key = html.escape(bytes.fromhex(swap_client.settings.get('coingecko_api_key_enc', '')).decode('utf-8')) + if "coingecko_api_key_enc" in swap_client.settings: + coingecko_api_key = html.escape( + bytes.fromhex(swap_client.settings.get("coingecko_api_key_enc", "")).decode( + "utf-8" + ) + ) else: - coingecko_api_key = swap_client.settings.get('coingecko_api_key', '') + coingecko_api_key = swap_client.settings.get("coingecko_api_key", "") chart_settings = { - 'show_chart': swap_client.settings.get('show_chart', True), - 'chart_api_key': chart_api_key, - 'coingecko_api_key': coingecko_api_key, - 'enabled_chart_coins': swap_client.settings.get('enabled_chart_coins', ''), + "show_chart": swap_client.settings.get("show_chart", True), + "chart_api_key": chart_api_key, + "coingecko_api_key": coingecko_api_key, + "enabled_chart_coins": swap_client.settings.get("enabled_chart_coins", ""), } - tor_control_password = '' if swap_client.tor_control_password is None else swap_client.tor_control_password + tor_control_password = ( + "" + if swap_client.tor_control_password is None + else swap_client.tor_control_password + ) tor_settings = { - 'use_tor': swap_client.use_tor_proxy, - 'proxy_host': swap_client.tor_proxy_host, - 'proxy_port': swap_client.tor_proxy_port, - 'control_password': html.escape(tor_control_password), - 'control_port': swap_client.tor_control_port, + "use_tor": swap_client.use_tor_proxy, + "proxy_host": swap_client.tor_proxy_host, + "proxy_port": swap_client.tor_proxy_port, + "control_password": html.escape(tor_control_password), + "control_port": swap_client.tor_control_port, } - template = server.env.get_template('settings.html') - return self.render_template(template, { - 'messages': messages, - 'err_messages': err_messages, - 'summary': swap_client.getSummary(), - 'chains': chains_formatted, - 'general_settings': general_settings, - 'chart_settings': chart_settings, - 'tor_settings': tor_settings, - 'active_tab': active_tab, - }) + template = server.env.get_template("settings.html") + return self.render_template( + template, + { + "messages": messages, + "err_messages": err_messages, + "summary": swap_client.getSummary(), + "chains": chains_formatted, + "general_settings": general_settings, + "chart_settings": chart_settings, + "tor_settings": tor_settings, + "active_tab": active_tab, + }, + ) diff --git a/basicswap/ui/page_smsgaddresses.py b/basicswap/ui/page_smsgaddresses.py index de8fa96..db1f50f 100644 --- a/basicswap/ui/page_smsgaddresses.py +++ b/basicswap/ui/page_smsgaddresses.py @@ -28,11 +28,11 @@ def page_smsgaddresses(self, url_split, post_string): summary = swap_client.getSummary() filters = { - 'page_no': 1, - 'limit': PAGE_LIMIT, - 'sort_by': 'created_at', - 'sort_dir': 'desc', - 'addr_type': -1, + "page_no": 1, + "limit": PAGE_LIMIT, + "sort_by": "created_at", + "sort_dir": "desc", + "addr_type": -1, } page_data = {} @@ -41,96 +41,114 @@ def page_smsgaddresses(self, url_split, post_string): smsgaddresses = [] listaddresses = True - form_data = self.checkForm(post_string, 'smsgaddresses', err_messages) + form_data = self.checkForm(post_string, "smsgaddresses", err_messages) if form_data: edit_address_id = None for key in form_data: - if key.startswith(b'editaddr_'): - edit_address_id = int(key.split(b'_')[1]) + if key.startswith(b"editaddr_"): + edit_address_id = int(key.split(b"_")[1]) break if edit_address_id is not None: listaddresses = False - page_data['edit_address'] = edit_address_id - page_data['addr_data'] = swap_client.listAllSMSGAddresses({'addr_id': edit_address_id})[0] - elif have_data_entry(form_data, 'saveaddr'): - edit_address_id = int(get_data_entry(form_data, 'edit_address_id')) - edit_addr = get_data_entry(form_data, 'edit_address') - active_ind = int(get_data_entry(form_data, 'active_ind')) - ensure(active_ind in (0, 1), 'Invalid sort by') - addressnote = get_data_entry_or(form_data, 'addressnote', '') - if not validateTextInput(addressnote, 'Address note', err_messages, max_length=30): + page_data["edit_address"] = edit_address_id + page_data["addr_data"] = swap_client.listAllSMSGAddresses( + {"addr_id": edit_address_id} + )[0] + elif have_data_entry(form_data, "saveaddr"): + edit_address_id = int(get_data_entry(form_data, "edit_address_id")) + edit_addr = get_data_entry(form_data, "edit_address") + active_ind = int(get_data_entry(form_data, "active_ind")) + ensure(active_ind in (0, 1), "Invalid sort by") + addressnote = get_data_entry_or(form_data, "addressnote", "") + if not validateTextInput( + addressnote, "Address note", err_messages, max_length=30 + ): listaddresses = False - page_data['edit_address'] = edit_address_id + page_data["edit_address"] = edit_address_id else: - swap_client.editSMSGAddress(edit_addr, active_ind=active_ind, addressnote=addressnote) - messages.append(f'Edited address {edit_addr}') - elif have_data_entry(form_data, 'shownewaddr'): + swap_client.editSMSGAddress( + edit_addr, active_ind=active_ind, addressnote=addressnote + ) + messages.append(f"Edited address {edit_addr}") + elif have_data_entry(form_data, "shownewaddr"): listaddresses = False - page_data['new_address'] = True - elif have_data_entry(form_data, 'showaddaddr'): + page_data["new_address"] = True + elif have_data_entry(form_data, "showaddaddr"): listaddresses = False - page_data['new_send_address'] = True - elif have_data_entry(form_data, 'createnewaddr'): - addressnote = get_data_entry_or(form_data, 'addressnote', '') - if not validateTextInput(addressnote, 'Address note', err_messages, max_length=30): + page_data["new_send_address"] = True + elif have_data_entry(form_data, "createnewaddr"): + addressnote = get_data_entry_or(form_data, "addressnote", "") + if not validateTextInput( + addressnote, "Address note", err_messages, max_length=30 + ): listaddresses = False - page_data['new_address'] = True + page_data["new_address"] = True else: new_addr, pubkey = swap_client.newSMSGAddress(addressnote=addressnote) - messages.append(f'Created address {new_addr}, pubkey {pubkey}') - elif have_data_entry(form_data, 'createnewsendaddr'): - pubkey_hex = get_data_entry(form_data, 'addresspubkey') - addressnote = get_data_entry_or(form_data, 'addressnote', '') - if not validateTextInput(addressnote, 'Address note', messages, max_length=30) or \ - not validateTextInput(pubkey_hex, 'Pubkey', messages, max_length=66): + messages.append(f"Created address {new_addr}, pubkey {pubkey}") + elif have_data_entry(form_data, "createnewsendaddr"): + pubkey_hex = get_data_entry(form_data, "addresspubkey") + addressnote = get_data_entry_or(form_data, "addressnote", "") + if not validateTextInput( + addressnote, "Address note", messages, max_length=30 + ) or not validateTextInput(pubkey_hex, "Pubkey", messages, max_length=66): listaddresses = False - page_data['new_send_address'] = True + page_data["new_send_address"] = True else: - new_addr = swap_client.addSMSGAddress(pubkey_hex, addressnote=addressnote) - messages.append(f'Added address {new_addr}') + new_addr = swap_client.addSMSGAddress( + pubkey_hex, addressnote=addressnote + ) + messages.append(f"Added address {new_addr}") - if have_data_entry(form_data, 'clearfilters'): - swap_client.clearFilters('page_smsgaddresses') + if have_data_entry(form_data, "clearfilters"): + swap_client.clearFilters("page_smsgaddresses") else: - if have_data_entry(form_data, 'sort_by'): - sort_by = get_data_entry(form_data, 'sort_by') - ensure(sort_by in ['created_at', 'rate'], 'Invalid sort by') - filters['sort_by'] = sort_by - if have_data_entry(form_data, 'sort_dir'): - sort_dir = get_data_entry(form_data, 'sort_dir') - ensure(sort_dir in ['asc', 'desc'], 'Invalid sort dir') - filters['sort_dir'] = sort_dir - if have_data_entry(form_data, 'filter_addressnote'): - addressnote = get_data_entry(form_data, 'filter_addressnote') - if validateTextInput(addressnote, 'Address note', err_messages, max_length=30): - filters['addressnote'] = addressnote - if have_data_entry(form_data, 'filter_addr_type'): - filters['addr_type'] = int(get_data_entry(form_data, 'filter_addr_type')) + if have_data_entry(form_data, "sort_by"): + sort_by = get_data_entry(form_data, "sort_by") + ensure(sort_by in ["created_at", "rate"], "Invalid sort by") + filters["sort_by"] = sort_by + if have_data_entry(form_data, "sort_dir"): + sort_dir = get_data_entry(form_data, "sort_dir") + ensure(sort_dir in ["asc", "desc"], "Invalid sort dir") + filters["sort_dir"] = sort_dir + if have_data_entry(form_data, "filter_addressnote"): + addressnote = get_data_entry(form_data, "filter_addressnote") + if validateTextInput( + addressnote, "Address note", err_messages, max_length=30 + ): + filters["addressnote"] = addressnote + if have_data_entry(form_data, "filter_addr_type"): + filters["addr_type"] = int( + get_data_entry(form_data, "filter_addr_type") + ) set_pagination_filters(form_data, filters) - if have_data_entry(form_data, 'applyfilters'): - swap_client.setFilters('page_smsgaddresses', filters) + if have_data_entry(form_data, "applyfilters"): + swap_client.setFilters("page_smsgaddresses", filters) else: - saved_filters = swap_client.getFilters('page_smsgaddresses') + saved_filters = swap_client.getFilters("page_smsgaddresses") if saved_filters: filters.update(saved_filters) if listaddresses is True: smsgaddresses = swap_client.listAllSMSGAddresses(filters) - page_data['addr_types'] = [(int(t), strAddressType(t)) for t in AddressTypes] - page_data['network_addr'] = swap_client.network_addr + page_data["addr_types"] = [(int(t), strAddressType(t)) for t in AddressTypes] + page_data["network_addr"] = swap_client.network_addr for addr in smsgaddresses: - addr['type'] = strAddressType(addr['type']) + addr["type"] = strAddressType(addr["type"]) - template = self.server.env.get_template('smsgaddresses.html') - return self.render_template(template, { - 'messages': messages, - 'err_messages': err_messages, - 'filters': filters, - 'data': page_data, - 'smsgaddresses': smsgaddresses, - 'page_data': page_data, - 'summary': summary, - }) + template = self.server.env.get_template("smsgaddresses.html") + return self.render_template( + template, + { + "messages": messages, + "err_messages": err_messages, + "filters": filters, + "data": page_data, + "smsgaddresses": smsgaddresses, + "page_data": page_data, + "summary": summary, + }, + ) diff --git a/basicswap/ui/page_tor.py b/basicswap/ui/page_tor.py index b4ea655..2afa17c 100644 --- a/basicswap/ui/page_tor.py +++ b/basicswap/ui/page_tor.py @@ -7,19 +7,19 @@ def extract_data(bytes_in): if bytes_in is None: return None - str_in = bytes_in.decode('utf-8') - start = str_in.find('=') + str_in = bytes_in.decode("utf-8") + start = str_in.find("=") if start < 0: return None start += 1 - end = str_in.find('\r', start) + end = str_in.find("\r", start) if end < 0: return None - return str_in[start: end] + return str_in[start:end] def get_tor_established_state(swap_client): - rv = swap_client.torControl('GETINFO status/circuit-established') + rv = swap_client.torControl("GETINFO status/circuit-established") return extract_data(rv) @@ -28,23 +28,26 @@ def page_tor(self, url_split, post_string): summary = swap_client.getSummary() page_data = {} try: - page_data['circuit_established'] = get_tor_established_state(swap_client) + page_data["circuit_established"] = get_tor_established_state(swap_client) except Exception: - page_data['circuit_established'] = 'error' + page_data["circuit_established"] = "error" try: - rv = swap_client.torControl('GETINFO traffic/read') - page_data['bytes_written'] = extract_data(rv) + rv = swap_client.torControl("GETINFO traffic/read") + page_data["bytes_written"] = extract_data(rv) except Exception: - page_data['bytes_written'] = 'error' + page_data["bytes_written"] = "error" try: - rv = swap_client.torControl('GETINFO traffic/written') - page_data['bytes_read'] = extract_data(rv) + rv = swap_client.torControl("GETINFO traffic/written") + page_data["bytes_read"] = extract_data(rv) except Exception: - page_data['bytes_read'] = 'error' + page_data["bytes_read"] = "error" messages = [] - template = self.server.env.get_template('tor.html') - return self.render_template(template, { - 'messages': messages, - 'data': page_data, - 'summary': summary, - }) + template = self.server.env.get_template("tor.html") + return self.render_template( + template, + { + "messages": messages, + "data": page_data, + "summary": summary, + }, + ) diff --git a/basicswap/ui/page_wallet.py b/basicswap/ui/page_wallet.py index 0e65513..e7e1c02 100644 --- a/basicswap/ui/page_wallet.py +++ b/basicswap/ui/page_wallet.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2022-2023 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -23,52 +24,52 @@ from basicswap.chainparams import ( def format_wallet_data(swap_client, ci, w): wf = { - 'name': ci.coin_name(), - 'version': w.get('version', '?'), - 'ticker': ci.ticker_mainnet(), - 'cid': str(int(ci.coin_type())), - 'balance': w.get('balance', '?'), - 'blocks': w.get('blocks', '?'), - 'synced': w.get('synced', '?'), - 'expected_seed': w.get('expected_seed', '?'), - 'encrypted': w.get('encrypted', '?'), - 'locked': w.get('locked', '?'), - 'updating': w.get('updating', '?'), - 'havedata': True, + "name": ci.coin_name(), + "version": w.get("version", "?"), + "ticker": ci.ticker_mainnet(), + "cid": str(int(ci.coin_type())), + "balance": w.get("balance", "?"), + "blocks": w.get("blocks", "?"), + "synced": w.get("synced", "?"), + "expected_seed": w.get("expected_seed", "?"), + "encrypted": w.get("encrypted", "?"), + "locked": w.get("locked", "?"), + "updating": w.get("updating", "?"), + "havedata": True, } - if w.get('bootstrapping', False) is True: - wf['bootstrapping'] = True - if 'known_block_count' in w: - wf['known_block_count'] = w['known_block_count'] - if 'locked_utxos' in w: - wf['locked_utxos'] = w['locked_utxos'] + if w.get("bootstrapping", False) is True: + wf["bootstrapping"] = True + if "known_block_count" in w: + wf["known_block_count"] = w["known_block_count"] + if "locked_utxos" in w: + wf["locked_utxos"] = w["locked_utxos"] - if 'balance' in w and 'unconfirmed' in w: - wf['balance_all'] = float(w['balance']) + float(w['unconfirmed']) - if 'lastupdated' in w: - wf['lastupdated'] = format_timestamp(w['lastupdated']) + if "balance" in w and "unconfirmed" in w: + wf["balance_all"] = float(w["balance"]) + float(w["unconfirmed"]) + if "lastupdated" in w: + wf["lastupdated"] = format_timestamp(w["lastupdated"]) pending: int = 0 - if 'unconfirmed' in w and float(w['unconfirmed']) > 0.0: - pending += ci.make_int(w['unconfirmed']) - if 'immature' in w and float(w['immature']) > 0.0: - pending += ci.make_int(w['immature']) + if "unconfirmed" in w and float(w["unconfirmed"]) > 0.0: + pending += ci.make_int(w["unconfirmed"]) + if "immature" in w and float(w["immature"]) > 0.0: + pending += ci.make_int(w["immature"]) if pending > 0.0: - wf['pending'] = ci.format_amount(pending) + wf["pending"] = ci.format_amount(pending) if ci.coin_type() == Coins.PART: - wf['stealth_address'] = w.get('stealth_address', '?') - wf['blind_balance'] = w.get('blind_balance', '?') - if 'blind_unconfirmed' in w and float(w['blind_unconfirmed']) > 0.0: - wf['blind_unconfirmed'] = w['blind_unconfirmed'] - wf['anon_balance'] = w.get('anon_balance', '?') - if 'anon_pending' in w and float(w['anon_pending']) > 0.0: - wf['anon_pending'] = w['anon_pending'] + wf["stealth_address"] = w.get("stealth_address", "?") + wf["blind_balance"] = w.get("blind_balance", "?") + if "blind_unconfirmed" in w and float(w["blind_unconfirmed"]) > 0.0: + wf["blind_unconfirmed"] = w["blind_unconfirmed"] + wf["anon_balance"] = w.get("anon_balance", "?") + if "anon_pending" in w and float(w["anon_pending"]) > 0.0: + wf["anon_pending"] = w["anon_pending"] elif ci.coin_type() == Coins.LTC: - wf['mweb_address'] = w.get('mweb_address', '?') - wf['mweb_balance'] = w.get('mweb_balance', '?') - wf['mweb_pending'] = w.get('mweb_pending', '?') + wf["mweb_address"] = w.get("mweb_address", "?") + wf["mweb_balance"] = w.get("mweb_balance", "?") + wf["mweb_pending"] = w.get("mweb_pending", "?") checkAddressesOwned(swap_client, ci, wf) return wf @@ -91,19 +92,18 @@ def page_wallets(self, url_split, post_string): for k in sk: w = wallets[k] - if 'error' in w: - wallets_formatted.append({ - 'cid': str(int(k)), - 'error': w['error'] - }) + if "error" in w: + wallets_formatted.append({"cid": str(int(k)), "error": w["error"]}) continue - if 'no_data' in w: - wallets_formatted.append({ - 'name': w['name'], - 'havedata': False, - 'updating': w['updating'], - }) + if "no_data" in w: + wallets_formatted.append( + { + "name": w["name"], + "havedata": False, + "updating": w["updating"], + } + ) continue ci = swap_client.ci(k) @@ -111,17 +111,20 @@ def page_wallets(self, url_split, post_string): wallets_formatted.append(wf) - template = server.env.get_template('wallets.html') - return self.render_template(template, { - 'messages': messages, - 'err_messages': err_messages, - 'wallets': wallets_formatted, - 'summary': summary, - }) + template = server.env.get_template("wallets.html") + return self.render_template( + template, + { + "messages": messages, + "err_messages": err_messages, + "wallets": wallets_formatted, + "summary": summary, + }, + ) def page_wallet(self, url_split, post_string): - ensure(len(url_split) > 2, 'Wallet not specified') + ensure(len(url_split) > 2, "Wallet not specified") wallet_ticker = url_split[2] server = self.server swap_client = server.swap_client @@ -136,94 +139,130 @@ def page_wallet(self, url_split, post_string): show_utxo_groups: bool = False withdrawal_successful: bool = False force_refresh: bool = False - form_data = self.checkForm(post_string, 'wallet', err_messages) + form_data = self.checkForm(post_string, "wallet", err_messages) if form_data: cid = str(int(coin_id)) - estimate_fee: bool = have_data_entry(form_data, 'estfee_' + cid) - withdraw: bool = have_data_entry(form_data, 'withdraw_' + cid) - if have_data_entry(form_data, 'newaddr_' + cid): + estimate_fee: bool = have_data_entry(form_data, "estfee_" + cid) + withdraw: bool = have_data_entry(form_data, "withdraw_" + cid) + if have_data_entry(form_data, "newaddr_" + cid): swap_client.cacheNewAddressForCoin(coin_id) - elif have_data_entry(form_data, 'forcerefresh'): + elif have_data_entry(form_data, "forcerefresh"): force_refresh = True - elif have_data_entry(form_data, 'newmwebaddr_' + cid): + elif have_data_entry(form_data, "newmwebaddr_" + cid): swap_client.cacheNewStealthAddressForCoin(coin_id) - elif have_data_entry(form_data, 'reseed_' + cid): + elif have_data_entry(form_data, "reseed_" + cid): try: swap_client.reseedWallet(coin_id) - messages.append('Reseed complete ' + str(coin_id)) + messages.append("Reseed complete " + str(coin_id)) except Exception as ex: - err_messages.append('Reseed failed ' + str(ex)) + err_messages.append("Reseed failed " + str(ex)) swap_client.updateWalletsInfo(True, coin_id) elif withdraw or estimate_fee: - subfee = True if have_data_entry(form_data, 'subfee_' + cid) else False - page_data['wd_subfee_' + cid] = subfee + subfee = True if have_data_entry(form_data, "subfee_" + cid) else False + page_data["wd_subfee_" + cid] = subfee - sweepall = True if have_data_entry(form_data, 'sweepall_' + cid) else False - page_data['wd_sweepall_' + cid] = sweepall + sweepall = True if have_data_entry(form_data, "sweepall_" + cid) else False + page_data["wd_sweepall_" + cid] = sweepall value = None if not sweepall: try: - value = form_data[bytes('amt_' + cid, 'utf-8')][0].decode('utf-8') - page_data['wd_value_' + cid] = value - except Exception as e: - err_messages.append('Missing value') + value = form_data[bytes("amt_" + cid, "utf-8")][0].decode("utf-8") + page_data["wd_value_" + cid] = value + except Exception as e: # noqa: F841 + err_messages.append("Missing value") try: - address = form_data[bytes('to_' + cid, 'utf-8')][0].decode('utf-8') - page_data['wd_address_' + cid] = address - except Exception as e: - err_messages.append('Missing address') + address = form_data[bytes("to_" + cid, "utf-8")][0].decode("utf-8") + page_data["wd_address_" + cid] = address + except Exception as e: # noqa: F841 + err_messages.append("Missing address") if estimate_fee and withdraw: - err_messages.append('Estimate fee and withdraw can\'t be used together.') + err_messages.append("Estimate fee and withdraw can't be used together.") if estimate_fee and coin_id not in (Coins.XMR, Coins.WOW): ci = swap_client.ci(coin_id) ticker: str = ci.ticker() - err_messages.append(f'Estimate fee unavailable for {ticker}.') + err_messages.append(f"Estimate fee unavailable for {ticker}.") if coin_id == Coins.PART: try: - type_from = form_data[bytes('withdraw_type_from_' + cid, 'utf-8')][0].decode('utf-8') - type_to = form_data[bytes('withdraw_type_to_' + cid, 'utf-8')][0].decode('utf-8') - page_data['wd_type_from_' + cid] = type_from - page_data['wd_type_to_' + cid] = type_to - except Exception as e: - err_messages.append('Missing type') + type_from = form_data[bytes("withdraw_type_from_" + cid, "utf-8")][ + 0 + ].decode("utf-8") + type_to = form_data[bytes("withdraw_type_to_" + cid, "utf-8")][ + 0 + ].decode("utf-8") + page_data["wd_type_from_" + cid] = type_from + page_data["wd_type_to_" + cid] = type_to + except Exception as e: # noqa: F841 + err_messages.append("Missing type") elif coin_id == Coins.LTC: try: - type_from = form_data[bytes('withdraw_type_from_' + cid, 'utf-8')][0].decode('utf-8') - page_data['wd_type_from_' + cid] = type_from - except Exception as e: - err_messages.append('Missing type') + type_from = form_data[bytes("withdraw_type_from_" + cid, "utf-8")][ + 0 + ].decode("utf-8") + page_data["wd_type_from_" + cid] = type_from + except Exception as e: # noqa: F841 + err_messages.append("Missing type") if len(err_messages) == 0: ci = swap_client.ci(coin_id) ticker: str = ci.ticker() try: if coin_id == Coins.PART: - txid = swap_client.withdrawParticl(type_from, type_to, value, address, subfee) - messages.append('Withdrew {} {} ({} to {}) to address {}
In txid: {}'.format(value, ticker, type_from, type_to, address, txid)) + txid = swap_client.withdrawParticl( + type_from, type_to, value, address, subfee + ) + messages.append( + "Withdrew {} {} ({} to {}) to address {}
In txid: {}".format( + value, ticker, type_from, type_to, address, txid + ) + ) elif coin_id == Coins.LTC: - txid = swap_client.withdrawLTC(type_from, value, address, subfee) - messages.append('Withdrew {} {} (from {}) to address {}
In txid: {}'.format(value, ticker, type_from, address, txid)) + txid = swap_client.withdrawLTC( + type_from, value, address, subfee + ) + messages.append( + "Withdrew {} {} (from {}) to address {}
In txid: {}".format( + value, ticker, type_from, address, txid + ) + ) elif coin_id in (Coins.XMR, Coins.WOW): if estimate_fee: fee_estimate = ci.estimateFee(value, address, sweepall) - suffix = 's' if fee_estimate['num_txns'] > 1 else '' - sum_fees = ci.format_amount(fee_estimate['sum_fee']) - value_str = ci.format_amount(fee_estimate['sum_amount']) - messages.append(f'Estimated fee for {value_str} {ticker} to address {address}: {sum_fees} in {fee_estimate["num_txns"]} transaction{suffix}.') - page_data['fee_estimate'] = fee_estimate + suffix = "s" if fee_estimate["num_txns"] > 1 else "" + sum_fees = ci.format_amount(fee_estimate["sum_fee"]) + value_str = ci.format_amount(fee_estimate["sum_amount"]) + messages.append( + f'Estimated fee for {value_str} {ticker} to address {address}: {sum_fees} in {fee_estimate["num_txns"]} transaction{suffix}.' + ) + page_data["fee_estimate"] = fee_estimate else: - txid = swap_client.withdrawCoin(coin_id, value, address, sweepall) + txid = swap_client.withdrawCoin( + coin_id, value, address, sweepall + ) if sweepall: - messages.append('Swept all {} to address {}
In txid: {}'.format(ticker, address, txid)) + messages.append( + "Swept all {} to address {}
In txid: {}".format( + ticker, address, txid + ) + ) else: - messages.append('Withdrew {} {} to address {}
In txid: {}'.format(value, ticker, address, txid)) - messages.append('Note: The wallet balance can take a while to update.') + messages.append( + "Withdrew {} {} to address {}
In txid: {}".format( + value, ticker, address, txid + ) + ) + messages.append( + "Note: The wallet balance can take a while to update." + ) else: txid = swap_client.withdrawCoin(coin_id, value, address, subfee) - messages.append('Withdrew {} {} to address {}
In txid: {}'.format(value, ticker, address, txid)) + messages.append( + "Withdrew {} {} to address {}
In txid: {}".format( + value, ticker, address, txid + ) + ) if not estimate_fee: withdrawal_successful = True except Exception as e: @@ -232,41 +271,44 @@ def page_wallet(self, url_split, post_string): err_messages.append(str(e)) if not estimate_fee: swap_client.updateWalletsInfo(True, only_coin=coin_id) - elif have_data_entry(form_data, 'showutxogroups'): + elif have_data_entry(form_data, "showutxogroups"): show_utxo_groups = True - elif have_data_entry(form_data, 'create_utxo'): + elif have_data_entry(form_data, "create_utxo"): show_utxo_groups = True try: - value = get_data_entry(form_data, 'utxo_value') - page_data['utxo_value'] = value + value = get_data_entry(form_data, "utxo_value") + page_data["utxo_value"] = value ci = swap_client.ci(coin_id) value_sats = ci.make_int(value) txid, address = ci.createUTXO(value_sats) - messages.append('Created new utxo of value {} and address {}
In txid: {}'.format(value, address, txid)) + messages.append( + "Created new utxo of value {} and address {}
In txid: {}".format( + value, address, txid + ) + ) except Exception as e: err_messages.append(str(e)) if swap_client.debug is True: swap_client.log.error(traceback.format_exc()) - swap_client.updateWalletsInfo(force_refresh, only_coin=coin_id, wait_for_complete=True) - wallets = swap_client.getCachedWalletsInfo({'coin_id': coin_id}) + swap_client.updateWalletsInfo( + force_refresh, only_coin=coin_id, wait_for_complete=True + ) + wallets = swap_client.getCachedWalletsInfo({"coin_id": coin_id}) wallet_data = {} for k in wallets.keys(): w = wallets[k] - if 'error' in w: - wallet_data = { - 'cid': str(int(k)), - 'error': w['error'] - } + if "error" in w: + wallet_data = {"cid": str(int(k)), "error": w["error"]} continue - if 'no_data' in w: + if "no_data" in w: wallet_data = { - 'name': w['name'], - 'havedata': False, - 'updating': w['updating'], + "name": w["name"], + "havedata": False, + "updating": w["updating"], } continue @@ -277,55 +319,68 @@ def page_wallet(self, url_split, post_string): fee_rate, fee_src = swap_client.getFeeRateForCoin(k) est_fee = swap_client.estimateWithdrawFee(k, fee_rate) - wallet_data['fee_rate'] = ci.format_amount(int(fee_rate * ci.COIN())) - wallet_data['fee_rate_src'] = fee_src - wallet_data['est_fee'] = 'Unknown' if est_fee is None else ci.format_amount(int(est_fee * ci.COIN())) - wallet_data['deposit_address'] = w.get('deposit_address', 'Refresh necessary') + wallet_data["fee_rate"] = ci.format_amount(int(fee_rate * ci.COIN())) + wallet_data["fee_rate_src"] = fee_src + wallet_data["est_fee"] = ( + "Unknown" if est_fee is None else ci.format_amount(int(est_fee * ci.COIN())) + ) + wallet_data["deposit_address"] = w.get("deposit_address", "Refresh necessary") if k in (Coins.XMR, Coins.WOW): - wallet_data['main_address'] = w.get('main_address', 'Refresh necessary') + wallet_data["main_address"] = w.get("main_address", "Refresh necessary") elif k == Coins.LTC: - wallet_data['mweb_address'] = w.get('mweb_address', 'Refresh necessary') + wallet_data["mweb_address"] = w.get("mweb_address", "Refresh necessary") - if 'wd_type_from_' + cid in page_data: - wallet_data['wd_type_from'] = page_data['wd_type_from_' + cid] - if 'wd_type_to_' + cid in page_data: - wallet_data['wd_type_to'] = page_data['wd_type_to_' + cid] + if "wd_type_from_" + cid in page_data: + wallet_data["wd_type_from"] = page_data["wd_type_from_" + cid] + if "wd_type_to_" + cid in page_data: + wallet_data["wd_type_to"] = page_data["wd_type_to_" + cid] - if 'utxo_value' in page_data: - wallet_data['utxo_value'] = page_data['utxo_value'] + if "utxo_value" in page_data: + wallet_data["utxo_value"] = page_data["utxo_value"] if not withdrawal_successful: - if 'wd_value_' + cid in page_data: - wallet_data['wd_value'] = page_data['wd_value_' + cid] - if 'wd_address_' + cid in page_data: - wallet_data['wd_address'] = page_data['wd_address_' + cid] - if 'wd_subfee_' + cid in page_data: - wallet_data['wd_subfee'] = page_data['wd_subfee_' + cid] - if 'wd_sweepall_' + cid in page_data: - wallet_data['wd_sweepall'] = page_data['wd_sweepall_' + cid] - if 'fee_estimate' in page_data: - wallet_data['est_fee'] = ci.format_amount(page_data['fee_estimate']['sum_fee']) - wallet_data['fee_rate'] = ci.format_amount(page_data['fee_estimate']['sum_fee'] * 1000 // page_data['fee_estimate']['sum_weight']) + if "wd_value_" + cid in page_data: + wallet_data["wd_value"] = page_data["wd_value_" + cid] + if "wd_address_" + cid in page_data: + wallet_data["wd_address"] = page_data["wd_address_" + cid] + if "wd_subfee_" + cid in page_data: + wallet_data["wd_subfee"] = page_data["wd_subfee_" + cid] + if "wd_sweepall_" + cid in page_data: + wallet_data["wd_sweepall"] = page_data["wd_sweepall_" + cid] + if "fee_estimate" in page_data: + wallet_data["est_fee"] = ci.format_amount( + page_data["fee_estimate"]["sum_fee"] + ) + wallet_data["fee_rate"] = ci.format_amount( + page_data["fee_estimate"]["sum_fee"] + * 1000 + // page_data["fee_estimate"]["sum_weight"] + ) if show_utxo_groups: - utxo_groups = '' + utxo_groups = "" unspent_by_addr = ci.getUnspentsByAddr() - sorted_unspent_by_addr = sorted(unspent_by_addr.items(), key=lambda x: x[1], reverse=True) + sorted_unspent_by_addr = sorted( + unspent_by_addr.items(), key=lambda x: x[1], reverse=True + ) for kv in sorted_unspent_by_addr: - utxo_groups += kv[0] + ' ' + ci.format_amount(kv[1]) + '\n' + utxo_groups += kv[0] + " " + ci.format_amount(kv[1]) + "\n" - wallet_data['show_utxo_groups'] = True - wallet_data['utxo_groups'] = utxo_groups + wallet_data["show_utxo_groups"] = True + wallet_data["utxo_groups"] = utxo_groups checkAddressesOwned(swap_client, ci, wallet_data) - template = server.env.get_template('wallet.html') - return self.render_template(template, { - 'messages': messages, - 'err_messages': err_messages, - 'w': wallet_data, - 'summary': summary, - 'block_unknown_seeds': swap_client._restrict_unknown_seed_wallets, - }) + template = server.env.get_template("wallet.html") + return self.render_template( + template, + { + "messages": messages, + "err_messages": err_messages, + "w": wallet_data, + "summary": summary, + "block_unknown_seeds": swap_client._restrict_unknown_seed_wallets, + }, + ) diff --git a/basicswap/ui/util.py b/basicswap/ui/util.py index adc09ad..efb9285 100644 --- a/basicswap/ui/util.py +++ b/basicswap/ui/util.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020-2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -33,7 +34,21 @@ from basicswap.protocols.xmr_swap_1 import getChainBSplitKey, getChainBRemoteSpl PAGE_LIMIT = 25 invalid_coins_from = [] -known_chart_coins = ['BTC', 'PART', 'XMR', 'LTC', 'FIRO', 'DASH', 'PIVX', 'DOGE', 'ETH', 'DCR', 'ZANO', 'WOW', 'BCH'] +known_chart_coins = [ + "BTC", + "PART", + "XMR", + "LTC", + "FIRO", + "DASH", + "PIVX", + "DOGE", + "ETH", + "DCR", + "ZANO", + "WOW", + "BCH", +] def tickerToCoinId(ticker): @@ -41,7 +56,7 @@ def tickerToCoinId(ticker): for c in Coins: if c.name == search_str: return c.value - raise ValueError('Unknown coin') + raise ValueError("Unknown coin") def getCoinType(coin_type_ind): @@ -55,9 +70,9 @@ def getCoinType(coin_type_ind): def validateAmountString(amount, ci): if not isinstance(amount, str): return - ar = amount.split('.') + ar = amount.split(".") if len(ar) > 1 and len(ar[1]) > ci.exp(): - raise ValueError('Too many decimal places in amount {}'.format(amount)) + raise ValueError("Too many decimal places in amount {}".format(amount)) def inputAmount(amount_str, ci): @@ -66,24 +81,24 @@ def inputAmount(amount_str, ci): def get_data_entry_or(post_data, name, default): - if 'is_json' in post_data: + if "is_json" in post_data: return post_data.get(name, default) - key_bytes = name.encode('utf-8') + key_bytes = name.encode("utf-8") if key_bytes in post_data: - return post_data[key_bytes][0].decode('utf-8') + return post_data[key_bytes][0].decode("utf-8") return default def get_data_entry(post_data, name): - if 'is_json' in post_data: + if "is_json" in post_data: return post_data[name] - return post_data[name.encode('utf-8')][0].decode('utf-8') + return post_data[name.encode("utf-8")][0].decode("utf-8") def have_data_entry(post_data, name): - if 'is_json' in post_data: + if "is_json" in post_data: return name in post_data - return name.encode('utf-8') in post_data + return name.encode("utf-8") in post_data def setCoinFilter(form_data, field_name): @@ -96,18 +111,18 @@ def setCoinFilter(form_data, field_name): try: return Coins(coin_type) except Exception: - raise ValueError('Unknown Coin Type {}'.format(str(field_name))) + raise ValueError("Unknown Coin Type {}".format(str(field_name))) def set_pagination_filters(form_data, filters): - if form_data and have_data_entry(form_data, 'pageback'): - filters['page_no'] = int(form_data[b'pageno'][0]) - 1 - if filters['page_no'] < 1: - filters['page_no'] = 1 - elif form_data and have_data_entry(form_data, 'pageforwards'): - filters['page_no'] = int(form_data[b'pageno'][0]) + 1 - if filters['page_no'] > 1: - filters['offset'] = (filters['page_no'] - 1) * PAGE_LIMIT + if form_data and have_data_entry(form_data, "pageback"): + filters["page_no"] = int(form_data[b"pageno"][0]) - 1 + if filters["page_no"] < 1: + filters["page_no"] = 1 + elif form_data and have_data_entry(form_data, "pageforwards"): + filters["page_no"] = int(form_data[b"pageno"][0]) + 1 + if filters["page_no"] > 1: + filters["offset"] = (filters["page_no"] - 1) * PAGE_LIMIT def getTxIdHex(bid, tx_type, suffix): @@ -116,12 +131,12 @@ def getTxIdHex(bid, tx_type, suffix): elif tx_type == TxTypes.PTX: obj = bid.participate_tx else: - return 'Unknown Type' + return "Unknown Type" if not obj: - return 'None' + return "None" if not obj.txid: - return 'None' + return "None" return obj.txid.hex() + suffix @@ -131,13 +146,13 @@ def getTxSpendHex(bid, tx_type): elif tx_type == TxTypes.PTX: obj = bid.participate_tx else: - return 'Unknown Type' + return "Unknown Type" if not obj: - return 'None' + return "None" if not obj.spend_txid: - return 'None' - return obj.spend_txid.hex() + ' {}'.format(obj.spend_n) + return "None" + return obj.spend_txid.hex() + " {}".format(obj.spend_n) def listBidStates(): @@ -154,7 +169,19 @@ def listBidActions(): return rv -def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_bid, show_txns, view_tx_ind=None, for_api=False, show_lock_transfers=False): +def describeBid( + swap_client, + bid, + xmr_swap, + offer, + xmr_offer, + bid_events, + edit_bid, + show_txns, + view_tx_ind=None, + for_api=False, + show_lock_transfers=False, +): ci_from = swap_client.ci(Coins(offer.coin_from)) ci_to = swap_client.ci(Coins(offer.coin_to)) @@ -166,240 +193,404 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b bid_amount_to: int = bid.amount_to bid_rate: int = offer.rate if bid.rate is None else bid.rate - initiator_role: str = 'offerer' # Leader - participant_role: str = 'bidder' # Follower + initiator_role: str = "offerer" # Leader + participant_role: str = "bidder" # Follower if reverse_bid: bid_amount = bid.amount_to bid_amount_to = bid.amount bid_rate = ci_from.make_int(bid.amount / bid.amount_to, r=1) - initiator_role = 'bidder' - participant_role = 'offerer' + initiator_role = "bidder" + participant_role = "offerer" - state_description = '' + state_description = "" if offer.swap_type == SwapTypes.SELLER_FIRST: if bid.state == BidStates.BID_SENT: - state_description = 'Waiting for seller to accept.' + state_description = "Waiting for seller to accept." elif bid.state == BidStates.BID_RECEIVED: - state_description = 'Waiting for seller to accept.' + state_description = "Waiting for seller to accept." elif bid.state == BidStates.BID_ACCEPTED: if not bid.initiate_tx: - state_description = 'Waiting for seller to send initiate tx.' + state_description = "Waiting for seller to send initiate tx." else: - state_description = 'Waiting for initiate tx to confirm.' + state_description = "Waiting for initiate tx to confirm." elif bid.state == BidStates.SWAP_INITIATED: - state_description = 'Waiting for participate txn to be confirmed in {} chain'.format(ci_follower.ticker()) + state_description = ( + "Waiting for participate txn to be confirmed in {} chain".format( + ci_follower.ticker() + ) + ) elif bid.state == BidStates.SWAP_PARTICIPATING: if bid.was_sent: - state_description = 'Waiting for participate txn to be spent in {} chain'.format(ci_follower.ticker()) + state_description = ( + "Waiting for participate txn to be spent in {} chain".format( + ci_follower.ticker() + ) + ) else: - state_description = 'Waiting for initiate txn to be spent in {} chain'.format(ci_leader.ticker()) + state_description = ( + "Waiting for initiate txn to be spent in {} chain".format( + ci_leader.ticker() + ) + ) elif bid.state == BidStates.SWAP_COMPLETED: - state_description = 'Swap completed' - if bid.getITxState() == TxStates.TX_REDEEMED and bid.getPTxState() == TxStates.TX_REDEEMED: - state_description += ' successfully' + state_description = "Swap completed" + if ( + bid.getITxState() == TxStates.TX_REDEEMED + and bid.getPTxState() == TxStates.TX_REDEEMED + ): + state_description += " successfully" else: - state_description += ', ITX ' + strTxState(bid.getITxState()) + ', PTX ' + strTxState(bid.getPTxState()) + state_description += ( + ", ITX " + + strTxState(bid.getITxState()) + + ", PTX " + + strTxState(bid.getPTxState()) + ) elif bid.state == BidStates.SWAP_TIMEDOUT: - state_description = 'Timed out waiting for initiate txn' + state_description = "Timed out waiting for initiate txn" elif bid.state == BidStates.BID_ABANDONED: - state_description = 'Bid abandoned' + state_description = "Bid abandoned" elif bid.state == BidStates.BID_ERROR: state_description = bid.state_note elif offer.swap_type == SwapTypes.XMR_SWAP: if bid.state == BidStates.BID_SENT: - state_description = 'Waiting for offerer to accept' + state_description = "Waiting for offerer to accept" if bid.state == BidStates.BID_RECEIVING: # Offerer receiving bid from bidder - state_description = 'Waiting for bid to be fully received' + state_description = "Waiting for bid to be fully received" elif bid.state == BidStates.BID_RECEIVED: # Offerer received bid from bidder # TODO: Manual vs automatic - state_description = 'Bid must be accepted' + state_description = "Bid must be accepted" elif bid.state == BidStates.BID_RECEIVING_ACC: - state_description = 'Receiving accepted bid message' + state_description = "Receiving accepted bid message" elif bid.state == BidStates.BID_ACCEPTED: - state_description = 'Offerer has accepted bid, waiting for bidder to respond' + state_description = ( + "Offerer has accepted bid, waiting for bidder to respond" + ) elif bid.state == BidStates.SWAP_DELAYING: last_state = getLastBidState(bid.states) if last_state == BidStates.BID_RECEIVED: - state_description = 'Delaying before accepting bid' + state_description = "Delaying before accepting bid" elif last_state == BidStates.BID_RECEIVING_ACC: - state_description = 'Delaying before responding to accepted bid' + state_description = "Delaying before responding to accepted bid" elif last_state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED: - state_description = f'Delaying before spending from {ci_follower.ticker()} lock tx' + state_description = ( + f"Delaying before spending from {ci_follower.ticker()} lock tx" + ) elif last_state == BidStates.BID_ACCEPTED: - state_description = f'Delaying before sending {ci_leader.ticker()} lock tx' + state_description = ( + f"Delaying before sending {ci_leader.ticker()} lock tx" + ) else: - state_description = 'Delaying before automated action' + state_description = "Delaying before automated action" elif bid.state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX: - state_description = f'Waiting for {ci_leader.ticker()} lock tx to confirm in chain ({ci_leader.blocks_confirmed} blocks)' + state_description = f"Waiting for {ci_leader.ticker()} lock tx to confirm in chain ({ci_leader.blocks_confirmed} blocks)" elif bid.state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED: if xmr_swap.b_lock_tx_id is None: - state_description = f'Waiting for {ci_follower.ticker()} lock tx' + state_description = f"Waiting for {ci_follower.ticker()} lock tx" else: - state_description = f'Waiting for {ci_follower.ticker()} lock tx to confirm in chain ({ci_follower.blocks_confirmed} blocks)' + state_description = f"Waiting for {ci_follower.ticker()} lock tx to confirm in chain ({ci_follower.blocks_confirmed} blocks)" elif bid.state == BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED: - state_description = f'Waiting for {initiator_role} to unlock {ci_leader.ticker()} lock tx' + state_description = ( + f"Waiting for {initiator_role} to unlock {ci_leader.ticker()} lock tx" + ) elif bid.state == BidStates.XMR_SWAP_LOCK_RELEASED: - state_description = f'Waiting for {participant_role} to spend from {ci_leader.ticker()} lock tx' + state_description = f"Waiting for {participant_role} to spend from {ci_leader.ticker()} lock tx" elif bid.state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED: - state_description = f'Waiting for {initiator_role} to spend from {ci_follower.ticker()} lock tx' + state_description = f"Waiting for {initiator_role} to spend from {ci_follower.ticker()} lock tx" elif bid.state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED: - state_description = f'Waiting for {ci_follower.ticker()} lock tx spend tx to confirm in chain' + state_description = f"Waiting for {ci_follower.ticker()} lock tx spend tx to confirm in chain" elif bid.state == BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND: if bid.was_sent: - state_description = f'Waiting for {initiator_role} to redeem or locktime to expire' + state_description = ( + f"Waiting for {initiator_role} to redeem or locktime to expire" + ) else: - state_description = 'Redeeming output' + state_description = "Redeeming output" - addr_label = swap_client.getAddressLabel([bid.bid_addr, ])[0] + addr_label = swap_client.getAddressLabel( + [ + bid.bid_addr, + ] + )[0] can_abandon: bool = False - if swap_client.debug and bid.state not in (BidStates.BID_ABANDONED, BidStates.SWAP_COMPLETED): + if swap_client.debug and bid.state not in ( + BidStates.BID_ABANDONED, + BidStates.SWAP_COMPLETED, + ): can_abandon = True data = { - 'coin_from': ci_from.coin_name(), - 'coin_to': ci_to.coin_name(), - 'amt_from': ci_from.format_amount(bid_amount), - 'amt_to': ci_to.format_amount(bid_amount_to), - 'bid_rate': ci_to.format_amount(bid_rate), - 'ticker_from': ci_from.ticker(), - 'ticker_to': ci_to.ticker(), - 'bid_state': strBidState(bid.state), - 'state_description': state_description, - 'itx_state': strTxState(bid.getITxState()), - 'ptx_state': strTxState(bid.getPTxState()), - 'offer_id': bid.offer_id.hex(), - 'addr_from': bid.bid_addr, - 'addr_from_label': addr_label, - 'addr_fund_proof': bid.proof_address, - 'created_at': bid.created_at if for_api else format_timestamp(bid.created_at, with_seconds=True), - 'expired_at': bid.expire_at if for_api else format_timestamp(bid.expire_at, with_seconds=True), - 'was_sent': 'True' if bid.was_sent else 'False', - 'was_received': 'True' if bid.was_received else 'False', - 'initiate_tx': getTxIdHex(bid, TxTypes.ITX, ' ' + ci_leader.ticker()), - 'initiate_conf': 'None' if (not bid.initiate_tx or not bid.initiate_tx.conf) else bid.initiate_tx.conf, - 'participate_tx': getTxIdHex(bid, TxTypes.PTX, ' ' + ci_follower.ticker()), - 'participate_conf': 'None' if (not bid.participate_tx or not bid.participate_tx.conf) else bid.participate_tx.conf, - 'show_txns': show_txns, - 'can_abandon': can_abandon, - 'events': bid_events, - 'debug_ui': swap_client.debug_ui, - 'reverse_bid': reverse_bid, + "coin_from": ci_from.coin_name(), + "coin_to": ci_to.coin_name(), + "amt_from": ci_from.format_amount(bid_amount), + "amt_to": ci_to.format_amount(bid_amount_to), + "bid_rate": ci_to.format_amount(bid_rate), + "ticker_from": ci_from.ticker(), + "ticker_to": ci_to.ticker(), + "bid_state": strBidState(bid.state), + "state_description": state_description, + "itx_state": strTxState(bid.getITxState()), + "ptx_state": strTxState(bid.getPTxState()), + "offer_id": bid.offer_id.hex(), + "addr_from": bid.bid_addr, + "addr_from_label": addr_label, + "addr_fund_proof": bid.proof_address, + "created_at": ( + bid.created_at + if for_api + else format_timestamp(bid.created_at, with_seconds=True) + ), + "expired_at": ( + bid.expire_at + if for_api + else format_timestamp(bid.expire_at, with_seconds=True) + ), + "was_sent": "True" if bid.was_sent else "False", + "was_received": "True" if bid.was_received else "False", + "initiate_tx": getTxIdHex(bid, TxTypes.ITX, " " + ci_leader.ticker()), + "initiate_conf": ( + "None" + if (not bid.initiate_tx or not bid.initiate_tx.conf) + else bid.initiate_tx.conf + ), + "participate_tx": getTxIdHex(bid, TxTypes.PTX, " " + ci_follower.ticker()), + "participate_conf": ( + "None" + if (not bid.participate_tx or not bid.participate_tx.conf) + else bid.participate_tx.conf + ), + "show_txns": show_txns, + "can_abandon": can_abandon, + "events": bid_events, + "debug_ui": swap_client.debug_ui, + "reverse_bid": reverse_bid, } if edit_bid: - data['bid_state_ind'] = int(bid.state) - data['bid_states'] = listBidStates() + data["bid_state_ind"] = int(bid.state) + data["bid_states"] = listBidStates() if swap_client.debug_ui: - data['debug_ind'] = bid.debug_ind - data['debug_options'] = [(int(t), t.name) for t in DebugTypes] + data["debug_ind"] = bid.debug_ind + data["debug_options"] = [(int(t), t.name) for t in DebugTypes] if show_txns: if offer.swap_type == SwapTypes.XMR_SWAP: txns = [] if bid.xmr_a_lock_tx: confirms = None - if swap_client.coin_clients[ci_leader.coin_type()]['chain_height'] and bid.xmr_a_lock_tx.chain_height: - confirms = (swap_client.coin_clients[ci_leader.coin_type()]['chain_height'] - bid.xmr_a_lock_tx.chain_height) + 1 - txns.append({'type': 'Chain A Lock', 'txid': hex_or_none(bid.xmr_a_lock_tx.txid), 'confirms': confirms}) + if ( + swap_client.coin_clients[ci_leader.coin_type()]["chain_height"] + and bid.xmr_a_lock_tx.chain_height + ): + confirms = ( + swap_client.coin_clients[ci_leader.coin_type()]["chain_height"] + - bid.xmr_a_lock_tx.chain_height + ) + 1 + txns.append( + { + "type": "Chain A Lock", + "txid": hex_or_none(bid.xmr_a_lock_tx.txid), + "confirms": confirms, + } + ) if bid.xmr_a_lock_spend_tx: - txns.append({'type': 'Chain A Lock Spend', 'txid': bid.xmr_a_lock_spend_tx.txid.hex()}) + txns.append( + { + "type": "Chain A Lock Spend", + "txid": bid.xmr_a_lock_spend_tx.txid.hex(), + } + ) if bid.xmr_b_lock_tx: confirms = None - if swap_client.coin_clients[ci_follower.coin_type()]['chain_height'] and bid.xmr_b_lock_tx.chain_height: - confirms = (swap_client.coin_clients[ci_follower.coin_type()]['chain_height'] - bid.xmr_b_lock_tx.chain_height) + 1 - txns.append({'type': 'Chain B Lock', 'txid': bid.xmr_b_lock_tx.txid.hex(), 'confirms': confirms}) + if ( + swap_client.coin_clients[ci_follower.coin_type()]["chain_height"] + and bid.xmr_b_lock_tx.chain_height + ): + confirms = ( + swap_client.coin_clients[ci_follower.coin_type()][ + "chain_height" + ] + - bid.xmr_b_lock_tx.chain_height + ) + 1 + txns.append( + { + "type": "Chain B Lock", + "txid": bid.xmr_b_lock_tx.txid.hex(), + "confirms": confirms, + } + ) if bid.xmr_b_lock_tx and bid.xmr_b_lock_tx.spend_txid: - txns.append({'type': 'Chain B Lock Spend', 'txid': bid.xmr_b_lock_tx.spend_txid.hex()}) + txns.append( + { + "type": "Chain B Lock Spend", + "txid": bid.xmr_b_lock_tx.spend_txid.hex(), + } + ) if xmr_swap.a_lock_refund_tx: - txns.append({'type': strTxType(TxTypes.XMR_SWAP_A_LOCK_REFUND), 'txid': xmr_swap.a_lock_refund_tx_id.hex()}) + txns.append( + { + "type": strTxType(TxTypes.XMR_SWAP_A_LOCK_REFUND), + "txid": xmr_swap.a_lock_refund_tx_id.hex(), + } + ) if xmr_swap.a_lock_refund_spend_tx: - txns.append({'type': strTxType(TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND), 'txid': xmr_swap.a_lock_refund_spend_tx_id.hex()}) + txns.append( + { + "type": strTxType(TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND), + "txid": xmr_swap.a_lock_refund_spend_tx_id.hex(), + } + ) for tx_type, tx in bid.txns.items(): - if tx_type in (TxTypes.XMR_SWAP_A_LOCK_REFUND, TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND): + if tx_type in ( + TxTypes.XMR_SWAP_A_LOCK_REFUND, + TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND, + ): continue - txns.append({'type': strTxType(tx_type), 'txid': tx.txid.hex()}) - data['txns'] = txns + txns.append({"type": strTxType(tx_type), "txid": tx.txid.hex()}) + data["txns"] = txns - data['xmr_b_shared_address'] = ci_to.encodeSharedAddress(xmr_swap.pkbv, xmr_swap.pkbs) if xmr_swap.pkbs else None - data['xmr_b_shared_viewkey'] = ci_to.encodeKey(xmr_swap.vkbv) if xmr_swap.vkbv else None + data["xmr_b_shared_address"] = ( + ci_to.encodeSharedAddress(xmr_swap.pkbv, xmr_swap.pkbs) + if xmr_swap.pkbs + else None + ) + data["xmr_b_shared_viewkey"] = ( + ci_to.encodeKey(xmr_swap.vkbv) if xmr_swap.vkbv else None + ) if swap_client.debug_ui: try: - data['xmr_b_half_privatekey'] = getChainBSplitKey(swap_client, bid, xmr_swap, offer) - except Exception as e: - swap_client.log.debug('Unable to get xmr_b_half_privatekey for bid: {}'.format(bid.bid_id.hex())) + data["xmr_b_half_privatekey"] = getChainBSplitKey( + swap_client, bid, xmr_swap, offer + ) + except Exception as e: # noqa: F841 + swap_client.log.debug( + "Unable to get xmr_b_half_privatekey for bid: {}".format( + bid.bid_id.hex() + ) + ) try: - remote_split_key = getChainBRemoteSplitKey(swap_client, bid, xmr_swap, offer) + remote_split_key = getChainBRemoteSplitKey( + swap_client, bid, xmr_swap, offer + ) if remote_split_key: - data['xmr_b_half_privatekey_remote'] = remote_split_key - except Exception as e: - swap_client.log.debug('Unable to get xmr_b_half_privatekey_remote for bid: {}'.format(bid.bid_id.hex())) + data["xmr_b_half_privatekey_remote"] = remote_split_key + except Exception as e: # noqa: F841 + swap_client.log.debug( + "Unable to get xmr_b_half_privatekey_remote for bid: {}".format( + bid.bid_id.hex() + ) + ) if show_lock_transfers: if xmr_swap.pkbs: - data['lock_transfers'] = json.dumps(ci_to.showLockTransfers(xmr_swap.vkbv, xmr_swap.pkbs, bid.chain_b_height_start), indent=4) + data["lock_transfers"] = json.dumps( + ci_to.showLockTransfers( + xmr_swap.vkbv, xmr_swap.pkbs, bid.chain_b_height_start + ), + indent=4, + ) else: - data['lock_transfers'] = 'Shared address not yet known.' + data["lock_transfers"] = "Shared address not yet known." else: - data['initiate_tx_refund'] = 'None' if not bid.initiate_txn_refund else bid.initiate_txn_refund.hex() - data['participate_tx_refund'] = 'None' if not bid.participate_txn_refund else bid.participate_txn_refund.hex() - data['initiate_tx_spend'] = getTxSpendHex(bid, TxTypes.ITX) - data['participate_tx_spend'] = getTxSpendHex(bid, TxTypes.PTX) + data["initiate_tx_refund"] = ( + "None" if not bid.initiate_txn_refund else bid.initiate_txn_refund.hex() + ) + data["participate_tx_refund"] = ( + "None" + if not bid.participate_txn_refund + else bid.participate_txn_refund.hex() + ) + data["initiate_tx_spend"] = getTxSpendHex(bid, TxTypes.ITX) + data["participate_tx_spend"] = getTxSpendHex(bid, TxTypes.PTX) if bid.initiate_tx and bid.initiate_tx.tx_data is not None: - data['initiate_tx_inputs'] = ci_from.listInputs(bid.initiate_tx.tx_data) + data["initiate_tx_inputs"] = ci_from.listInputs(bid.initiate_tx.tx_data) if bid.participate_tx and bid.participate_tx.tx_data is not None: - data['initiate_tx_inputs'] = ci_from.listInputs(bid.participate_tx.tx_data) + data["initiate_tx_inputs"] = ci_from.listInputs( + bid.participate_tx.tx_data + ) if offer.swap_type == SwapTypes.XMR_SWAP: - data['coin_a_lock_refund_tx_est_final'] = 'None' - data['coin_a_lock_refund_swipe_tx_est_final'] = 'None' + data["coin_a_lock_refund_tx_est_final"] = "None" + data["coin_a_lock_refund_swipe_tx_est_final"] = "None" if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME: if bid.xmr_a_lock_tx and bid.xmr_a_lock_tx.block_time: - raw_sequence = ci_leader.getExpectedSequence(offer.lock_type, offer.lock_value) + raw_sequence = ci_leader.getExpectedSequence( + offer.lock_type, offer.lock_value + ) seconds_locked = ci_leader.decodeSequence(raw_sequence) - data['coin_a_lock_refund_tx_est_final'] = bid.xmr_a_lock_tx.block_time + seconds_locked - data['coin_a_last_median_time'] = swap_client.coin_clients[offer.coin_from]['chain_median_time'] + data["coin_a_lock_refund_tx_est_final"] = ( + bid.xmr_a_lock_tx.block_time + seconds_locked + ) + data["coin_a_last_median_time"] = swap_client.coin_clients[ + offer.coin_from + ]["chain_median_time"] if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns: refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] if refund_tx.block_time is not None: - raw_sequence = ci_leader.getExpectedSequence(offer.lock_type, offer.lock_value) + raw_sequence = ci_leader.getExpectedSequence( + offer.lock_type, offer.lock_value + ) seconds_locked = ci_leader.decodeSequence(raw_sequence) - data['coin_a_lock_refund_swipe_tx_est_final'] = refund_tx.block_time + seconds_locked + data["coin_a_lock_refund_swipe_tx_est_final"] = ( + refund_tx.block_time + seconds_locked + ) if view_tx_ind: - data['view_tx_ind'] = view_tx_ind + data["view_tx_ind"] = view_tx_ind view_tx_id = bytes.fromhex(view_tx_ind) if xmr_swap: if view_tx_id == xmr_swap.a_lock_tx_id and xmr_swap.a_lock_tx: - data['view_tx_hex'] = xmr_swap.a_lock_tx.hex() - data['chain_a_lock_tx_inputs'] = ci_leader.listInputs(xmr_swap.a_lock_tx) - if view_tx_id == xmr_swap.a_lock_refund_tx_id and xmr_swap.a_lock_refund_tx: - data['view_tx_hex'] = xmr_swap.a_lock_refund_tx.hex() - if view_tx_id == xmr_swap.a_lock_refund_spend_tx_id and xmr_swap.a_lock_refund_spend_tx: - data['view_tx_hex'] = xmr_swap.a_lock_refund_spend_tx.hex() - if view_tx_id == xmr_swap.a_lock_spend_tx_id and xmr_swap.a_lock_spend_tx: - data['view_tx_hex'] = xmr_swap.a_lock_spend_tx.hex() + data["view_tx_hex"] = xmr_swap.a_lock_tx.hex() + data["chain_a_lock_tx_inputs"] = ci_leader.listInputs( + xmr_swap.a_lock_tx + ) + if ( + view_tx_id == xmr_swap.a_lock_refund_tx_id + and xmr_swap.a_lock_refund_tx + ): + data["view_tx_hex"] = xmr_swap.a_lock_refund_tx.hex() + if ( + view_tx_id == xmr_swap.a_lock_refund_spend_tx_id + and xmr_swap.a_lock_refund_spend_tx + ): + data["view_tx_hex"] = xmr_swap.a_lock_refund_spend_tx.hex() + if ( + view_tx_id == xmr_swap.a_lock_spend_tx_id + and xmr_swap.a_lock_spend_tx + ): + data["view_tx_hex"] = xmr_swap.a_lock_spend_tx.hex() - if 'view_tx_hex' in data: - data['view_tx_desc'] = json.dumps(ci_leader.describeTx(data['view_tx_hex']), indent=4) + if "view_tx_hex" in data: + data["view_tx_desc"] = json.dumps( + ci_leader.describeTx(data["view_tx_hex"]), indent=4 + ) else: if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME: if bid.initiate_tx and bid.initiate_tx.block_time is not None: - raw_sequence = ci_leader.getExpectedSequence(offer.lock_type, offer.lock_value) + raw_sequence = ci_leader.getExpectedSequence( + offer.lock_type, offer.lock_value + ) seconds_locked = ci_leader.decodeSequence(raw_sequence) - data['itx_refund_tx_est_final'] = bid.initiate_tx.block_time + seconds_locked + data["itx_refund_tx_est_final"] = ( + bid.initiate_tx.block_time + seconds_locked + ) if bid.participate_tx and bid.participate_tx.block_time is not None: - raw_sequence = ci_follower.getExpectedSequence(offer.lock_type, offer.lock_value // 2) + raw_sequence = ci_follower.getExpectedSequence( + offer.lock_type, offer.lock_value // 2 + ) seconds_locked = ci_follower.decodeSequence(raw_sequence) - data['ptx_refund_tx_est_final'] = bid.participate_tx.block_time + seconds_locked + data["ptx_refund_tx_est_final"] = ( + bid.participate_tx.block_time + seconds_locked + ) return data @@ -408,20 +599,24 @@ def listOldBidStates(bid): old_states = [] num_states = len(bid.states) // 12 for i in range(num_states): - up = struct.unpack_from(' 0: old_states.sort(key=lambda x: x[0]) return old_states @@ -429,16 +624,16 @@ def listOldBidStates(bid): def getCoinName(c): if c == Coins.PART_ANON: - return chainparams[Coins.PART]['name'].capitalize() + ' Anon' + return chainparams[Coins.PART]["name"].capitalize() + " Anon" if c == Coins.PART_BLIND: - return chainparams[Coins.PART]['name'].capitalize() + ' Blind' + return chainparams[Coins.PART]["name"].capitalize() + " Blind" if c == Coins.LTC_MWEB: - return chainparams[Coins.LTC]['name'].capitalize() + ' MWEB' + return chainparams[Coins.LTC]["name"].capitalize() + " MWEB" coin_chainparams = chainparams[c] - if 'display_name' in coin_chainparams: - return coin_chainparams['display_name'] - return coin_chainparams['name'].capitalize() + if "display_name" in coin_chainparams: + return coin_chainparams["display_name"] + return coin_chainparams["name"].capitalize() def listAvailableCoins(swap_client, with_variants=True, split_from=False): @@ -447,7 +642,7 @@ def listAvailableCoins(swap_client, with_variants=True, split_from=False): for k, v in swap_client.coin_clients.items(): if k not in chainparams: continue - if v['connection_type'] == 'rpc': + if v["connection_type"] == "rpc": coins.append((int(k), getCoinName(k))) if split_from and k not in invalid_coins_from: coins_from.append(coins[-1]) @@ -457,7 +652,7 @@ def listAvailableCoins(swap_client, with_variants=True, split_from=False): if split_from and v not in invalid_coins_from: coins_from.append(coins[-1]) if with_variants and k == Coins.LTC: - for v in (Coins.LTC_MWEB, ): + for v in (Coins.LTC_MWEB,): pass # Add when swappable if split_from: return coins_from, coins @@ -465,29 +660,37 @@ def listAvailableCoins(swap_client, with_variants=True, split_from=False): def checkAddressesOwned(swap_client, ci, wallet_info): - if 'stealth_address' in wallet_info: + if "stealth_address" in wallet_info: - if wallet_info['stealth_address'] != '?': - if not ci.isAddressMine(wallet_info['stealth_address']): - ci._log.error('Unowned stealth address: {}'.format(wallet_info['stealth_address'])) - wallet_info['stealth_address'] = 'Error: unowned address' - elif swap_client._restrict_unknown_seed_wallets and not ci.knownWalletSeed(): - wallet_info['stealth_address'] = 'WARNING: Unknown wallet seed' + if wallet_info["stealth_address"] != "?": + if not ci.isAddressMine(wallet_info["stealth_address"]): + ci._log.error( + "Unowned stealth address: {}".format(wallet_info["stealth_address"]) + ) + wallet_info["stealth_address"] = "Error: unowned address" + elif ( + swap_client._restrict_unknown_seed_wallets and not ci.knownWalletSeed() + ): + wallet_info["stealth_address"] = "WARNING: Unknown wallet seed" - if 'deposit_address' in wallet_info: - if wallet_info['deposit_address'] != 'Refresh necessary': - if not ci.isAddressMine(wallet_info['deposit_address']): - ci._log.error('Unowned deposit address: {}'.format(wallet_info['deposit_address'])) - wallet_info['deposit_address'] = 'Error: unowned address' - elif swap_client._restrict_unknown_seed_wallets and not ci.knownWalletSeed(): - wallet_info['deposit_address'] = 'WARNING: Unknown wallet seed' + if "deposit_address" in wallet_info: + if wallet_info["deposit_address"] != "Refresh necessary": + if not ci.isAddressMine(wallet_info["deposit_address"]): + ci._log.error( + "Unowned deposit address: {}".format(wallet_info["deposit_address"]) + ) + wallet_info["deposit_address"] = "Error: unowned address" + elif ( + swap_client._restrict_unknown_seed_wallets and not ci.knownWalletSeed() + ): + wallet_info["deposit_address"] = "WARNING: Unknown wallet seed" def validateTextInput(text, name, messages, max_length=None): if max_length is not None and len(text) > max_length: - messages.append(f'Error: {name} is too long') + messages.append(f"Error: {name} is too long") return False if len(text) > 0 and all(c.isalnum() or c.isspace() for c in text) is False: - messages.append(f'Error: {name} must consist of only letters and digits') + messages.append(f"Error: {name} must consist of only letters and digits") return False return True diff --git a/basicswap/util/__init__.py b/basicswap/util/__init__.py index 155d603..05323a0 100644 --- a/basicswap/util/__init__.py +++ b/basicswap/util/__init__.py @@ -39,7 +39,7 @@ class LockedCoinError(Exception): self.coinid = coinid def __str__(self): - return 'Coin must be unlocked: ' + str(self.coinid) + return "Coin must be unlocked: " + str(self.coinid) def ensure(v, err_string): @@ -50,7 +50,7 @@ def ensure(v, err_string): def toBool(s) -> bool: if isinstance(s, bool): return s - return s.lower() in ['1', 'true'] + return s.lower() in ["1", "true"] def jsonDecimal(obj): @@ -76,8 +76,8 @@ def SerialiseNum(n: int) -> bytes: rv = bytearray() neg = n < 0 absvalue = -n if neg else n - while (absvalue): - rv.append(absvalue & 0xff) + while absvalue: + rv.append(absvalue & 0xFF) absvalue >>= 8 if rv[-1] & 0x80: rv.append(0x80 if neg else 0) @@ -106,34 +106,36 @@ def DeserialiseNum(b: bytes, o: int = 0) -> int: def float_to_str(f: float) -> str: # stackoverflow.com/questions/38847690 d1 = decimal_ctx.create_decimal(repr(f)) - return format(d1, 'f') + return format(d1, "f") -def make_int(v, scale: int = 8, r: int = 0) -> int: # r = 0, no rounding (fail), r > 0 round off, r < 0 floor +def make_int( + v, scale: int = 8, r: int = 0 +) -> int: # r = 0, no rounding (fail), r > 0 round off, r < 0 floor if isinstance(v, float): v = float_to_str(v) elif isinstance(v, int): - return v * 10 ** scale + return v * 10**scale sign = 1 - if v[0] == '-': + if v[0] == "-": v = v[1:] sign = -1 - ep = 10 ** scale + ep = 10**scale have_dp = False rv = 0 for c in v: - if c == '.': + if c == ".": rv *= ep have_dp = True continue if not c.isdigit(): - raise ValueError('Invalid char: ' + c) + raise ValueError("Invalid char: " + c) if have_dp: ep //= 10 if ep <= 0: if r == 0: - raise ValueError('Mantissa too long') + raise ValueError("Mantissa too long") if r > 0: # Round off if int(c) > 4: @@ -151,51 +153,53 @@ def validate_amount(amount, scale: int = 8) -> bool: str_amount = float_to_str(amount) if isinstance(amount, float) else str(amount) has_decimal = False for c in str_amount: - if c == '.' and not has_decimal: + if c == "." and not has_decimal: has_decimal = True continue if not c.isdigit(): - raise ValueError('Invalid amount') + raise ValueError("Invalid amount") - ar = str_amount.split('.') + ar = str_amount.split(".") if len(ar) > 1 and len(ar[1]) > scale: - raise ValueError('Too many decimal places in amount {}'.format(str_amount)) + raise ValueError("Too many decimal places in amount {}".format(str_amount)) return True def format_amount(i: int, display_scale: int, scale: int = None) -> str: if not isinstance(i, int): - raise ValueError('Amount must be an integer.') # Raise error instead of converting as amounts should always be integers + raise ValueError( + "Amount must be an integer." + ) # Raise error instead of converting as amounts should always be integers if scale is None: scale = display_scale - ep = 10 ** scale + ep = 10**scale n = abs(i) quotient = n // ep remainder = n % ep if display_scale != scale: - remainder %= (10 ** display_scale) - rv = '{}.{:0>{scale}}'.format(quotient, remainder, scale=display_scale) + remainder %= 10**display_scale + rv = "{}.{:0>{scale}}".format(quotient, remainder, scale=display_scale) if i < 0: - rv = '-' + rv + rv = "-" + rv return rv def format_timestamp(value: int, with_seconds: bool = False) -> str: - str_format = '%Y-%m-%d %H:%M' + str_format = "%Y-%m-%d %H:%M" if with_seconds: - str_format += ':%S' - str_format += ' %z' + str_format += ":%S" + str_format += " %z" return time.strftime(str_format, time.localtime(value)) def b2i(b: bytes) -> int: # bytes32ToInt - return int.from_bytes(b, byteorder='big') + return int.from_bytes(b, byteorder="big") def i2b(i: int) -> bytes: # intToBytes32 - return i.to_bytes(32, byteorder='big') + return i.to_bytes(32, byteorder="big") def b2h(b: bytes) -> str: @@ -203,7 +207,7 @@ def b2h(b: bytes) -> str: def h2b(h: str) -> bytes: - if h.startswith('0x'): + if h.startswith("0x"): h = h[2:] return bytes.fromhex(h) @@ -220,5 +224,5 @@ def zeroIfNone(value) -> int: def hex_or_none(value: bytes) -> str: if value is None: - return 'None' + return "None" return value.hex() diff --git a/basicswap/util/address.py b/basicswap/util/address.py index 675c777..2b64cb0 100644 --- a/basicswap/util/address.py +++ b/basicswap/util/address.py @@ -7,12 +7,12 @@ from basicswap.contrib.segwit_addr import bech32_decode, convertbits, bech32_encode from basicswap.util.crypto import ripemd160, sha256 -__b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +__b58chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" def b58decode(v, length=None): long_value = 0 - for (i, c) in enumerate(v[::-1]): + for i, c in enumerate(v[::-1]): ofs = __b58chars.find(c) if ofs < 0: return None @@ -38,10 +38,10 @@ def b58decode(v, length=None): def b58encode(v): long_value = 0 - for (i, c) in enumerate(v[::-1]): + for i, c in enumerate(v[::-1]): long_value += (256**i) * c - result = '' + result = "" while long_value >= 58: div, mod = divmod(long_value, 58) result = __b58chars[mod] + result @@ -58,7 +58,9 @@ def b58encode(v): return (__b58chars[0] * nPad) + result -def encodeStealthAddress(prefix_byte: int, scan_pubkey: bytes, spend_pubkey: bytes) -> str: +def encodeStealthAddress( + prefix_byte: int, scan_pubkey: bytes, spend_pubkey: bytes +) -> str: data = bytes((0x00,)) data += scan_pubkey data += bytes((0x01,)) @@ -114,7 +116,7 @@ def decodeAddress(address: str) -> bytes: prefixed_data = addr_data[:-4] checksum = addr_data[-4:] if sha256(sha256(prefixed_data))[:4] != checksum: - raise ValueError('Checksum mismatch') + raise ValueError("Checksum mismatch") return prefixed_data diff --git a/basicswap/util/ecc.py b/basicswap/util/ecc.py index fb71e93..fa72d62 100644 --- a/basicswap/util/ecc.py +++ b/basicswap/util/ecc.py @@ -9,7 +9,7 @@ from basicswap.contrib.ellipticcurve import CurveFp, Point, INFINITY, jacobi_sym from . import i2b -class ECCParameters(): +class ECCParameters: def __init__(self, p, a, b, Gx, Gy, o): self.p = p self.a = a @@ -20,12 +20,13 @@ class ECCParameters(): ep = ECCParameters( - p=0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f, + p=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F, a=0x0, b=0x7, - Gx=0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798, - Gy=0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8, - o=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141) + Gx=0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, + Gy=0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, + o=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141, +) curve_secp256k1 = CurveFp(ep.p, ep.a, ep.b) @@ -34,7 +35,11 @@ SECP256K1_ORDER_HALF = ep.o // 2 def ToDER(P) -> bytes: - return bytes((4, )) + int(P.x()).to_bytes(32, byteorder='big') + int(P.y()).to_bytes(32, byteorder='big') + return ( + bytes((4,)) + + int(P.x()).to_bytes(32, byteorder="big") + + int(P.y()).to_bytes(32, byteorder="big") + ) def getSecretBytes() -> bytes: @@ -50,7 +55,7 @@ def getInsecureBytes() -> bytes: while True: s = os.urandom(32) - s_test = int.from_bytes(s, byteorder='big') + s_test = int.from_bytes(s, byteorder="big") if s_test > 1 and s_test < ep.o: return s @@ -59,7 +64,7 @@ def getInsecureInt() -> int: while True: s = os.urandom(32) - s_test = int.from_bytes(s, byteorder='big') + s_test = int.from_bytes(s, byteorder="big") if s_test > 1 and s_test < ep.o: return s_test @@ -77,7 +82,7 @@ def powMod(x, y, z) -> int: def ExpandPoint(xb, sign): - x = int.from_bytes(xb, byteorder='big') + x = int.from_bytes(xb, byteorder="big") a = (powMod(x, 3, ep.p) + 7) % ep.p y = powMod(a, (ep.p + 1) // 4, ep.p) @@ -89,7 +94,7 @@ def ExpandPoint(xb, sign): def CPKToPoint(cpk): y_parity = cpk[0] - 2 - x = int.from_bytes(cpk[1:], byteorder='big') + x = int.from_bytes(cpk[1:], byteorder="big") a = (powMod(x, 3, ep.p) + 7) % ep.p y = powMod(a, (ep.p + 1) // 4, ep.p) @@ -102,28 +107,29 @@ def CPKToPoint(cpk): def pointToCPK2(point, ind=0x09): # The function is_square(x), where x is an integer, returns whether or not x is a quadratic residue modulo p. Since p is prime, it is equivalent to the Legendre symbol (x / p) = x(p-1)/2 mod p being equal to 1[8]. ind = bytes((ind ^ (1 if jacobi_symbol(point.y(), ep.p) == 1 else 0),)) - return ind + point.x().to_bytes(32, byteorder='big') + return ind + point.x().to_bytes(32, byteorder="big") def pointToCPK(point): - y = point.y().to_bytes(32, byteorder='big') + y = point.y().to_bytes(32, byteorder="big") ind = bytes((0x03,)) if y[31] % 2 else bytes((0x02,)) - cpk = ind + point.x().to_bytes(32, byteorder='big') + cpk = ind + point.x().to_bytes(32, byteorder="big") return cpk def secretToCPK(secret): - secretInt = secret if isinstance(secret, int) \ - else int.from_bytes(secret, byteorder='big') + secretInt = ( + secret if isinstance(secret, int) else int.from_bytes(secret, byteorder="big") + ) R = G * secretInt - Y = R.y().to_bytes(32, byteorder='big') + Y = R.y().to_bytes(32, byteorder="big") ind = bytes((0x03,)) if Y[31] % 2 else bytes((0x02,)) - pubkey = ind + R.x().to_bytes(32, byteorder='big') + pubkey = ind + R.x().to_bytes(32, byteorder="big") return pubkey @@ -136,7 +142,7 @@ def getKeypair(): def hashToCurve(pubkey): xBytes = hashlib.sha256(pubkey).digest() - x = int.from_bytes(xBytes, byteorder='big') + x = int.from_bytes(xBytes, byteorder="big") for k in range(0, 100): # get matching y element for point @@ -155,12 +161,14 @@ def hashToCurve(pubkey): x = (x + 1) % ep.p # % P? continue - if R == INFINITY or R * ep.o != INFINITY: # is R * O != INFINITY check necessary? Validation of Elliptic Curve Public Keys says no if cofactor = 1 + if ( + R == INFINITY or R * ep.o != INFINITY + ): # is R * O != INFINITY check necessary? Validation of Elliptic Curve Public Keys says no if cofactor = 1 x = (x + 1) % ep.p # % P? continue return R - raise ValueError('hashToCurve failed for 100 tries') + raise ValueError("hashToCurve failed for 100 tries") def hash256(inb): @@ -168,23 +176,35 @@ def hash256(inb): def testEccUtils(): - print('testEccUtils()') + print("testEccUtils()") G_enc = ToDER(G) - assert (G_enc.hex() == '0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8') + assert ( + G_enc.hex() + == "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8" + ) G_enc = pointToCPK(G) - assert (G_enc.hex() == '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798') + assert ( + G_enc.hex() + == "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ) G_dec = CPKToPoint(G_enc) - assert (G_dec == G) + assert G_dec == G G_enc = pointToCPK2(G) - assert (G_enc.hex() == '0879be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798') + assert ( + G_enc.hex() + == "0879be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ) H = hashToCurve(ToDER(G)) - assert (pointToCPK(H).hex() == '0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0') + assert ( + pointToCPK(H).hex() + == "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" + ) - print('Passed.') + print("Passed.") if __name__ == "__main__": diff --git a/basicswap/util/extkey.py b/basicswap/util/extkey.py index 1757fa8..0f74478 100644 --- a/basicswap/util/extkey.py +++ b/basicswap/util/extkey.py @@ -7,21 +7,30 @@ from .crypto import blake256, hash160, hmac_sha512, ripemd160 -from coincurve.keys import ( - PrivateKey, - PublicKey) +from coincurve.keys import PrivateKey, PublicKey def BIP32Hash(chaincode: bytes, child_no: int, key_data_type: int, keydata: bytes): - return hmac_sha512(chaincode, key_data_type.to_bytes(1, 'big') + keydata + child_no.to_bytes(4, 'big')) + return hmac_sha512( + chaincode, + key_data_type.to_bytes(1, "big") + keydata + child_no.to_bytes(4, "big"), + ) def hash160_dcr(data: bytes) -> bytes: return ripemd160(blake256(data)) -class ExtKeyPair(): - __slots__ = ('_depth', '_fingerprint', '_child_no', '_chaincode', '_key', '_pubkey', 'hash_func') +class ExtKeyPair: + __slots__ = ( + "_depth", + "_fingerprint", + "_child_no", + "_chaincode", + "_key", + "_pubkey", + "hash_func", + ) def __init__(self, coin_type=1): if coin_type == 4: @@ -30,20 +39,20 @@ class ExtKeyPair(): self.hash_func = hash160 def set_seed(self, seed: bytes) -> None: - hashout: bytes = hmac_sha512(b'Bitcoin seed', seed) + hashout: bytes = hmac_sha512(b"Bitcoin seed", seed) self._key = hashout[:32] self._pubkey = None self._chaincode = hashout[32:] self._depth = 0 self._child_no = 0 - self._fingerprint = b'\0' * 4 + self._fingerprint = b"\0" * 4 def has_key(self) -> bool: return False if self._key is None else True def neuter(self) -> None: if self._key is None: - raise ValueError('Already neutered') + raise ValueError("Already neutered") self._pubkey = PublicKey.from_secret(self._key).format() self._key = None @@ -74,7 +83,11 @@ class ExtKeyPair(): out._pubkey = K.format() else: k = PrivateKey(self._key) - out._fingerprint = self.hash_func(self._pubkey if self._pubkey else PublicKey.from_secret(self._key).format())[:4] + out._fingerprint = self.hash_func( + self._pubkey + if self._pubkey + else PublicKey.from_secret(self._key).format() + )[:4] new_hash = BIP32Hash(self._chaincode, child_no, 0, self._key) out._chaincode = new_hash[32:] k.add(new_hash[:32], update=True) @@ -85,27 +98,35 @@ class ExtKeyPair(): return out def encode_v(self) -> bytes: - return self._depth.to_bytes(1, 'big') + \ - self._fingerprint + \ - self._child_no.to_bytes(4, 'big') + \ - self._chaincode + \ - b'\x00' + \ - self._key + return ( + self._depth.to_bytes(1, "big") + + self._fingerprint + + self._child_no.to_bytes(4, "big") + + self._chaincode + + b"\x00" + + self._key + ) def encode_p(self) -> bytes: - pubkey = PublicKey.from_secret(self._key).format() if self._pubkey is None else self._pubkey - return self._depth.to_bytes(1, 'big') + \ - self._fingerprint + \ - self._child_no.to_bytes(4, 'big') + \ - self._chaincode + \ - pubkey + pubkey = ( + PublicKey.from_secret(self._key).format() + if self._pubkey is None + else self._pubkey + ) + return ( + self._depth.to_bytes(1, "big") + + self._fingerprint + + self._child_no.to_bytes(4, "big") + + self._chaincode + + pubkey + ) def decode(self, data: bytes) -> None: if len(data) != 74: - raise ValueError('Unexpected extkey length') + raise ValueError("Unexpected extkey length") self._depth = data[0] self._fingerprint = data[1:5] - self._child_no = int.from_bytes(data[5:9], 'big') + self._child_no = int.from_bytes(data[5:9], "big") self._chaincode = data[9:41] if data[41] == 0: diff --git a/basicswap/util/integer.py b/basicswap/util/integer.py index 8fea309..58f7ea0 100644 --- a/basicswap/util/integer.py +++ b/basicswap/util/integer.py @@ -7,25 +7,25 @@ def decode_compactsize(b: bytes, offset: int = 0) -> (int, int): i = b[offset] - if i < 0xfd: + if i < 0xFD: return i, 1 offset += 1 - if i == 0xfd: - return int.from_bytes(b[offset: offset + 2], 'little'), 3 - if i == 0xfe: - return int.from_bytes(b[offset: offset + 4], 'little'), 5 + if i == 0xFD: + return int.from_bytes(b[offset : offset + 2], "little"), 3 + if i == 0xFE: + return int.from_bytes(b[offset : offset + 4], "little"), 5 # 0xff - return int.from_bytes(b[offset: offset + 8], 'little'), 9 + return int.from_bytes(b[offset : offset + 8], "little"), 9 def encode_compactsize(i: int) -> bytes: - if i < 0xfd: + if i < 0xFD: return bytes((i,)) - if i <= 0xffff: - return bytes((0xfd,)) + i.to_bytes(2, 'little') - if i <= 0xffffffff: - return bytes((0xfe,)) + i.to_bytes(4, 'little') - return bytes((0xff,)) + i.to_bytes(8, 'little') + if i <= 0xFFFF: + return bytes((0xFD,)) + i.to_bytes(2, "little") + if i <= 0xFFFFFFFF: + return bytes((0xFE,)) + i.to_bytes(4, "little") + return bytes((0xFF,)) + i.to_bytes(8, "little") def decode_varint(b: bytes, offset: int = 0) -> (int, int): @@ -38,7 +38,7 @@ def decode_varint(b: bytes, offset: int = 0) -> (int, int): if not c & 0x80: break if num_bytes > 8: - raise ValueError('Too many bytes') + raise ValueError("Too many bytes") return i, num_bytes @@ -46,6 +46,6 @@ def encode_varint(i: int) -> bytes: b = bytearray() while i > 0x7F: b += bytes(((i & 0x7F) | 0x80,)) - i = (i >> 7) + i = i >> 7 b += bytes((i,)) return b diff --git a/basicswap/util/network.py b/basicswap/util/network.py index b2fc4bf..a029d73 100644 --- a/basicswap/util/network.py +++ b/basicswap/util/network.py @@ -15,9 +15,9 @@ from urllib.request import Request, urlopen def is_private_ip_address(addr: str): - if addr == 'localhost': + if addr == "localhost": return True - if addr.endswith('.local'): + if addr.endswith(".local"): return True try: return ipaddress.ip_address(addr).is_private @@ -27,7 +27,7 @@ def is_private_ip_address(addr: str): def make_reporthook(read_start: int, logger): read = read_start # Number of bytes read so far - last_percent_str = '' + last_percent_str = "" time_last = time.time() read_last = read_start display_last = time_last @@ -35,7 +35,7 @@ def make_reporthook(read_start: int, logger): average_buffer = [-1] * 8 if read_start > 0: - logger.info(f'Attempting to resume from byte {read_start}') + logger.info(f"Attempting to resume from byte {read_start}") def reporthook(blocknum, blocksize, totalsize): nonlocal read, last_percent_str, time_last, read_last, display_last, read_start @@ -72,32 +72,32 @@ def make_reporthook(read_start: int, logger): 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)) + 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) + speed_str = "{:.2f} kbps".format(average_bits_per_second / 1000) if totalsize > 0: - percent_str = '%5.0f%%' % (read * 1e2 / use_size) + 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) + logger.info(percent_str + " " + speed_str) last_percent_str = percent_str display_last = time_now else: - logger.info(f'Read {read}, {speed_str}') + logger.info(f"Read {read}, {speed_str}") + return reporthook def urlretrieve(url, filename, reporthook=None, data=None, resume_from=0): - '''urlretrieve with resume - ''' + """urlretrieve with resume""" url_type, path = _splittype(url) req = Request(url) if resume_from > 0: - req.add_header('Range', f'bytes={resume_from}-') + req.add_header("Range", f"bytes={resume_from}-") with contextlib.closing(urlopen(req)) as fp: headers = fp.info() @@ -106,7 +106,7 @@ def urlretrieve(url, filename, reporthook=None, data=None, resume_from=0): 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: + with open(filename, "ab" if resume_from > 0 else "wb") as tfp: result = filename, headers bs = 1024 * 8 size = -1 @@ -117,10 +117,10 @@ def urlretrieve(url, filename, reporthook=None, data=None, resume_from=0): size = int(headers["Content-Length"]) if "Content-Range" in headers: range_str = headers["Content-Range"] - offset = range_str.find('-') + 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') + raise ValueError("Download is not resuming from the expected byte") if reporthook: reporthook(blocknum, bs, size) @@ -137,7 +137,7 @@ def urlretrieve(url, filename, reporthook=None, data=None, resume_from=0): if size >= 0 and read < size: raise ContentTooShortError( - "retrieval incomplete: got only %i out of %i bytes" - % (read, size), result) + "retrieval incomplete: got only %i out of %i bytes" % (read, size), result + ) return result diff --git a/basicswap/util/rfc2440.py b/basicswap/util/rfc2440.py index ae6f2e8..6b3343f 100644 --- a/basicswap/util/rfc2440.py +++ b/basicswap/util/rfc2440.py @@ -16,7 +16,7 @@ def rfc2440_hash_password(password, salt=None): salt = secrets.token_bytes(8) assert len(salt) == 8 - hashbytes = salt + password.encode('utf-8') + hashbytes = salt + password.encode("utf-8") len_hashbytes = len(hashbytes) h = hashlib.sha1() @@ -27,5 +27,5 @@ def rfc2440_hash_password(password, salt=None): continue h.update(hashbytes[:count]) break - rv = '16:' + salt.hex() + '60' + h.hexdigest() + rv = "16:" + salt.hex() + "60" + h.hexdigest() return rv.upper() diff --git a/basicswap/util/script.py b/basicswap/util/script.py index 4b929e5..996ceed 100644 --- a/basicswap/util/script.py +++ b/basicswap/util/script.py @@ -6,7 +6,13 @@ import struct import hashlib -from basicswap.contrib.test_framework.script import OP_PUSHDATA1, OP_PUSHDATA2, OP_PUSHDATA4, CScriptInvalidError, CScriptTruncatedPushDataError +from basicswap.contrib.test_framework.script import ( + OP_PUSHDATA1, + OP_PUSHDATA2, + OP_PUSHDATA4, + CScriptInvalidError, + CScriptTruncatedPushDataError, +) from basicswap.script import OpCodes @@ -17,15 +23,15 @@ def decodeScriptNum(script_bytes, o): return ((num_len - OpCodes.OP_1) + 1, 1) if num_len > 4: - raise ValueError('Bad scriptnum length') # Max 4 bytes + raise ValueError("Bad scriptnum length") # Max 4 bytes if num_len + o >= len(script_bytes): - raise ValueError('Bad script length') + raise ValueError("Bad script length") o += 1 for i in range(num_len): b = script_bytes[o + i] # Negative flag set in last byte, if num is positive and > 0x80 an extra 0x00 byte will be appended if i == num_len - 1 and b & 0x80: - b &= (~(0x80) & 0xFF) + b &= ~(0x80) & 0xFF v += int(b) << 8 * i v *= -1 else: @@ -41,47 +47,50 @@ def decodePushData(script_bytes, o): i += 1 if opcode < OP_PUSHDATA1: - pushdata_type = 'PUSHDATA(%d)' % opcode + pushdata_type = "PUSHDATA(%d)" % opcode datasize = opcode elif opcode == OP_PUSHDATA1: - pushdata_type = 'PUSHDATA1' + pushdata_type = "PUSHDATA1" if i >= len(script_bytes): - raise CScriptInvalidError('PUSHDATA1: missing data length') + raise CScriptInvalidError("PUSHDATA1: missing data length") datasize = script_bytes[i] i += 1 elif opcode == OP_PUSHDATA2: - pushdata_type = 'PUSHDATA2' + pushdata_type = "PUSHDATA2" if i + 1 >= len(script_bytes): - raise CScriptInvalidError('PUSHDATA2: missing data length') + raise CScriptInvalidError("PUSHDATA2: missing data length") datasize = script_bytes[i] + (script_bytes[i + 1] << 8) i += 2 elif opcode == OP_PUSHDATA4: - pushdata_type = 'PUSHDATA4' + pushdata_type = "PUSHDATA4" if i + 3 >= len(script_bytes): - raise CScriptInvalidError('PUSHDATA4: missing data length') - datasize = script_bytes[i] + (script_bytes[i + 1] << 8) + (script_bytes[i + 2] << 16) + (script_bytes[i + 3] << 24) + raise CScriptInvalidError("PUSHDATA4: missing data length") + datasize = ( + script_bytes[i] + + (script_bytes[i + 1] << 8) + + (script_bytes[i + 2] << 16) + + (script_bytes[i + 3] << 24) + ) i += 4 else: assert False # shouldn't happen - data = bytes(script_bytes[i:i + datasize]) + data = bytes(script_bytes[i : i + datasize]) # Check for truncation if len(data) < datasize: - raise CScriptTruncatedPushDataError('%s: truncated data' % pushdata_type, data) + raise CScriptTruncatedPushDataError("%s: truncated data" % pushdata_type, data) # return data and the number of bytes to skip forward return (data, i + datasize - o) def getP2SHScriptForHash(p2sh): - return bytes((OpCodes.OP_HASH160, 0x14)) \ - + p2sh \ - + bytes((OpCodes.OP_EQUAL,)) + return bytes((OpCodes.OP_HASH160, 0x14)) + p2sh + bytes((OpCodes.OP_EQUAL,)) def getP2WSH(script): @@ -91,26 +100,26 @@ def getP2WSH(script): def SerialiseNumCompact(v): if v < 253: return bytes((v,)) - if v <= 0xffff: # USHRT_MAX + if v <= 0xFFFF: # USHRT_MAX return struct.pack(" str: prefix_bytes = version if isinstance(version, bytes) else encode_varint(version) buf = prefix_bytes + spend_point + view_point h = cn_fast_hash(buf) - buf = buf + bytes.fromhex(h[0: 8]) + buf = buf + bytes.fromhex(h[0:8]) return xmr_b58encode(buf.hex()) diff --git a/bin/install_certifi.py b/bin/install_certifi.py index 1a970f2..0d56fe4 100755 --- a/bin/install_certifi.py +++ b/bin/install_certifi.py @@ -11,17 +11,27 @@ import stat import subprocess import sys -STAT_0o775 = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR - | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP - | stat.S_IROTH | stat.S_IXOTH ) +STAT_0o775 = ( + stat.S_IRUSR + | stat.S_IWUSR + | stat.S_IXUSR + | stat.S_IRGRP + | stat.S_IWGRP + | stat.S_IXGRP + | stat.S_IROTH + | stat.S_IXOTH +) + def main(): openssl_dir, openssl_cafile = os.path.split( - ssl.get_default_verify_paths().openssl_cafile) + ssl.get_default_verify_paths().openssl_cafile + ) print(" -- pip install --upgrade certifi") - subprocess.check_call([sys.executable, - "-E", "-s", "-m", "pip", "install", "--upgrade", "certifi"]) + subprocess.check_call( + [sys.executable, "-E", "-s", "-m", "pip", "install", "--upgrade", "certifi"] + ) import certifi @@ -39,5 +49,6 @@ def main(): os.chmod(openssl_cafile, STAT_0o775) print(" -- update complete") -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/docker/production/scripts/build_yml_files.py b/docker/production/scripts/build_yml_files.py index aef5e8c..0e1801e 100755 --- a/docker/production/scripts/build_yml_files.py +++ b/docker/production/scripts/build_yml_files.py @@ -9,91 +9,119 @@ Join docker compose fragments """ -__version__ = '0.1' +__version__ = "0.1" import os import argparse -def get_bkp_offset(filename, ext='yml'): +def get_bkp_offset(filename, ext="yml"): for i in range(1000): - if not os.path.exists(f'{filename}_bkp_{i}.{ext}'): + if not os.path.exists(f"{filename}_bkp_{i}.{ext}"): return i - raise ValueError(f'Unable to get backup filename for: {filename}.{ext}') + raise ValueError(f"Unable to get backup filename for: {filename}.{ext}") def main(): parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('-v', '--version', action='version', - version='%(prog)s {version}'.format(version=__version__)) - parser.add_argument('-c', '--coins', nargs='+', help=' Select coins', required=True) - parser.add_argument('--withscript', dest='withscript', help='Add container to run createoffers.py (default=false)', required=False, action='store_true') + parser.add_argument( + "-v", + "--version", + action="version", + version="%(prog)s {version}".format(version=__version__), + ) + parser.add_argument( + "-c", "--coins", nargs="+", help=" Select coins", required=True + ) + parser.add_argument( + "--withscript", + dest="withscript", + help="Add container to run createoffers.py (default=false)", + required=False, + action="store_true", + ) args = parser.parse_args() - with_coins = ['particl', ] + with_coins = [ + "particl", + ] for coin_name in args.coins: parsed_name = coin_name.lower() if parsed_name not in with_coins: with_coins.append(parsed_name) - print('Preparing docker compose files with coins:', ','.join(with_coins)) + print("Preparing docker compose files with coins:", ",".join(with_coins)) - num_docker_compose = get_bkp_offset('docker-compose') - num_docker_compose_prepare = get_bkp_offset('docker-compose-prepare') + num_docker_compose = get_bkp_offset("docker-compose") + num_docker_compose_prepare = get_bkp_offset("docker-compose-prepare") - if os.path.exists('docker-compose.yml'): - os.rename('docker-compose.yml', f'docker-compose_bkp_{num_docker_compose}.yml') - if os.path.exists('docker-compose-prepare.yml'): - os.rename('docker-compose-prepare.yml', f'docker-compose-prepare_bkp_{num_docker_compose_prepare}.yml') + if os.path.exists("docker-compose.yml"): + os.rename("docker-compose.yml", f"docker-compose_bkp_{num_docker_compose}.yml") + if os.path.exists("docker-compose-prepare.yml"): + os.rename( + "docker-compose-prepare.yml", + f"docker-compose-prepare_bkp_{num_docker_compose_prepare}.yml", + ) - fragments_dir = 'compose-fragments' - with open('docker-compose.yml', 'wb') as fp, open('docker-compose-prepare.yml', 'wb') as fpp: - with open(os.path.join(fragments_dir, '0_start.yml'), 'rb') as fp_in: + fragments_dir = "compose-fragments" + with ( + open("docker-compose.yml", "wb") as fp, + open("docker-compose-prepare.yml", "wb") as fpp, + ): + with open(os.path.join(fragments_dir, "0_start.yml"), "rb") as fp_in: for line in fp_in: fp.write(line) fpp.write(line) for coin_name in with_coins: - if coin_name == 'particl': + if coin_name == "particl": # Nothing to do continue - if coin_name in ('monero', 'wownero'): - with open(os.path.join(fragments_dir, '1_{coin_name}-wallet.yml'), 'rb') as fp_in: + if coin_name in ("monero", "wownero"): + with open( + os.path.join(fragments_dir, "1_{coin_name}-wallet.yml"), "rb" + ) as fp_in: for line in fp_in: fp.write(line) fpp.write(line) - with open(os.path.join(fragments_dir, '8_{coin_name}-daemon.yml'), 'rb') as fp_in: + with open( + os.path.join(fragments_dir, "8_{coin_name}-daemon.yml"), "rb" + ) as fp_in: for line in fp_in: fp.write(line) continue - if coin_name == 'decred': - with open(os.path.join(fragments_dir, '1_decred-wallet.yml'), 'rb') as fp_in: + if coin_name == "decred": + with open( + os.path.join(fragments_dir, "1_decred-wallet.yml"), "rb" + ) as fp_in: for line in fp_in: fp.write(line) fpp.write(line) - with open(os.path.join(fragments_dir, '8_decred-daemon.yml'), 'rb') as fp_in: + with open( + os.path.join(fragments_dir, "8_decred-daemon.yml"), "rb" + ) as fp_in: for line in fp_in: fp.write(line) continue - with open(os.path.join(fragments_dir, f'1_{coin_name}.yml'), 'rb') as fp_in: + with open(os.path.join(fragments_dir, f"1_{coin_name}.yml"), "rb") as fp_in: for line in fp_in: fp.write(line) fpp.write(line) - with open(os.path.join(fragments_dir, '8_swapclient.yml'), 'rb') as fp_in: + with open(os.path.join(fragments_dir, "8_swapclient.yml"), "rb") as fp_in: for line in fp_in: fp.write(line) if args.withscript: - with open(os.path.join(fragments_dir, '8_script.yml'), 'rb') as fp_in: + with open(os.path.join(fragments_dir, "8_script.yml"), "rb") as fp_in: for line in fp_in: fp.write(line) - with open(os.path.join(fragments_dir, '9_swapprepare.yml'), 'rb') as fp_in: + with open(os.path.join(fragments_dir, "9_swapprepare.yml"), "rb") as fp_in: for line in fp_in: fpp.write(line) - print('Done.') + print("Done.") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/scripts/createoffers.py b/scripts/createoffers.py index f71902a..1d94538 100755 --- a/scripts/createoffers.py +++ b/scripts/createoffers.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2023-2024 tecnovert +# Copyright (c) 2024 The Basicswap developers # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -57,7 +58,7 @@ Create offers """ -__version__ = '0.2' +__version__ = "0.2" import os import json @@ -73,16 +74,16 @@ from urllib.request import urlopen delay_event = threading.Event() -DEFAULT_CONFIG_FILE: str = 'createoffers.json' -DEFAULT_STATE_FILE: str = 'createoffers_state.json' +DEFAULT_CONFIG_FILE: str = "createoffers.json" +DEFAULT_STATE_FILE: str = "createoffers_state.json" def post_req(url: str, json_data=None): - req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'}) + req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) if json_data: - req.add_header('Content-Type', 'application/json; charset=utf-8') - post_bytes = json.dumps(json_data).encode('utf-8') - req.add_header('Content-Length', len(post_bytes)) + req.add_header("Content-Type", "application/json; charset=utf-8") + post_bytes = json.dumps(json_data).encode("utf-8") + req.add_header("Content-Length", len(post_bytes)) else: post_bytes = None return urlopen(req, data=post_bytes, timeout=300).read() @@ -94,28 +95,32 @@ def make_json_api_func(host: str, port: int): def api_func(path=None, json_data=None, timeout=300): nonlocal host, port - url = f'http://{host}:{port}/json' + url = f"http://{host}:{port}/json" if path is not None: - url += '/' + path + url += "/" + path if json_data is not None: return json.loads(post_req(url, json_data)) response = urlopen(url, timeout=300).read() return json.loads(response) + return api_func def signal_handler(sig, frame) -> None: - logging.info('Signal {} detected.'.format(sig)) + logging.info("Signal {} detected.".format(sig)) delay_event.set() def findCoin(coin: str, known_coins) -> str: for known_coin in known_coins: - if known_coin['name'].lower() == coin.lower() or known_coin['ticker'].lower() == coin.lower(): - if known_coin['active'] is False: - raise ValueError(f'Inactive coin {coin}') - return known_coin['name'] - raise ValueError(f'Unknown coin {coin}') + if ( + known_coin["name"].lower() == coin.lower() + or known_coin["ticker"].lower() == coin.lower() + ): + if known_coin["active"] is False: + raise ValueError(f"Inactive coin {coin}") + return known_coin["name"] + raise ValueError(f"Unknown coin {coin}") def readConfig(args, known_coins): @@ -124,126 +129,126 @@ def readConfig(args, known_coins): with open(config_path) as fs: config = json.load(fs) - if 'offers' not in config: - config['offers'] = [] - if 'bids' not in config: - config['bids'] = [] - if 'stealthex' not in config: - config['stealthex'] = [] + if "offers" not in config: + config["offers"] = [] + if "bids" not in config: + config["bids"] = [] + if "stealthex" not in config: + config["stealthex"] = [] - if 'min_seconds_between_offers' not in config: - config['min_seconds_between_offers'] = 60 - print('Set min_seconds_between_offers', config['min_seconds_between_offers']) + if "min_seconds_between_offers" not in config: + config["min_seconds_between_offers"] = 60 + print("Set min_seconds_between_offers", config["min_seconds_between_offers"]) num_changes += 1 - if 'max_seconds_between_offers' not in config: - config['max_seconds_between_offers'] = config['min_seconds_between_offers'] * 4 - print('Set max_seconds_between_offers', config['max_seconds_between_offers']) + if "max_seconds_between_offers" not in config: + config["max_seconds_between_offers"] = config["min_seconds_between_offers"] * 4 + print("Set max_seconds_between_offers", config["max_seconds_between_offers"]) num_changes += 1 - if 'min_seconds_between_bids' not in config: - config['min_seconds_between_bids'] = 60 - print('Set min_seconds_between_bids', config['min_seconds_between_bids']) + if "min_seconds_between_bids" not in config: + config["min_seconds_between_bids"] = 60 + print("Set min_seconds_between_bids", config["min_seconds_between_bids"]) num_changes += 1 - if 'max_seconds_between_bids' not in config: - config['max_seconds_between_bids'] = config['min_seconds_between_bids'] * 4 - print('Set max_seconds_between_bids', config['max_seconds_between_bids']) + if "max_seconds_between_bids" not in config: + config["max_seconds_between_bids"] = config["min_seconds_between_bids"] * 4 + print("Set max_seconds_between_bids", config["max_seconds_between_bids"]) num_changes += 1 - offer_templates = config['offers'] + offer_templates = config["offers"] offer_templates_map = {} num_enabled = 0 for i, offer_template in enumerate(offer_templates): - num_enabled += 1 if offer_template.get('enabled', True) else 0 - if 'name' not in offer_template: - print('Naming offer template', i) - offer_template['name'] = f'Offer {i}' + num_enabled += 1 if offer_template.get("enabled", True) else 0 + if "name" not in offer_template: + print("Naming offer template", i) + offer_template["name"] = f"Offer {i}" num_changes += 1 - if offer_template['name'] in offer_templates_map: - print('Renaming offer template', offer_template['name']) - original_name = offer_template['name'] + if offer_template["name"] in offer_templates_map: + print("Renaming offer template", offer_template["name"]) + original_name = offer_template["name"] offset = 2 - while f'{original_name}_{offset}' in offer_templates_map: + while f"{original_name}_{offset}" in offer_templates_map: offset += 1 - offer_template['name'] = f'{original_name}_{offset}' + offer_template["name"] = f"{original_name}_{offset}" num_changes += 1 - offer_templates_map[offer_template['name']] = offer_template + offer_templates_map[offer_template["name"]] = offer_template - if 'amount_step' not in offer_template: - if offer_template.get('min_coin_from_amt', 0) < offer_template['amount']: - print('Setting min_coin_from_amt for', offer_template['name']) - offer_template['min_coin_from_amt'] = offer_template['amount'] + if "amount_step" not in offer_template: + if offer_template.get("min_coin_from_amt", 0) < offer_template["amount"]: + print("Setting min_coin_from_amt for", offer_template["name"]) + offer_template["min_coin_from_amt"] = offer_template["amount"] num_changes += 1 else: - if 'min_coin_from_amt' not in offer_template: - print('Setting min_coin_from_amt for', offer_template['name']) - offer_template['min_coin_from_amt'] = 0 + if "min_coin_from_amt" not in offer_template: + print("Setting min_coin_from_amt for", offer_template["name"]) + offer_template["min_coin_from_amt"] = 0 num_changes += 1 - if 'address' not in offer_template: - print('Setting address to auto for offer', offer_template['name']) - offer_template['address'] = 'auto' + if "address" not in offer_template: + print("Setting address to auto for offer", offer_template["name"]) + offer_template["address"] = "auto" num_changes += 1 - if 'ratetweakpercent' not in offer_template: - print('Setting ratetweakpercent to 0 for offer', offer_template['name']) - offer_template['ratetweakpercent'] = 0 + if "ratetweakpercent" not in offer_template: + print("Setting ratetweakpercent to 0 for offer", offer_template["name"]) + offer_template["ratetweakpercent"] = 0 num_changes += 1 - if 'amount_variable' not in offer_template: - print('Setting amount_variable to True for offer', offer_template['name']) - offer_template['amount_variable'] = True + if "amount_variable" not in offer_template: + print("Setting amount_variable to True for offer", offer_template["name"]) + offer_template["amount_variable"] = True num_changes += 1 - if offer_template.get('enabled', True) is False: + if offer_template.get("enabled", True) is False: continue - offer_template['coin_from'] = findCoin(offer_template['coin_from'], known_coins) - offer_template['coin_to'] = findCoin(offer_template['coin_to'], known_coins) - config['num_enabled_offers'] = num_enabled + offer_template["coin_from"] = findCoin(offer_template["coin_from"], known_coins) + offer_template["coin_to"] = findCoin(offer_template["coin_to"], known_coins) + config["num_enabled_offers"] = num_enabled - bid_templates = config['bids'] + bid_templates = config["bids"] bid_templates_map = {} num_enabled = 0 for i, bid_template in enumerate(bid_templates): - num_enabled += 1 if bid_template.get('enabled', True) else 0 - if 'name' not in bid_template: - print('Naming bid template', i) - bid_template['name'] = f'Bid {i}' + num_enabled += 1 if bid_template.get("enabled", True) else 0 + if "name" not in bid_template: + print("Naming bid template", i) + bid_template["name"] = f"Bid {i}" num_changes += 1 - if bid_template['name'] in bid_templates_map: - print('Renaming bid template', bid_template['name']) - original_name = bid_template['name'] + if bid_template["name"] in bid_templates_map: + print("Renaming bid template", bid_template["name"]) + original_name = bid_template["name"] offset = 2 - while f'{original_name}_{offset}' in bid_templates_map: + while f"{original_name}_{offset}" in bid_templates_map: offset += 1 - bid_template['name'] = f'{original_name}_{offset}' + bid_template["name"] = f"{original_name}_{offset}" num_changes += 1 - bid_templates_map[bid_template['name']] = bid_template + bid_templates_map[bid_template["name"]] = bid_template - if bid_template.get('min_swap_amount', 0.0) < 0.00001: - print('Setting min_swap_amount for bid template', bid_template['name']) - bid_template['min_swap_amount'] = 0.00001 + if bid_template.get("min_swap_amount", 0.0) < 0.00001: + print("Setting min_swap_amount for bid template", bid_template["name"]) + bid_template["min_swap_amount"] = 0.00001 - if 'address' not in bid_template: - print('Setting address to auto for bid', bid_template['name']) - bid_template['address'] = 'auto' + if "address" not in bid_template: + print("Setting address to auto for bid", bid_template["name"]) + bid_template["address"] = "auto" num_changes += 1 - if bid_template.get('enabled', True) is False: + if bid_template.get("enabled", True) is False: continue - bid_template['coin_from'] = findCoin(bid_template['coin_from'], known_coins) - bid_template['coin_to'] = findCoin(bid_template['coin_to'], known_coins) - config['num_enabled_bids'] = num_enabled + bid_template["coin_from"] = findCoin(bid_template["coin_from"], known_coins) + bid_template["coin_to"] = findCoin(bid_template["coin_to"], known_coins) + config["num_enabled_bids"] = num_enabled num_enabled = 0 - stealthex_swaps = config['stealthex'] + stealthex_swaps = config["stealthex"] for i, swap in enumerate(stealthex_swaps): - num_enabled += 1 if swap.get('enabled', True) else 0 - if swap.get('enabled', True) is False: + num_enabled += 1 if swap.get("enabled", True) else 0 + if swap.get("enabled", True) is False: continue - swap['coin_from'] = findCoin(swap['coin_from'], known_coins) - config['num_enabled_swaps'] = num_enabled + swap["coin_from"] = findCoin(swap["coin_from"], known_coins) + config["num_enabled_swaps"] = num_enabled if num_changes > 0: - shutil.copyfile(config_path, config_path + '.last') - with open(config_path, 'w') as fp: + shutil.copyfile(config_path, config_path + ".last") + with open(config_path, "w") as fp: json.dump(config, fp, indent=4) return config @@ -251,21 +256,65 @@ def readConfig(args, known_coins): def write_state(statefile, script_state): if os.path.exists(statefile): - shutil.copyfile(statefile, statefile + '.last') - with open(statefile, 'w') as fp: + shutil.copyfile(statefile, statefile + ".last") + with open(statefile, "w") as fp: json.dump(script_state, fp, indent=4) def main(): parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('-v', '--version', action='version', - version='%(prog)s {version}'.format(version=__version__)) - parser.add_argument('--host', dest='host', help='RPC host (default=127.0.0.1)', type=str, default='127.0.0.1', required=False) - parser.add_argument('--port', dest='port', help='RPC port (default=12700)', type=int, default=12700, required=False) - parser.add_argument('--oneshot', dest='oneshot', help='Exit after one iteration (default=false)', required=False, action='store_true') - parser.add_argument('--debug', dest='debug', help='Print extra debug messages (default=false)', required=False, action='store_true') - parser.add_argument('--configfile', dest='configfile', help=f'config file path (default={DEFAULT_CONFIG_FILE})', type=str, default=DEFAULT_CONFIG_FILE, required=False) - parser.add_argument('--statefile', dest='statefile', help=f'state file path (default={DEFAULT_STATE_FILE})', type=str, default=DEFAULT_STATE_FILE, required=False) + parser.add_argument( + "-v", + "--version", + action="version", + version="%(prog)s {version}".format(version=__version__), + ) + parser.add_argument( + "--host", + dest="host", + help="RPC host (default=127.0.0.1)", + type=str, + default="127.0.0.1", + required=False, + ) + parser.add_argument( + "--port", + dest="port", + help="RPC port (default=12700)", + type=int, + default=12700, + required=False, + ) + parser.add_argument( + "--oneshot", + dest="oneshot", + help="Exit after one iteration (default=false)", + required=False, + action="store_true", + ) + parser.add_argument( + "--debug", + dest="debug", + help="Print extra debug messages (default=false)", + required=False, + action="store_true", + ) + parser.add_argument( + "--configfile", + dest="configfile", + help=f"config file path (default={DEFAULT_CONFIG_FILE})", + type=str, + default=DEFAULT_CONFIG_FILE, + required=False, + ) + parser.add_argument( + "--statefile", + dest="statefile", + help=f"state file path (default={DEFAULT_STATE_FILE})", + type=str, + default=DEFAULT_STATE_FILE, + required=False, + ) args = parser.parse_args() read_json_api = make_json_api_func(args.host, args.port) @@ -273,10 +322,10 @@ def main(): if not os.path.exists(args.configfile): raise ValueError(f'Config file "{args.configfile}" not found.') - known_coins = read_json_api('coins') + known_coins = read_json_api("coins") coins_map = {} for known_coin in known_coins: - coins_map[known_coin['name']] = known_coin + coins_map[known_coin["name"]] = known_coin script_state = {} if os.path.exists(args.statefile): @@ -287,71 +336,101 @@ def main(): while not delay_event.is_set(): # Read config each iteration so they can be modified without restarting config = readConfig(args, known_coins) - offer_templates = config['offers'] + offer_templates = config["offers"] random.shuffle(offer_templates) - bid_templates = config['bids'] + bid_templates = config["bids"] random.shuffle(bid_templates) - stealthex_swaps = config['stealthex'] + stealthex_swaps = config["stealthex"] random.shuffle(bid_templates) # override wallet api calls for testing - if 'wallet_port_override' in config: - wallet_api_port = int(config['wallet_port_override']) - print(f'Overriding wallet api port: {wallet_api_port}') + if "wallet_port_override" in config: + wallet_api_port = int(config["wallet_port_override"]) + print(f"Overriding wallet api port: {wallet_api_port}") read_json_api_wallet = make_json_api_func(args.host, wallet_api_port) else: read_json_api_wallet = read_json_api try: - sent_offers = read_json_api('sentoffers', {'active': 'active'}) + sent_offers = read_json_api("sentoffers", {"active": "active"}) if args.debug and len(offer_templates) > 0: - print('Processing {} offer template{}'.format(config['num_enabled_offers'], 's' if config['num_enabled_offers'] != 1 else '')) + print( + "Processing {} offer template{}".format( + config["num_enabled_offers"], + "s" if config["num_enabled_offers"] != 1 else "", + ) + ) for offer_template in offer_templates: - if offer_template.get('enabled', True) is False: + if offer_template.get("enabled", True) is False: continue offers_found = 0 - coin_from_data = coins_map[offer_template['coin_from']] - coin_to_data = coins_map[offer_template['coin_to']] + coin_from_data = coins_map[offer_template["coin_from"]] + coin_to_data = coins_map[offer_template["coin_to"]] - wallet_from = read_json_api_wallet('wallets/{}'.format(coin_from_data['ticker'])) + wallet_from = read_json_api_wallet( + "wallets/{}".format(coin_from_data["ticker"]) + ) for offer in sent_offers: - created_offers = script_state.get('offers', {}) - prev_template_offers = created_offers.get(offer_template['name'], {}) + created_offers = script_state.get("offers", {}) + prev_template_offers = created_offers.get( + offer_template["name"], {} + ) - if next((x for x in prev_template_offers if x['offer_id'] == offer['offer_id']), None): + if next( + ( + x + for x in prev_template_offers + if x["offer_id"] == offer["offer_id"] + ), + None, + ): offers_found += 1 - if float(wallet_from['balance']) <= float(offer_template['min_coin_from_amt']): - offer_id = offer['offer_id'] - print('Revoking offer {}, wallet from balance below minimum'.format(offer_id)) - result = read_json_api(f'revokeoffer/{offer_id}') - print('revokeoffer', result) + if float(wallet_from["balance"]) <= float( + offer_template["min_coin_from_amt"] + ): + offer_id = offer["offer_id"] + print( + "Revoking offer {}, wallet from balance below minimum".format( + offer_id + ) + ) + result = read_json_api(f"revokeoffer/{offer_id}") + print("revokeoffer", result) if offers_found > 0: continue - max_offer_amount: float = offer_template['amount'] - min_offer_amount: float = offer_template.get('amount_step', max_offer_amount) - wallet_balance: float = float(wallet_from['balance']) - min_wallet_from_amount: float = float(offer_template['min_coin_from_amt']) + max_offer_amount: float = offer_template["amount"] + min_offer_amount: float = offer_template.get( + "amount_step", max_offer_amount + ) + wallet_balance: float = float(wallet_from["balance"]) + min_wallet_from_amount: float = float( + offer_template["min_coin_from_amt"] + ) if wallet_balance - min_offer_amount <= min_wallet_from_amount: - print('Skipping template {}, wallet from balance below minimum'.format(offer_template['name'])) + print( + "Skipping template {}, wallet from balance below minimum".format( + offer_template["name"] + ) + ) continue offer_amount: float = max_offer_amount if wallet_balance - max_offer_amount <= min_wallet_from_amount: available_balance: float = wallet_balance - min_wallet_from_amount min_steps: int = available_balance // min_offer_amount - assert (min_steps > 0) # Should not be possible, checked above + assert min_steps > 0 # Should not be possible, checked above offer_amount = min_offer_amount * min_steps - delay_next_offer_before = script_state.get('delay_next_offer_before', 0) + delay_next_offer_before = script_state.get("delay_next_offer_before", 0) if delay_next_offer_before > int(time.time()): - print('Delaying offers until {}'.format(delay_next_offer_before)) + print("Delaying offers until {}".format(delay_next_offer_before)) break """ @@ -361,148 +440,193 @@ def main(): TODO - adjust rates based on existing offers """ - rates = read_json_api('rates', {'coin_from': coin_from_data['id'], 'coin_to': coin_to_data['id']}) - print('Rates', rates) - coingecko_rate = float(rates['coingecko']['rate_inferred']) + rates = read_json_api( + "rates", + {"coin_from": coin_from_data["id"], "coin_to": coin_to_data["id"]}, + ) + print("Rates", rates) + coingecko_rate = float(rates["coingecko"]["rate_inferred"]) use_rate = coingecko_rate - if offer_template['ratetweakpercent'] != 0: - print('Adjusting rate {} by {}%.'.format(use_rate, offer_template['ratetweakpercent'])) - tweak = offer_template['ratetweakpercent'] / 100.0 + if offer_template["ratetweakpercent"] != 0: + print( + "Adjusting rate {} by {}%.".format( + use_rate, offer_template["ratetweakpercent"] + ) + ) + tweak = offer_template["ratetweakpercent"] / 100.0 use_rate += use_rate * tweak - if use_rate < offer_template['minrate']: - print('Warning: Clamping rate to minimum.') - use_rate = offer_template['minrate'] + if use_rate < offer_template["minrate"]: + print("Warning: Clamping rate to minimum.") + use_rate = offer_template["minrate"] - print('Creating offer for: {} at rate: {}'.format(offer_template, use_rate)) - template_from_addr = offer_template['address'] + print( + "Creating offer for: {} at rate: {}".format( + offer_template, use_rate + ) + ) + template_from_addr = offer_template["address"] offer_data = { - 'addr_from': -1 if template_from_addr == 'auto' else template_from_addr, - 'coin_from': coin_from_data['ticker'], - 'coin_to': coin_to_data['ticker'], - 'amt_from': offer_amount, - 'amt_var': offer_template['amount_variable'], - 'valid_for_seconds': offer_template.get('offer_valid_seconds', config.get('offer_valid_seconds', 3600)), - 'rate': use_rate, - 'swap_type': offer_template.get('swap_type', 'adaptor_sig'), - 'lockhrs': '24', - 'automation_strat_id': 1} - if 'min_swap_amount' in offer_template: - offer_data['amt_bid_min'] = offer_template['min_swap_amount'] + "addr_from": ( + -1 if template_from_addr == "auto" else template_from_addr + ), + "coin_from": coin_from_data["ticker"], + "coin_to": coin_to_data["ticker"], + "amt_from": offer_amount, + "amt_var": offer_template["amount_variable"], + "valid_for_seconds": offer_template.get( + "offer_valid_seconds", config.get("offer_valid_seconds", 3600) + ), + "rate": use_rate, + "swap_type": offer_template.get("swap_type", "adaptor_sig"), + "lockhrs": "24", + "automation_strat_id": 1, + } + if "min_swap_amount" in offer_template: + offer_data["amt_bid_min"] = offer_template["min_swap_amount"] if args.debug: - print('offer data {}'.format(offer_data)) - new_offer = read_json_api('offers/new', offer_data) - if 'error' in new_offer: - raise ValueError('Server failed to create offer: {}'.format(new_offer['error'])) - print('New offer: {}'.format(new_offer['offer_id'])) - if 'offers' not in script_state: - script_state['offers'] = {} - template_name = offer_template['name'] - if template_name not in script_state['offers']: - script_state['offers'][template_name] = [] - script_state['offers'][template_name].append({'offer_id': new_offer['offer_id'], 'time': int(time.time())}) - max_seconds_between_offers = config['max_seconds_between_offers'] - min_seconds_between_offers = config['min_seconds_between_offers'] + print("offer data {}".format(offer_data)) + new_offer = read_json_api("offers/new", offer_data) + if "error" in new_offer: + raise ValueError( + "Server failed to create offer: {}".format(new_offer["error"]) + ) + print("New offer: {}".format(new_offer["offer_id"])) + if "offers" not in script_state: + script_state["offers"] = {} + template_name = offer_template["name"] + if template_name not in script_state["offers"]: + script_state["offers"][template_name] = [] + script_state["offers"][template_name].append( + {"offer_id": new_offer["offer_id"], "time": int(time.time())} + ) + max_seconds_between_offers = config["max_seconds_between_offers"] + min_seconds_between_offers = config["min_seconds_between_offers"] time_between_offers = min_seconds_between_offers if max_seconds_between_offers > min_seconds_between_offers: - time_between_offers = random.randint(min_seconds_between_offers, max_seconds_between_offers) + time_between_offers = random.randint( + min_seconds_between_offers, max_seconds_between_offers + ) - script_state['delay_next_offer_before'] = int(time.time()) + time_between_offers + script_state["delay_next_offer_before"] = ( + int(time.time()) + time_between_offers + ) write_state(args.statefile, script_state) if args.debug and len(bid_templates) > 0: - print('Processing {} bid template{}'.format(config['num_enabled_bids'], 's' if config['num_enabled_bids'] != 1 else '')) + print( + "Processing {} bid template{}".format( + config["num_enabled_bids"], + "s" if config["num_enabled_bids"] != 1 else "", + ) + ) for bid_template in bid_templates: - if bid_template.get('enabled', True) is False: + if bid_template.get("enabled", True) is False: continue - delay_next_bid_before = script_state.get('delay_next_bid_before', 0) + delay_next_bid_before = script_state.get("delay_next_bid_before", 0) if delay_next_bid_before > int(time.time()): - print('Delaying bids until {}'.format(delay_next_bid_before)) + print("Delaying bids until {}".format(delay_next_bid_before)) break # Check bids in progress - max_concurrent = bid_template.get('max_concurrent', 1) - if 'bids' not in script_state: - script_state['bids'] = {} - template_name = bid_template['name'] - if template_name not in script_state['bids']: - script_state['bids'][template_name] = [] - previous_bids = script_state['bids'][template_name] + max_concurrent = bid_template.get("max_concurrent", 1) + if "bids" not in script_state: + script_state["bids"] = {} + template_name = bid_template["name"] + if template_name not in script_state["bids"]: + script_state["bids"][template_name] = [] + previous_bids = script_state["bids"][template_name] bids_in_progress: int = 0 for previous_bid in previous_bids: - if not previous_bid['active']: + if not previous_bid["active"]: continue - previous_bid_id = previous_bid['bid_id'] - previous_bid_info = read_json_api(f'bids/{previous_bid_id}') - bid_state = previous_bid_info['bid_state'] - if bid_state in ('Completed', 'Timed-out', 'Abandoned', 'Error', 'Rejected'): - print(f'Marking bid inactive {previous_bid_id}, state {bid_state}') - previous_bid['active'] = False + previous_bid_id = previous_bid["bid_id"] + previous_bid_info = read_json_api(f"bids/{previous_bid_id}") + bid_state = previous_bid_info["bid_state"] + if bid_state in ( + "Completed", + "Timed-out", + "Abandoned", + "Error", + "Rejected", + ): + print( + f"Marking bid inactive {previous_bid_id}, state {bid_state}" + ) + previous_bid["active"] = False write_state(args.statefile, script_state) continue - if bid_state in ('Sent', 'Received') and previous_bid_info['expired_at'] < int(time.time()): - print(f'Marking bid inactive {previous_bid_id}, expired') - previous_bid['active'] = False + if bid_state in ("Sent", "Received") and previous_bid_info[ + "expired_at" + ] < int(time.time()): + print(f"Marking bid inactive {previous_bid_id}, expired") + previous_bid["active"] = False write_state(args.statefile, script_state) continue bids_in_progress += 1 if bids_in_progress >= max_concurrent: - print('Max concurrent bids reached for template') + print("Max concurrent bids reached for template") continue # Bidder sends coin_to and receives coin_from - coin_from_data = coins_map[bid_template['coin_from']] - coin_to_data = coins_map[bid_template['coin_to']] + coin_from_data = coins_map[bid_template["coin_from"]] + coin_to_data = coins_map[bid_template["coin_to"]] page_limit: int = 25 offers_options = { - 'active': 'active', - 'include_sent': False, - 'coin_from': coin_from_data['id'], - 'coin_to': coin_to_data['id'], - 'with_extra_info': True, - 'sort_by': 'rate', - 'sort_dir': 'asc', - 'offset': 0, - 'limit': page_limit, + "active": "active", + "include_sent": False, + "coin_from": coin_from_data["id"], + "coin_to": coin_to_data["id"], + "with_extra_info": True, + "sort_by": "rate", + "sort_dir": "asc", + "offset": 0, + "limit": page_limit, } received_offers = [] for i in range(1000000): # for i in itertools.count() - page_offers = read_json_api('offers', offers_options) + page_offers = read_json_api("offers", offers_options) if len(page_offers) < 1: break received_offers += page_offers - offers_options['offset'] = offers_options['offset'] + page_limit + offers_options["offset"] = offers_options["offset"] + page_limit if i > 100: - print(f'Warning: Broke offers loop at: {i}') + print(f"Warning: Broke offers loop at: {i}") break if args.debug: - print('Received Offers', received_offers) + print("Received Offers", received_offers) for offer in received_offers: - offer_id = offer['offer_id'] - offer_amount = float(offer['amount_from']) - offer_rate = float(offer['rate']) - bid_amount = bid_template['amount'] + offer_id = offer["offer_id"] + offer_amount = float(offer["amount_from"]) + offer_rate = float(offer["rate"]) + bid_amount = bid_template["amount"] - min_swap_amount = bid_template.get('min_swap_amount', 0.01) # TODO: Make default vary per coin - can_adjust_offer_amount: bool = offer['amount_negotiable'] - can_adjust_bid_amount: bool = bid_template.get('amount_variable', True) - can_adjust_amount: bool = can_adjust_offer_amount and can_adjust_bid_amount + min_swap_amount = bid_template.get( + "min_swap_amount", 0.01 + ) # TODO: Make default vary per coin + can_adjust_offer_amount: bool = offer["amount_negotiable"] + can_adjust_bid_amount: bool = bid_template.get( + "amount_variable", True + ) + can_adjust_amount: bool = ( + can_adjust_offer_amount and can_adjust_bid_amount + ) if offer_amount < min_swap_amount: if args.debug: - print(f'Offer amount below min swap amount bid {offer_id}') + print(f"Offer amount below min swap amount bid {offer_id}") continue if can_adjust_offer_amount is False and offer_amount > bid_amount: if args.debug: - print(f'Bid amount too low for offer {offer_id}') + print(f"Bid amount too low for offer {offer_id}") continue if bid_amount > offer_amount: @@ -510,195 +634,295 @@ def main(): bid_amount = offer_amount else: if args.debug: - print(f'Bid amount too high for offer {offer_id}') + print(f"Bid amount too high for offer {offer_id}") continue - if offer_rate > bid_template['maxrate']: + if offer_rate > bid_template["maxrate"]: if args.debug: - print(f'Bid rate too low for offer {offer_id}') + print(f"Bid rate too low for offer {offer_id}") continue - sent_bids = read_json_api('sentbids', {'offer_id': offer['offer_id'], 'with_available_or_active': True}) + sent_bids = read_json_api( + "sentbids", + { + "offer_id": offer["offer_id"], + "with_available_or_active": True, + }, + ) if len(sent_bids) > 0: if args.debug: - print(f'Already bidding on offer {offer_id}') + print(f"Already bidding on offer {offer_id}") continue - offer_identity = read_json_api('identities/{}'.format(offer['addr_from'])) + offer_identity = read_json_api( + "identities/{}".format(offer["addr_from"]) + ) if len(offer_identity) > 0: id_offer_from = offer_identity[0] - automation_override = id_offer_from['automation_override'] + automation_override = id_offer_from["automation_override"] if automation_override == 2: if args.debug: - print(f'Not bidding on offer {offer_id}, automation_override ({automation_override}).') + print( + f"Not bidding on offer {offer_id}, automation_override ({automation_override})." + ) continue if automation_override == 1: if args.debug: - print('Offer address from {}, set to always accept.'.format(offer['addr_from'])) + print( + "Offer address from {}, set to always accept.".format( + offer["addr_from"] + ) + ) else: - successful_sent_bids = id_offer_from['num_sent_bids_successful'] - failed_sent_bids = id_offer_from['num_sent_bids_failed'] - if failed_sent_bids > 3 and failed_sent_bids > successful_sent_bids: + successful_sent_bids = id_offer_from[ + "num_sent_bids_successful" + ] + failed_sent_bids = id_offer_from["num_sent_bids_failed"] + if ( + failed_sent_bids > 3 + and failed_sent_bids > successful_sent_bids + ): if args.debug: - print(f'Not bidding on offer {offer_id}, too many failed bids ({failed_sent_bids}).') + print( + f"Not bidding on offer {offer_id}, too many failed bids ({failed_sent_bids})." + ) continue validateamount: bool = False - max_coin_from_balance = bid_template.get('max_coin_from_balance', -1) + max_coin_from_balance = bid_template.get( + "max_coin_from_balance", -1 + ) if max_coin_from_balance > 0: - wallet_from = read_json_api_wallet('wallets/{}'.format(coin_from_data['ticker'])) - total_balance_from = float(wallet_from['balance']) + float(wallet_from['unconfirmed']) + wallet_from = read_json_api_wallet( + "wallets/{}".format(coin_from_data["ticker"]) + ) + total_balance_from = float(wallet_from["balance"]) + float( + wallet_from["unconfirmed"] + ) if args.debug: - print(f'Total coin from balance {total_balance_from}') + print(f"Total coin from balance {total_balance_from}") if total_balance_from + bid_amount > max_coin_from_balance: - if can_adjust_amount and max_coin_from_balance - total_balance_from > min_swap_amount: + if ( + can_adjust_amount + and max_coin_from_balance - total_balance_from + > min_swap_amount + ): bid_amount = max_coin_from_balance - total_balance_from validateamount = True - print(f'Reduced bid amount to {bid_amount}') + print(f"Reduced bid amount to {bid_amount}") else: if args.debug: - print(f'Bid amount would exceed maximum wallet total for offer {offer_id}') + print( + f"Bid amount would exceed maximum wallet total for offer {offer_id}" + ) continue - min_coin_to_balance = bid_template['min_coin_to_balance'] + min_coin_to_balance = bid_template["min_coin_to_balance"] if min_coin_to_balance > 0: - wallet_to = read_json_api_wallet('wallets/{}'.format(coin_to_data['ticker'])) + wallet_to = read_json_api_wallet( + "wallets/{}".format(coin_to_data["ticker"]) + ) - total_balance_to = float(wallet_to['balance']) + float(wallet_to['unconfirmed']) + total_balance_to = float(wallet_to["balance"]) + float( + wallet_to["unconfirmed"] + ) if args.debug: - print(f'Total coin to balance {total_balance_to}') + print(f"Total coin to balance {total_balance_to}") swap_amount_to = bid_amount * offer_rate if total_balance_to - swap_amount_to < min_coin_to_balance: if can_adjust_amount: - adjusted_swap_amount_to = total_balance_to - min_coin_to_balance - adjusted_bid_amount = adjusted_swap_amount_to / offer_rate + adjusted_swap_amount_to = ( + total_balance_to - min_coin_to_balance + ) + adjusted_bid_amount = ( + adjusted_swap_amount_to / offer_rate + ) if adjusted_bid_amount > min_swap_amount: bid_amount = adjusted_bid_amount validateamount = True - print(f'Reduced bid amount to {bid_amount}') + print(f"Reduced bid amount to {bid_amount}") swap_amount_to = adjusted_bid_amount * offer_rate if total_balance_to - swap_amount_to < min_coin_to_balance: if args.debug: - print(f'Bid amount would exceed minimum coin to wallet total for offer {offer_id}') + print( + f"Bid amount would exceed minimum coin to wallet total for offer {offer_id}" + ) continue if validateamount: - bid_amount = read_json_api('validateamount', {'coin': coin_from_data['ticker'], 'amount': bid_amount, 'method': 'rounddown'}) + bid_amount = read_json_api( + "validateamount", + { + "coin": coin_from_data["ticker"], + "amount": bid_amount, + "method": "rounddown", + }, + ) bid_data = { - 'offer_id': offer['offer_id'], - 'amount_from': bid_amount} + "offer_id": offer["offer_id"], + "amount_from": bid_amount, + } - if 'address' in bid_template: - addr_from = bid_template['address'] - if addr_from != -1 and addr_from != 'auto': - bid_data['addr_from'] = addr_from + if "address" in bid_template: + addr_from = bid_template["address"] + if addr_from != -1 and addr_from != "auto": + bid_data["addr_from"] = addr_from - if config.get('test_mode', False): - print('Would create bid: {}'.format(bid_data)) - bid_id = 'simulated' + if config.get("test_mode", False): + print("Would create bid: {}".format(bid_data)) + bid_id = "simulated" else: if args.debug: - print('Creating bid: {}'.format(bid_data)) - new_bid = read_json_api('bids/new', bid_data) - if 'error' in new_bid: - raise ValueError('Server failed to create bid: {}'.format(new_bid['error'])) - print('New bid: {} on offer {}'.format(new_bid['bid_id'], offer['offer_id'])) - bid_id = new_bid['bid_id'] + print("Creating bid: {}".format(bid_data)) + new_bid = read_json_api("bids/new", bid_data) + if "error" in new_bid: + raise ValueError( + "Server failed to create bid: {}".format( + new_bid["error"] + ) + ) + print( + "New bid: {} on offer {}".format( + new_bid["bid_id"], offer["offer_id"] + ) + ) + bid_id = new_bid["bid_id"] - script_state['bids'][template_name].append({'bid_id': bid_id, 'time': int(time.time()), 'active': True}) + script_state["bids"][template_name].append( + {"bid_id": bid_id, "time": int(time.time()), "active": True} + ) - max_seconds_between_bids = config['max_seconds_between_bids'] - min_seconds_between_bids = config['min_seconds_between_bids'] + max_seconds_between_bids = config["max_seconds_between_bids"] + min_seconds_between_bids = config["min_seconds_between_bids"] if max_seconds_between_bids > min_seconds_between_bids: - time_between_bids = random.randint(min_seconds_between_bids, max_seconds_between_bids) + time_between_bids = random.randint( + min_seconds_between_bids, max_seconds_between_bids + ) else: time_between_bids = min_seconds_between_bids - script_state['delay_next_bid_before'] = int(time.time()) + time_between_bids + script_state["delay_next_bid_before"] = ( + int(time.time()) + time_between_bids + ) write_state(args.statefile, script_state) break # Create max one bid per iteration if args.debug and len(stealthex_swaps) > 0: - print('Processing {} stealthex template{}'.format(config['num_enabled_swaps'], 's' if config['num_enabled_swaps'] != 1 else '')) + print( + "Processing {} stealthex template{}".format( + config["num_enabled_swaps"], + "s" if config["num_enabled_swaps"] != 1 else "", + ) + ) for stealthex_swap in stealthex_swaps: - if stealthex_swap.get('enabled', True) is False: + if stealthex_swap.get("enabled", True) is False: continue - coin_from_data = coins_map[stealthex_swap['coin_from']] + coin_from_data = coins_map[stealthex_swap["coin_from"]] - wallet_from = read_json_api_wallet('wallets/{}'.format(coin_from_data['ticker'])) + wallet_from = read_json_api_wallet( + "wallets/{}".format(coin_from_data["ticker"]) + ) - current_balance = float(wallet_from['balance']) + current_balance = float(wallet_from["balance"]) - min_balance_from = float(stealthex_swap['min_balance_from']) - min_swap_amount = float(stealthex_swap['min_amount_tx']) - max_swap_amount = float(stealthex_swap['max_amount_tx']) + min_balance_from = float(stealthex_swap["min_balance_from"]) + min_swap_amount = float(stealthex_swap["min_amount_tx"]) + max_swap_amount = float(stealthex_swap["max_amount_tx"]) # TODO: Check range limits if current_balance >= min_balance_from + min_swap_amount: swap_amount = max_swap_amount if current_balance - swap_amount < min_balance_from: - swap_amount = max(min_swap_amount, current_balance - min_balance_from) + swap_amount = max( + min_swap_amount, current_balance - min_balance_from + ) - estimate_url = 'https://api.stealthex.io/api/v2/estimate/{}/{}?amount={}&api_key={}&fixed=true'.format(coin_from_data['ticker'].lower(), stealthex_swap['coin_to'].lower(), swap_amount, stealthex_swap['api_key']) + estimate_url = "https://api.stealthex.io/api/v2/estimate/{}/{}?amount={}&api_key={}&fixed=true".format( + coin_from_data["ticker"].lower(), + stealthex_swap["coin_to"].lower(), + swap_amount, + stealthex_swap["api_key"], + ) if args.debug: - print(f'Estimate URL: {estimate_url}') + print(f"Estimate URL: {estimate_url}") estimate_response = json.loads(post_req(estimate_url)) - amount_to = float(estimate_response['estimated_amount']) + amount_to = float(estimate_response["estimated_amount"]) rate = swap_amount / amount_to - min_rate = float(stealthex_swap['min_rate']) + min_rate = float(stealthex_swap["min_rate"]) if rate < min_rate: if args.debug: - print('Stealthex rate {} below minimum {} for {} to {}'.format(rate, min_rate, coin_from_data['ticker'], stealthex_swap['coin_to'])) + print( + "Stealthex rate {} below minimum {} for {} to {}".format( + rate, + min_rate, + coin_from_data["ticker"], + stealthex_swap["coin_to"], + ) + ) continue - exchange_url = 'https://api.stealthex.io/api/v2/exchange?api_key={}'.format(stealthex_swap['api_key']) + exchange_url = ( + "https://api.stealthex.io/api/v2/exchange?api_key={}".format( + stealthex_swap["api_key"] + ) + ) - address_to = stealthex_swap.get('receive_address', 'auto') - if address_to == 'auto': - address_to = read_json_api('wallets/{}/nextdepositaddr'.format(stealthex_swap['coin_to'])) + address_to = stealthex_swap.get("receive_address", "auto") + if address_to == "auto": + address_to = read_json_api( + "wallets/{}/nextdepositaddr".format( + stealthex_swap["coin_to"] + ) + ) - address_refund = stealthex_swap.get('refund_address', 'auto') - if address_refund == 'auto': - address_refund = read_json_api('wallets/{}/nextdepositaddr'.format(coin_from_data['ticker'])) + address_refund = stealthex_swap.get("refund_address", "auto") + if address_refund == "auto": + address_refund = read_json_api( + "wallets/{}/nextdepositaddr".format( + coin_from_data["ticker"] + ) + ) exchange_data = { - 'currency_from': coin_from_data['ticker'].lower(), - 'currency_to': stealthex_swap['coin_to'].lower(), - 'address_to': address_to, - 'amount_from': swap_amount, - 'fixed': True, + "currency_from": coin_from_data["ticker"].lower(), + "currency_to": stealthex_swap["coin_to"].lower(), + "address_to": address_to, + "amount_from": swap_amount, + "fixed": True, # 'extra_id_to': # 'referral': - 'refund_address': address_refund, + "refund_address": address_refund, # 'refund_extra_id': - 'rate_id': estimate_response['rate_id'], + "rate_id": estimate_response["rate_id"], } if args.debug: - print(f'Exchange URL: {estimate_url}') - print(f'Exchange data: {exchange_data}') + print(f"Exchange URL: {estimate_url}") + print(f"Exchange data: {exchange_data}") - exchange_response = json.loads(post_req(exchange_url, exchange_data)) + exchange_response = json.loads( + post_req(exchange_url, exchange_data) + ) - if 'Error' in estimate_response: - raise ValueError('Exchange error ' + estimate_response) + if "Error" in exchange_response: + raise ValueError("Exchange error " + exchange_response) - raise ValueError('TODO') + raise ValueError("TODO") except Exception as e: - print(f'Error: {e}.') + print(f"Error: {e}.") if args.oneshot: break - print('Looping indefinitely, ctrl+c to exit.') + print("Looping indefinitely, ctrl+c to exit.") delay_event.wait(60) - print('Done.') + print("Done.") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/tests/basicswap/common.py b/tests/basicswap/common.py index 3068f6e..ab1fc8f 100644 --- a/tests/basicswap/common.py +++ b/tests/basicswap/common.py @@ -125,7 +125,7 @@ def checkForks(ro): assert ro["softforks"]["csv"]["active"] assert ro["softforks"]["segwit"]["active"] except Exception as e: - logging.warning("Could not parse deployment info") + logging.warning(f"Could not parse deployment info: {e}") def stopDaemons(daemons):