From 38fa498b0bdf9e429caae317f22bc90180bec0b6 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Fri, 29 Dec 2023 15:36:00 +0200 Subject: [PATCH] coins: Add LTC MWEB wallet --- basicswap/__init__.py | 2 +- basicswap/basicswap.py | 130 ++++++++++++---- basicswap/chainparams.py | 1 + basicswap/http_server.py | 14 +- basicswap/interface/btc.py | 149 ++++++++++-------- basicswap/interface/dash.py | 10 +- basicswap/interface/firo.py | 71 +++++---- basicswap/interface/ltc.py | 106 ++++++++++++- basicswap/interface/nav.py | 73 +++++---- basicswap/interface/nmc.py | 2 +- basicswap/interface/part.py | 150 +++++++++--------- basicswap/interface/pivx.py | 31 ++-- basicswap/interface/xmr.py | 91 +++++------ basicswap/js_server.py | 24 ++- basicswap/templates/style.html | 12 ++ basicswap/templates/wallet.html | 96 +++++++----- basicswap/templates/wallets.html | 29 +++- basicswap/ui/page_wallet.py | 32 ++-- basicswap/ui/util.py | 7 + bin/basicswap_prepare.py | 24 ++- doc/release-notes.md | 8 + tests/basicswap/extended/test_dash.py | 2 +- tests/basicswap/extended/test_nav.py | 2 +- tests/basicswap/extended/test_pivx.py | 2 +- tests/basicswap/test_btc_xmr.py | 211 ++++++++++++++------------ tests/basicswap/test_ltc_xmr.py | 146 ++++++++++++++++-- tests/basicswap/test_partblind_xmr.py | 28 ++-- tests/basicswap/test_run.py | 11 +- tests/basicswap/test_xmr.py | 40 ++--- 29 files changed, 987 insertions(+), 517 deletions(-) create mode 100644 basicswap/templates/style.html diff --git a/basicswap/__init__.py b/basicswap/__init__.py index d3cf1be..90c8579 100644 --- a/basicswap/__init__.py +++ b/basicswap/__init__.py @@ -1,3 +1,3 @@ name = "basicswap" -__version__ = "0.12.3" +__version__ = "0.12.4" diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 934ae40..31ec870 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -256,6 +256,7 @@ class BasicSwap(BaseApp): self._is_locked = None # TODO: Set dynamically + self.balance_only_coins = (Coins.LTC_MWEB, ) self.scriptless_coins = (Coins.XMR, Coins.PART_ANON, Coins.FIRO) self.adaptor_swap_only_coins = self.scriptless_coins + (Coins.PART_BLIND, ) self.coins_without_segwit = (Coins.PIVX, Coins.DASH, Coins.NMC) @@ -480,6 +481,9 @@ class BasicSwap(BaseApp): 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 coin == Coins.XMR: if chain_client_settings.get('automatically_select_daemon', False): @@ -510,8 +514,8 @@ class BasicSwap(BaseApp): if current_daemon_url in remote_daemon_urls: self.log.info(f'Trying last used url {rpchost}:{rpcport}.') try: - rpc_cb2 = make_xmr_rpc2_func(rpcport, daemon_login, rpchost) - test = rpc_cb2('get_height', timeout=20)['height'] + rpc2 = make_xmr_rpc2_func(rpcport, daemon_login, rpchost) + test = rpc2('get_height', timeout=20)['height'] return True except Exception as e: self.log.warning(f'Failed to set XMR remote daemon to {rpchost}:{rpcport}, {e}') @@ -520,8 +524,8 @@ class BasicSwap(BaseApp): self.log.info(f'Trying url {url}.') try: rpchost, rpcport = url.rsplit(':', 1) - rpc_cb2 = make_xmr_rpc2_func(rpcport, daemon_login, rpchost) - test = rpc_cb2('get_height', timeout=20)['height'] + rpc2 = make_xmr_rpc2_func(rpcport, daemon_login, rpchost) + test = rpc2('get_height', timeout=20)['height'] coin_settings['rpchost'] = rpchost coin_settings['rpcport'] = rpcport data = { @@ -544,6 +548,9 @@ class BasicSwap(BaseApp): if coin == Coins.PART_BLIND: use_coinid = Coins.PART interface_ind = 'interface_blind' + if coin == Coins.LTC_MWEB: + use_coinid = Coins.LTC + interface_ind = 'interface_mweb' if use_coinid not in self.coin_clients: raise ValueError('Unknown coinid {}'.format(int(coin))) @@ -558,6 +565,9 @@ class BasicSwap(BaseApp): if coin == Coins.PART_BLIND: use_coinid = Coins.PART interface_ind = 'interface_blind' + if coin == Coins.LTC_MWEB: + use_coinid = Coins.LTC + interface_ind = 'interface_mweb' if use_coinid not in self.coin_clients: raise ValueError('Unknown coinid {}'.format(int(coin))) @@ -573,13 +583,18 @@ class BasicSwap(BaseApp): def createInterface(self, coin): if coin == Coins.PART: - return PARTInterface(self.coin_clients[coin], self.chain, self) + 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) + return interface elif coin == Coins.BTC: from .interface.btc import BTCInterface return BTCInterface(self.coin_clients[coin], self.chain, self) elif coin == Coins.LTC: - from .interface.ltc import LTCInterface - return LTCInterface(self.coin_clients[coin], self.chain, self) + 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) + return interface elif coin == Coins.NMC: from .interface.nmc import NMCInterface return NMCInterface(self.coin_clients[coin], self.chain, self) @@ -662,9 +677,6 @@ class BasicSwap(BaseApp): def createCoinInterface(self, coin): if self.coin_clients[coin]['connection_type'] == 'rpc': self.coin_clients[coin]['interface'] = self.createInterface(coin) - if coin == Coins.PART: - 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) elif self.coin_clients[coin]['connection_type'] == 'passthrough': self.coin_clients[coin]['interface'] = self.createPassthroughInterface(coin) @@ -685,13 +697,11 @@ class BasicSwap(BaseApp): self.createCoinInterface(c) if self.coin_clients[c]['connection_type'] == 'rpc': - if c in (Coins.BTC, ): - self.waitForDaemonRPC(c, with_wallet=False) - if len(self.callcoinrpc(c, 'listwallets')) >= 1: - self.waitForDaemonRPC(c) - else: - self.waitForDaemonRPC(c) ci = self.ci(c) + self.waitForDaemonRPC(c, with_wallet=False) + if c not in (Coins.XMR,) and ci.checkWallets() >= 1: + 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 @@ -721,6 +731,11 @@ class BasicSwap(BaseApp): except Exception as e: self.log.warning('Can\'t open XMR wallet, could be locked.') continue + elif c == Coins.LTC: + ci_mweb = self.ci(Coins.LTC_MWEB) + is_encrypted, _ = self.getLockedState() + if not is_encrypted and not ci_mweb.has_mweb_wallet(): + ci_mweb.init_wallet() self.checkWalletSeed(c) @@ -850,6 +865,16 @@ class BasicSwap(BaseApp): if self.coin_clients[c]['connection_type'] == 'rpc': yield c + def getListOfWalletCoins(self): + coins_list = copy.deepcopy(self.activeCoins()) + # Always unlock Particl first + if Coins.PART in coins_list: + coins_list.pop(Coins.PART) + coins_list = [Coins.PART,] + coins_list + 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: # 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: @@ -861,8 +886,10 @@ class BasicSwap(BaseApp): if len(new_password) < 4: raise ValueError('New password is too short') + coins_list = self.getListOfWalletCoins() + # Unlock wallets to ensure they all have the same password. - for c in self.activeCoins(): + for c in coins_list: if coin and c != coin: continue ci = self.ci(c) @@ -871,7 +898,7 @@ class BasicSwap(BaseApp): except Exception as e: raise ValueError('Failed to unlock {}'.format(ci.coin_name())) - for c in self.activeCoins(): + for c in coins_list: if coin and c != coin: continue self.ci(c).changeWalletPassword(old_password, new_password) @@ -882,7 +909,7 @@ class BasicSwap(BaseApp): def unlockWallets(self, password: str, coin=None) -> None: self._read_zmq_queue = False - for c in self.activeCoins(): + for c in self.getListOfWalletCoins(): if coin and c != coin: continue self.ci(c).unlockWallet(password) @@ -896,7 +923,7 @@ class BasicSwap(BaseApp): self._read_zmq_queue = False self.swaps_in_progress.clear() - for c in self.activeCoins(): + for c in self.getListOfWalletCoins(): if coin and c != coin: continue self.ci(c).lockWallet() @@ -923,7 +950,6 @@ class BasicSwap(BaseApp): root_key = self.getWalletKey(coin_type, 1) root_hash = ci.getSeedHash(root_key) - try: ci.initialiseWallet(root_key) except Exception as e: @@ -931,6 +957,8 @@ class BasicSwap(BaseApp): self.log.error('initialiseWallet failed: {}'.format(str(e))) if raise_errors: raise e + if self.debug: + self.log.error(traceback.format_exc()) return try: @@ -1167,6 +1195,11 @@ class BasicSwap(BaseApp): return coin_from in self.scriptless_coins + self.coins_without_segwit def validateSwapType(self, coin_from, coin_to, swap_type): + + for coin in (coin_from, coin_to): + if coin in self.balance_only_coins: + raise ValueError('Invalid coin: {}'.format(coin.name)) + if swap_type == SwapTypes.XMR_SWAP: reverse_bid: bool = self.is_reverse_ads_bid(coin_from) itx_coin = coin_to if reverse_bid else coin_from @@ -1812,6 +1845,14 @@ class BasicSwap(BaseApp): self.log.debug('In txn: {}'.format(txid)) return txid + def withdrawLTC(self, type_from, value, addr_to, subfee): + ci = self.ci(Coins.LTC) + self.log.info('withdrawLTC %s %s to %s %s', value, ci.ticker(), addr_to, ' subfee' if subfee else '') + + txid = ci.withdrawCoin(value, type_from, addr_to, subfee) + self.log.debug('In txn: {}'.format(txid)) + return txid + def withdrawParticl(self, type_from, type_to, value, addr_to, subfee): self.log.info('withdrawParticl %s %s to %s %s %s', value, type_from, type_to, addr_to, ' subfee' if subfee else '') @@ -1827,7 +1868,7 @@ class BasicSwap(BaseApp): def cacheNewAddressForCoin(self, coin_type): self.log.debug('cacheNewAddressForCoin %s', coin_type) - key_str = 'receive_addr_' + chainparams[coin_type]['name'] + key_str = 'receive_addr_' + self.ci(coin_type).coin_name().lower() addr = self.getReceiveAddressForCoin(coin_type) self.setStringKV(key_str, addr) return addr @@ -1864,7 +1905,7 @@ class BasicSwap(BaseApp): if expect_seedid is None: 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_callback('listwallets')) < 1: + 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): @@ -1893,7 +1934,8 @@ class BasicSwap(BaseApp): self.log.debug('getCachedAddressForCoin %s', coin_type) # TODO: auto refresh after used - key_str = 'receive_addr_' + chainparams[coin_type]['name'] + ci = self.ci(coin_type) + key_str = 'receive_addr_' + ci.coin_name().lower() session = self.openSession() try: try: @@ -1908,9 +1950,22 @@ class BasicSwap(BaseApp): self.closeSession(session) return addr + def cacheNewStealthAddressForCoin(self, coin_type): + self.log.debug('cacheNewStealthAddressForCoin %s', coin_type) + + if coin_type == Coins.LTC_MWEB: + coin_type = Coins.LTC + ci = self.ci(coin_type) + key_str = 'stealth_addr_' + ci.coin_name().lower() + addr = ci.getNewStealthAddress() + self.setStringKV(key_str, addr) + return addr + def getCachedStealthAddressForCoin(self, coin_type): self.log.debug('getCachedStealthAddressForCoin %s', coin_type) + if coin_type == Coins.LTC_MWEB: + coin_type = Coins.LTC ci = self.ci(coin_type) key_str = 'stealth_addr_' + ci.coin_name().lower() session = self.openSession() @@ -2559,7 +2614,7 @@ class BasicSwap(BaseApp): address_out = self.getReceiveAddressFromPool(coin_from, offer_id, TxTypes.XMR_SWAP_A_LOCK) if coin_from == Coins.PART_BLIND: - addrinfo = ci_from.rpc_callback('getaddressinfo', [address_out]) + 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) @@ -2870,7 +2925,7 @@ class BasicSwap(BaseApp): address_out = self.getReceiveAddressFromPool(coin_from, bid.offer_id, TxTypes.XMR_SWAP_A_LOCK) if coin_from == Coins.PART_BLIND: - addrinfo = ci_from.rpc_callback('getaddressinfo', [address_out]) + 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) @@ -3339,7 +3394,8 @@ class BasicSwap(BaseApp): bid.participate_tx.chain_height = participate_txn_height # Start checking for spends of participate_txn before fully confirmed - self.log.debug('Watching %s chain for spend of output %s %d', chainparams[coin_type]['name'], txid_hex, vout) + 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) def participateTxnConfirmed(self, bid_id: bytes, bid, offer) -> None: @@ -4151,7 +4207,7 @@ class BasicSwap(BaseApp): last_height_checked += 1 if c['last_height_checked'] != last_height_checked: c['last_height_checked'] = last_height_checked - self.setIntKV('last_height_checked_' + chainparams[coin_type]['name'], last_height_checked) + self.setIntKV('last_height_checked_' + ci.coin_name().lower(), last_height_checked) def expireMessages(self) -> None: if self._is_locked is True: @@ -6413,6 +6469,11 @@ class BasicSwap(BaseApp): rv['main_address'] = self.getCachedMainWalletAddress(ci) elif coin == Coins.NAV: 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_pending'] = walletinfo['mweb_unconfirmed'] + walletinfo['mweb_immature'] return rv except Exception as e: @@ -6505,10 +6566,17 @@ class BasicSwap(BaseApp): wallet_data['lastupdated'] = row[3] wallet_data['updating'] = self._updating_wallets_info.get(coin_id, False) - # Ensure the latest deposit address is displayed - q = session.execute('SELECT value FROM kv_string WHERE key = "receive_addr_{}"'.format(chainparams[coin_id]['name'])) + # Ensure the latest addresses are displayed + q = session.execute('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: - wallet_data['deposit_address'] = row[0] + + if row[0].startswith('stealth'): + if coin_id == Coins.LTC: + wallet_data['mweb_address'] = row[1] + else: + wallet_data['stealth_address'] = row[1] + else: + wallet_data['deposit_address'] = row[1] if coin_id in rv: rv[coin_id].update(wallet_data) diff --git a/basicswap/chainparams.py b/basicswap/chainparams.py index e250745..f68873f 100644 --- a/basicswap/chainparams.py +++ b/basicswap/chainparams.py @@ -32,6 +32,7 @@ class Coins(IntEnum): DASH = 12 FIRO = 13 NAV = 14 + LTC_MWEB = 15 chainparams = { diff --git a/basicswap/http_server.py b/basicswap/http_server.py index 7a95c71..75ce134 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -280,6 +280,8 @@ class HttpHandler(BaseHTTPRequestHandler): coin_id = int(get_data_entry(form_data, 'coin_type')) if coin_id in (-2, -3, -4): coin_type = Coins(Coins.XMR) + elif coin_id in (-5,): + coin_type = Coins(Coins.LTC) else: coin_type = Coins(coin_id) except Exception: @@ -295,20 +297,23 @@ class HttpHandler(BaseHTTPRequestHandler): method = arr[0] params = json.loads(arr[1]) if len(arr) > 1 else [] if coin_id == -4: - rv = ci.rpc_wallet_cb(method, params) + rv = ci.rpc_wallet(method, params) elif coin_id == -3: - rv = ci.rpc_cb(method, params) + rv = ci.rpc(method, params) elif coin_id == -2: if params == []: params = None - rv = ci.rpc_cb2(method, params) + rv = ci.rpc2(method, params) else: raise ValueError('Unknown XMR RPC variant') result = json.dumps(rv, indent=4) else: if call_type == 'http': method, params = parse_cmd(cmd, type_map) - rv = swap_client.ci(coin_type).rpc_callback(method, params) + if coin_id == -5: + rv = swap_client.ci(coin_type).rpc_wallet_mweb(method, params) + else: + rv = swap_client.ci(coin_type).rpc_wallet(method, params) if not isinstance(rv, str): rv = json.dumps(rv, indent=4) result = cmd + '\n' + rv @@ -323,6 +328,7 @@ class HttpHandler(BaseHTTPRequestHandler): coins = listAvailableCoins(swap_client, with_variants=False) coins = [c for c in coins if c[0] != Coins.XMR] + coins.append((-5, 'Litecoin MWEB Wallet')) coins.append((-2, 'Monero')) coins.append((-3, 'Monero JSON')) coins.append((-4, 'Monero Wallet')) diff --git a/basicswap/interface/btc.py b/basicswap/interface/btc.py index 6e723e0..175647a 100644 --- a/basicswap/interface/btc.py +++ b/basicswap/interface/btc.py @@ -195,7 +195,9 @@ class BTCInterface(CoinInterface): self._rpc_host = coin_settings.get('rpchost', '127.0.0.1') self._rpcport = coin_settings['rpcport'] self._rpcauth = coin_settings['rpcauth'] - self.rpc_callback = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) + 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'] @@ -204,6 +206,23 @@ class BTCInterface(CoinInterface): self._log = self._sc.log if self._sc and self._sc.log else logging self._expect_seedid_hex = None + def checkWallets(self) -> int: + 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())) + for wallet_name in wallets: + # Skip over other expected wallets + 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) + break + + return len(wallets) + def using_segwit(self) -> bool: # Using btc native segwit return self._use_segwit @@ -239,34 +258,34 @@ class BTCInterface(CoinInterface): self._conf_target = new_conf_target def testDaemonRPC(self, with_wallet=True) -> None: - self.rpc_callback('getwalletinfo' if with_wallet else 'getblockchaininfo') + self.rpc_wallet('getwalletinfo' if with_wallet else 'getblockchaininfo') def getDaemonVersion(self): - return self.rpc_callback('getnetworkinfo')['version'] + return self.rpc('getnetworkinfo')['version'] def getBlockchainInfo(self): - return self.rpc_callback('getblockchaininfo') + return self.rpc('getblockchaininfo') def getChainHeight(self) -> int: - return self.rpc_callback('getblockcount') + return self.rpc('getblockcount') def getMempoolTx(self, txid): - return self.rpc_callback('getrawtransaction', [txid.hex()]) + return self.rpc('getrawtransaction', [txid.hex()]) def getBlockHeaderFromHeight(self, height): - block_hash = self.rpc_callback('getblockhash', [height]) - return self.rpc_callback('getblockheader', [block_hash]) + block_hash = self.rpc('getblockhash', [height]) + return self.rpc('getblockheader', [block_hash]) def getBlockHeader(self, block_hash): - return self.rpc_callback('getblockheader', [block_hash]) + return self.rpc('getblockheader', [block_hash]) def getBlockHeaderAt(self, time: int, block_after=False): - blockchaininfo = self.rpc_callback('getblockchaininfo') - last_block_header = self.rpc_callback('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_callback('getblock', [last_block_header['previousblockhash']]) + prev_block_header = self.rpc('getblock', [last_block_header['previousblockhash']]) if prev_block_header['time'] <= time: return last_block_header if block_after else prev_block_header @@ -275,11 +294,10 @@ class BTCInterface(CoinInterface): def initialiseWallet(self, key_bytes: bytes) -> None: key_wif = self.encodeKey(key_bytes) - - self.rpc_callback('sethdseed', [True, key_wif]) + self.rpc_wallet('sethdseed', [True, key_wif]) def getWalletInfo(self): - rv = self.rpc_callback('getwalletinfo') + rv = self.rpc_wallet('getwalletinfo') rv['encrypted'] = 'unlocked_until' in rv rv['locked'] = rv.get('unlocked_until', 1) <= 0 return rv @@ -288,7 +306,7 @@ class BTCInterface(CoinInterface): return self._restore_height def getWalletRestoreHeight(self) -> int: - start_time = self.rpc_callback('getwalletinfo')['keypoololdest'] + start_time = self.rpc_wallet('getwalletinfo')['keypoololdest'] blockchaininfo = self.getBlockchainInfo() best_block = blockchaininfo['bestblockhash'] @@ -312,7 +330,7 @@ class BTCInterface(CoinInterface): raise ValueError('{} wallet restore height not found.'.format(self.coin_name())) def getWalletSeedID(self) -> str: - wi = self.rpc_callback('getwalletinfo') + wi = self.rpc_wallet('getwalletinfo') return 'Not found' if 'hdseedid' not in wi else wi['hdseedid'] def checkExpectedSeed(self, expect_seedid) -> bool: @@ -323,11 +341,11 @@ class BTCInterface(CoinInterface): args = [label] if use_segwit: args.append('bech32') - return self.rpc_callback('getnewaddress', args) + return self.rpc_wallet('getnewaddress', args) def isValidAddress(self, address: str) -> bool: try: - rv = self.rpc_callback('validateaddress', [address]) + rv = self.rpc_wallet('validateaddress', [address]) if rv['isvalid'] is True: return True except Exception as ex: @@ -347,13 +365,13 @@ class BTCInterface(CoinInterface): return False def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool: - addr_info = self.rpc_callback('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'] def checkAddressMine(self, address: str) -> None: - addr_info = self.rpc_callback('getaddressinfo', [address]) + 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') @@ -369,16 +387,16 @@ class BTCInterface(CoinInterface): def try_get_fee_rate(self, conf_target): try: - fee_rate = self.rpc_callback('estimatesmartfee', [conf_target])['feerate'] + fee_rate = self.rpc_wallet('estimatesmartfee', [conf_target])['feerate'] assert (fee_rate > 0.0), 'Non positive feerate' return fee_rate, 'estimatesmartfee' except Exception: try: - fee_rate = self.rpc_callback('getwalletinfo')['paytxfee'] + fee_rate = self.rpc_wallet('getwalletinfo')['paytxfee'] assert (fee_rate > 0.0), 'Non positive feerate' return fee_rate, 'paytxfee' except Exception: - return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee' + return self.rpc('getnetworkinfo')['relayfee'], 'relayfee' fee_rate, rate_src = try_get_fee_rate(self, conf_target) if min_relay_fee and min_relay_fee > fee_rate: @@ -734,7 +752,7 @@ class BTCInterface(CoinInterface): add_bytes = 0 add_witness_bytes = getCompactSizeLen(len(tx.vin)) for pi in tx.vin: - ptx = self.rpc_callback('getrawtransaction', [i2h(pi.prevout.hash), True]) + ptx = self.rpc('getrawtransaction', [i2h(pi.prevout.hash), True]) prevout = ptx['vout'][pi.prevout.n] inputs_value += make_int(prevout['value']) @@ -942,13 +960,13 @@ class BTCInterface(CoinInterface): 'lockUnspents': True, 'feeRate': feerate_str, } - rv = self.rpc_callback('fundrawtransaction', [tx.hex(), options]) + rv = self.rpc_wallet('fundrawtransaction', [tx.hex(), options]) return bytes.fromhex(rv['hex']) def listInputs(self, tx_bytes): tx = self.loadTx(tx_bytes) - all_locked = self.rpc_callback('listlockunspent') + all_locked = self.rpc_wallet('listlockunspent') inputs = [] for pi in tx.vin: txid_hex = i2h(pi.prevout.hash) @@ -962,19 +980,19 @@ class BTCInterface(CoinInterface): inputs = [] for pi in tx.vin: inputs.append({'txid': i2h(pi.prevout.hash), 'vout': pi.prevout.n}) - self.rpc_callback('lockunspent', [True, inputs]) + self.rpc('lockunspent', [True, inputs]) def signTxWithWallet(self, tx: bytes) -> bytes: - rv = self.rpc_callback('signrawtransactionwithwallet', [tx.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_callback('signrawtransactionwithkey', [tx.hex(), [key_wif, ]]) + rv = self.rpc('signrawtransactionwithkey', [tx.hex(), [key_wif, ]]) return bytes.fromhex(rv['hex']) def publishTx(self, tx: bytes): - return self.rpc_callback('sendrawtransaction', [tx.hex()]) + return self.rpc('sendrawtransaction', [tx.hex()]) def encodeTx(self, tx) -> bytes: return tx.serialize() @@ -1018,18 +1036,18 @@ class BTCInterface(CoinInterface): return self.getScriptForPubkeyHash(self.getPubkeyHash(K)) def scanTxOutset(self, dest): - return self.rpc_callback('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_callback('getrawtransaction', [txid.hex()])) + return bytes.fromhex(self.rpc('getrawtransaction', [txid.hex()])) except Exception as ex: # TODO: filter errors return None def getWalletTransaction(self, txid: bytes): try: - return bytes.fromhex(self.rpc_callback('gettransaction', [txid.hex()])) + return bytes.fromhex(self.rpc('gettransaction', [txid.hex()])) except Exception as ex: # TODO: filter errors return None @@ -1115,7 +1133,7 @@ class BTCInterface(CoinInterface): 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) -> bytes: self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex()) - wtx = self.rpc_callback('gettransaction', [chain_b_lock_txid.hex(), ]) + wtx = self.rpc_wallet('gettransaction', [chain_b_lock_txid.hex(), ]) lock_tx = self.loadTx(bytes.fromhex(wtx['hex'])) Kbs = self.getPubkey(kbs) @@ -1144,10 +1162,10 @@ class BTCInterface(CoinInterface): return bytes.fromhex(self.publishTx(b_lock_spend_tx)) def importWatchOnlyAddress(self, address: str, label: str): - self.rpc_callback('importaddress', [address, label, False]) + self.rpc_wallet('importaddress', [address, label, False]) def isWatchOnlyAddress(self, address: str): - addr_info = self.rpc_callback('getaddressinfo', [address]) + addr_info = self.rpc_wallet('getaddressinfo', [address]) return addr_info['iswatchonly'] def getSCLockScriptAddress(self, lock_script): @@ -1161,11 +1179,11 @@ class BTCInterface(CoinInterface): 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_callback('rescanblockchain', [rescan_from]) + self.rpc_wallet('rescanblockchain', [rescan_from]) return_txid = True if txid is None else False if txid is None: - txns = self.rpc_callback('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: @@ -1176,11 +1194,11 @@ class BTCInterface(CoinInterface): return None try: - tx = self.rpc_callback('gettransaction', [txid.hex()]) + tx = self.rpc_wallet('gettransaction', [txid.hex()]) block_height = 0 if 'blockhash' in tx: - block_header = self.rpc_callback('getblockheader', [tx['blockhash']]) + block_header = self.rpc('getblockheader', [tx['blockhash']]) block_height = block_header['height'] rv = { @@ -1192,7 +1210,7 @@ class BTCInterface(CoinInterface): return None if find_index: - tx_obj = self.rpc_callback('decoderawtransaction', [tx['hex']]) + tx_obj = self.rpc('decoderawtransaction', [tx['hex']]) rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address) if return_txid: @@ -1202,7 +1220,7 @@ class BTCInterface(CoinInterface): def getOutput(self, txid, dest_script, expect_value, xmr_swap=None): # TODO: Use getrawtransaction if txindex is active - utxos = self.rpc_callback('scantxoutset', ['start', ['raw({})'.format(dest_script.hex())]]) + 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: @@ -1225,7 +1243,7 @@ class BTCInterface(CoinInterface): def withdrawCoin(self, value, addr_to, subfee): params = [addr_to, value, '', '', subfee, True, self._conf_target] - return self.rpc_callback('sendtoaddress', params) + return self.rpc_wallet('sendtoaddress', params) def signCompact(self, k, message): message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest() @@ -1318,10 +1336,10 @@ class BTCInterface(CoinInterface): return length def describeTx(self, tx_hex: str): - return self.rpc_callback('decoderawtransaction', [tx_hex]) + return self.rpc('decoderawtransaction', [tx_hex]) def getSpendableBalance(self) -> int: - return self.make_int(self.rpc_callback('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 @@ -1334,7 +1352,7 @@ class BTCInterface(CoinInterface): 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_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) + txn = self.rpc('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) options = { 'lockUnspents': lock_unspents, @@ -1342,18 +1360,18 @@ class BTCInterface(CoinInterface): } if sub_fee: options['subtractFeeFromOutputs'] = [0,] - return self.rpc_callback('fundrawtransaction', [txn, options])['hex'] + 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_callback('signrawtransactionwithwallet', [txn_funded])['hex'] + return self.rpc_wallet('signrawtransactionwithwallet', [txn_funded])['hex'] def getBlockWithTxns(self, block_hash: str): - return self.rpc_callback('getblock', [block_hash, 2]) + return self.rpc('getblock', [block_hash, 2]) def getUnspentsByAddr(self): unspent_addr = dict() - unspent = self.rpc_callback('listunspent') + unspent = self.rpc_wallet('listunspent') for u in unspent: if u['spendable'] is not True: continue @@ -1361,11 +1379,11 @@ class BTCInterface(CoinInterface): return unspent_addr def getUTXOBalance(self, address: str): - num_blocks = self.rpc_callback('getblockcount') + num_blocks = self.rpc('getblockcount') sum_unspent = 0 self._log.debug('[rm] scantxoutset start') # scantxoutset is slow - ro = self.rpc_callback('scantxoutset', ['start', ['addr({})'.format(address)]]) # TODO: Use combo(address) where possible + 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']) @@ -1391,7 +1409,7 @@ class BTCInterface(CoinInterface): sign_for_addr = self.pkh_to_address(pkh) self._log.debug('sign_for_addr converted %s', sign_for_addr) - signature = self.rpc_callback('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) @@ -1416,17 +1434,17 @@ class BTCInterface(CoinInterface): return self.getUTXOBalance(address) def isWalletEncrypted(self) -> bool: - wallet_info = self.rpc_callback('getwalletinfo') + wallet_info = self.rpc_wallet('getwalletinfo') return 'unlocked_until' in wallet_info def isWalletLocked(self) -> bool: - wallet_info = self.rpc_callback('getwalletinfo') + 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): - wallet_info = self.rpc_callback('getwalletinfo') + wallet_info = self.rpc_wallet('getwalletinfo') encrypted = 'unlocked_until' in wallet_info locked = encrypted and wallet_info['unlocked_until'] <= 0 return encrypted, locked @@ -1436,8 +1454,8 @@ class BTCInterface(CoinInterface): if old_password == '': if self.isWalletEncrypted(): raise ValueError('Old password must be set') - return self.rpc_callback('encryptwallet', [new_password]) - self.rpc_callback('walletpassphrasechange', [old_password, new_password]) + return self.rpc_wallet('encryptwallet', [new_password]) + self.rpc_wallet('walletpassphrasechange', [old_password, new_password]) def unlockWallet(self, password: str): if password == '': @@ -1447,21 +1465,20 @@ class BTCInterface(CoinInterface): 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_callback('listwallets') + wallets = self.rpc('listwallets') if len(wallets) < 1: self._log.info('Creating wallet.dat for {}.'.format(self.coin_name())) # wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors - self.rpc_callback('createwallet', ['wallet.dat', False, True, '', False, False]) - self.rpc_callback('encryptwallet', [password]) + self.rpc('createwallet', ['wallet.dat', False, True, '', False, False]) + self.rpc_wallet('encryptwallet', [password]) # Max timeout value, ~3 years - self.rpc_callback('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_callback('walletlock') + self.rpc_wallet('walletlock') def get_p2sh_script_pubkey(self, script: bytearray) -> bytearray: script_hash = hash160(script) @@ -1474,7 +1491,7 @@ class BTCInterface(CoinInterface): def findTxnByHash(self, txid_hex: str): # Only works for wallet txns try: - rv = self.rpc_callback('gettransaction', [txid_hex]) + rv = self.rpc_wallet('gettransaction', [txid_hex]) except Exception as ex: self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) return None diff --git a/basicswap/interface/dash.py b/basicswap/interface/dash.py index 3929300..933ea98 100644 --- a/basicswap/interface/dash.py +++ b/basicswap/interface/dash.py @@ -32,7 +32,7 @@ class DASHInterface(BTCInterface): words = self.seedToMnemonic(key) mnemonic_passphrase = '' - self.rpc_callback('upgradetohd', [words, mnemonic_passphrase, self._wallet_passphrase]) + self.rpc_wallet('upgradetohd', [words, mnemonic_passphrase, self._wallet_passphrase]) self._have_checked_seed = False if self._wallet_passphrase != '': self.unlockWallet(self._wallet_passphrase) @@ -42,7 +42,7 @@ class DASHInterface(BTCInterface): def checkExpectedSeed(self, key_hash: str): try: - rv = self.rpc_callback('dumphdinfo') + rv = self.rpc_wallet('dumphdinfo') entropy = Mnemonic('english').to_entropy(rv['mnemonic'].split(' ')) entropy_hash = self.getAddressHashFromKey(entropy)[::-1].hex() self._have_checked_seed = True @@ -53,10 +53,10 @@ class DASHInterface(BTCInterface): def withdrawCoin(self, value, addr_to, subfee): params = [addr_to, value, '', '', subfee, False, False, self._conf_target] - return self.rpc_callback('sendtoaddress', params) + return self.rpc_wallet('sendtoaddress', params) def getSpendableBalance(self) -> int: - return self.make_int(self.rpc_callback('getwalletinfo')['balance']) + return self.make_int(self.rpc_wallet('getwalletinfo')['balance']) def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray: # Return P2PKH @@ -72,7 +72,7 @@ class DASHInterface(BTCInterface): def findTxnByHash(self, txid_hex: str): # Only works for wallet txns try: - rv = self.rpc_callback('gettransaction', [txid_hex]) + rv = self.rpc_wallet('gettransaction', [txid_hex]) except Exception as ex: self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) return None diff --git a/basicswap/interface/firo.py b/basicswap/interface/firo.py index c5dcba0..d1daf8a 100644 --- a/basicswap/interface/firo.py +++ b/basicswap/interface/firo.py @@ -13,6 +13,7 @@ from basicswap.util import ( i2b, ensure, ) +from basicswap.rpc import make_rpc_func from basicswap.util.crypto import hash160 from basicswap.util.address import decodeAddress from basicswap.chainparams import Coins @@ -36,6 +37,14 @@ class FIROInterface(BTCInterface): def coin_type(): return Coins.FIRO + 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) + + def checkWallets(self) -> int: + return 1 + def getExchangeName(self, exchange_name): return 'zcoin' @@ -44,9 +53,9 @@ class FIROInterface(BTCInterface): pass def getNewAddress(self, use_segwit, label='swap_receive'): - return self.rpc_callback('getnewaddress', [label]) - # addr_plain = self.rpc_callback('getnewaddress', [label]) - # return self.rpc_callback('addwitnessaddress', [addr_plain]) + return self.rpc('getnewaddress', [label]) + # addr_plain = self.rpc('getnewaddress', [label]) + # return self.rpc('addwitnessaddress', [addr_plain]) def decodeAddress(self, address): return decodeAddress(address)[1:] @@ -58,11 +67,11 @@ class FIROInterface(BTCInterface): raise ValueError('TODO') def isWatchOnlyAddress(self, address): - addr_info = self.rpc_callback('validateaddress', [address]) + 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_callback('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'] @@ -73,8 +82,8 @@ class FIROInterface(BTCInterface): if not self.isAddressMine(address, or_watch_only=True): # Expects P2WSH nested in BIP16_P2SH - ro = self.rpc_callback('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True]) - addr_info = self.rpc_callback('validateaddress', [address]) + ro = self.rpc('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True]) + addr_info = self.rpc('validateaddress', [address]) return address @@ -89,7 +98,7 @@ class FIROInterface(BTCInterface): return_txid = True if txid is None else False if txid is None: - txns = self.rpc_callback('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: @@ -100,11 +109,11 @@ class FIROInterface(BTCInterface): return None try: - tx = self.rpc_callback('gettransaction', [txid.hex()]) + tx = self.rpc('gettransaction', [txid.hex()]) block_height = 0 if 'blockhash' in tx: - block_header = self.rpc_callback('getblockheader', [tx['blockhash']]) + block_header = self.rpc('getblockheader', [tx['blockhash']]) block_height = block_header['height'] rv = { @@ -116,7 +125,7 @@ class FIROInterface(BTCInterface): return None if find_index: - tx_obj = self.rpc_callback('decoderawtransaction', [tx['hex']]) + tx_obj = self.rpc('decoderawtransaction', [tx['hex']]) rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address) if return_txid: @@ -135,11 +144,11 @@ class FIROInterface(BTCInterface): return self.fundTx(tx_bytes, feerate) def signTxWithWallet(self, tx): - rv = self.rpc_callback('signrawtransaction', [tx.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_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) + 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}') options = { @@ -148,11 +157,11 @@ class FIROInterface(BTCInterface): } if sub_fee: options['subtractFeeFromOutputs'] = [0,] - return self.rpc_callback('fundrawtransaction', [txn, options])['hex'] + return self.rpc('fundrawtransaction', [txn, options])['hex'] def createRawSignedTransaction(self, addr_to, amount) -> str: txn_funded = self.createRawFundedTransaction(addr_to, amount) - return self.rpc_callback('signrawtransaction', [txn_funded])['hex'] + return self.rpc('signrawtransaction', [txn_funded])['hex'] def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray: # Return P2PKH @@ -180,13 +189,13 @@ class FIROInterface(BTCInterface): def withdrawCoin(self, value, addr_to, subfee): params = [addr_to, value, '', '', subfee] - return self.rpc_callback('sendtoaddress', params) + return self.rpc('sendtoaddress', params) def getWalletSeedID(self): - return self.rpc_callback('getwalletinfo')['hdmasterkeyid'] + return self.rpc('getwalletinfo')['hdmasterkeyid'] def getSpendableBalance(self) -> int: - return self.make_int(self.rpc_callback('getwalletinfo')['balance']) + return self.make_int(self.rpc('getwalletinfo')['balance']) def getBLockSpendTxFee(self, tx, fee_rate: int) -> int: add_bytes = 107 @@ -197,13 +206,13 @@ class FIROInterface(BTCInterface): def signTxWithKey(self, tx: bytes, key: bytes) -> bytes: key_wif = self.encodeKey(key) - rv = self.rpc_callback('signrawtransaction', [tx.hex(), [], [key_wif, ]]) + 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_callback('gettransaction', [txid_hex]) + rv = self.rpc('gettransaction', [txid_hex]) except Exception as ex: self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) return None @@ -216,7 +225,7 @@ class FIROInterface(BTCInterface): # TODO: Lock unspent and use same output/s to fund bid unspents_by_addr = dict() - unspents = self.rpc_callback('listunspent') + unspents = self.rpc('listunspent') for u in unspents: if u['spendable'] is not True: continue @@ -276,7 +285,7 @@ class FIROInterface(BTCInterface): sign_for_addr = self.pkh_to_address(pkh) self._log.debug('sign_for_addr converted %s', sign_for_addr) - signature = self.rpc_callback('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) @@ -296,7 +305,7 @@ class FIROInterface(BTCInterface): sum_value: int = 0 for outpoint in utxos: - txout = self.rpc_callback('gettxout', [outpoint[0].hex(), outpoint[1]]) + txout = self.rpc('gettxout', [outpoint[0].hex(), outpoint[1]]) sum_value += self.make_int(txout['value']) return sum_value @@ -307,15 +316,15 @@ class FIROInterface(BTCInterface): chain_blocks: int = self.getChainHeight() current_height: int = chain_blocks - block_hash = self.rpc_callback('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_callback('getblockhash', [current_height]) + block_hash = self.rpc('getblockhash', [current_height]) - block = self.rpc_callback('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: @@ -325,22 +334,22 @@ class FIROInterface(BTCInterface): 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_callback('invalidateblock', [block_hash]) - self.rpc_callback('reconsiderblock', [block_hash]) + self.rpc('invalidateblock', [block_hash]) + self.rpc('reconsiderblock', [block_hash]) return current_height -= 1 def getBlockWithTxns(self, block_hash): # TODO: Bypass decoderawtransaction and getblockheader - block = self.rpc_callback('getblock', [block_hash, False]) - block_header = self.rpc_callback('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_callback('decoderawtransaction', [tx_hex]) + tx_dec = self.rpc('decoderawtransaction', [tx_hex]) if 'hex' not in tx_dec: tx_dec['hex'] = tx_hex diff --git a/basicswap/interface/ltc.py b/basicswap/interface/ltc.py index 979265f..4576208 100644 --- a/basicswap/interface/ltc.py +++ b/basicswap/interface/ltc.py @@ -1,15 +1,117 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2020 tecnovert +# Copyright (c) 2020-2023 tecnovert # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. from .btc import BTCInterface -from basicswap.chainparams import Coins +from basicswap.rpc import make_rpc_func +from basicswap.chainparams import Coins, chainparams class LTCInterface(BTCInterface): @staticmethod def coin_type(): return Coins.LTC + + 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) + + def getNewMwebAddress(self, use_segwit=False, label='swap_receive') -> str: + return self.rpc_wallet_mweb('getnewaddress', [label, 'mweb']) + + 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) + + def getWalletInfo(self): + rv = self.rpc_wallet('getwalletinfo') + rv['encrypted'] = 'unlocked_until' in rv + rv['locked'] = rv.get('unlocked_until', 1) <= 0 + + 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 + + +class LTCInterfaceMWEB(LTCInterface): + @staticmethod + def coin_type(): + return Coins.LTC_MWEB + + 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) + + def chainparams(self): + return chainparams[Coins.LTC] + + def chainparams_network(self): + return chainparams[Coins.LTC][self._network] + + def coin_name(self) -> str: + coin_chainparams = chainparams[Coins.LTC] + if coin_chainparams.get('use_ticker_as_name', False): + return coin_chainparams['ticker'] + ' 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' + + 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') + + 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('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]) + + if password is not None: + # Max timeout value, ~3 years + self.rpc_wallet('walletpassphrase', [password, 100000000]) + + 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']) + if password is not None: + self.rpc_wallet('walletpassphrase', [password, 100000000]) + self.rpc_wallet('keypoolrefill') + + def unlockWallet(self, password: str): + if password == '': + return + 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._sc.checkWalletSeed(self.coin_type()) diff --git a/basicswap/interface/nav.py b/basicswap/interface/nav.py index df18ddd..2a8dc1c 100644 --- a/basicswap/interface/nav.py +++ b/basicswap/interface/nav.py @@ -14,6 +14,7 @@ from coincurve.keys import ( PrivateKey, ) from .btc import BTCInterface, find_vout_for_address_from_txobj, findOutput +from basicswap.rpc import make_rpc_func from basicswap.chainparams import Coins from basicswap.interface.contrib.nav_test_framework.mininode import ( CTxIn, @@ -63,6 +64,14 @@ class NAVInterface(BTCInterface): def txoType(): return CTxOut + 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) + + def checkWallets(self) -> int: + return 1 + def use_p2shp2wsh(self) -> bool: # p2sh-p2wsh return True @@ -75,24 +84,24 @@ class NAVInterface(BTCInterface): pass def getWalletSeedID(self): - return self.rpc_callback('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_callback('sendtoaddress', params) + return self.rpc('sendtoaddress', params) def getSpendableBalance(self) -> int: - return self.make_int(self.rpc_callback('getwalletinfo')['balance']) + return self.make_int(self.rpc('getwalletinfo')['balance']) def signTxWithWallet(self, tx: bytes) -> bytes: - rv = self.rpc_callback('signrawtransaction', [tx.hex()]) + rv = self.rpc('signrawtransaction', [tx.hex()]) return bytes.fromhex(rv['hex']) def checkExpectedSeed(self, key_hash: str): try: - rv = self.rpc_callback('dumpmnemonic') + rv = self.rpc('dumpmnemonic') entropy = Mnemonic('english').to_entropy(rv.split(' ')) entropy_hash = self.getAddressHashFromKey(entropy)[::-1].hex() @@ -155,7 +164,7 @@ class NAVInterface(BTCInterface): # TODO: Lock unspent and use same output/s to fund bid unspents_by_addr = dict() - unspents = self.rpc_callback('listunspent') + unspents = self.rpc('listunspent') for u in unspents: if u['spendable'] is not True: continue @@ -211,13 +220,13 @@ class NAVInterface(BTCInterface): 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_callback('validateaddress', [addr, ]) + 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) - signature = self.rpc_callback('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) @@ -237,13 +246,13 @@ class NAVInterface(BTCInterface): sum_value: int = 0 for outpoint in utxos: - txout = self.rpc_callback('gettxout', [outpoint[0].hex(), outpoint[1]]) + 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_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) + 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}') if sub_fee: @@ -254,17 +263,17 @@ class NAVInterface(BTCInterface): return self.fundTx(txn, fee_rate, lock_unspents).hex() def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool: - addr_info = self.rpc_callback('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'] def createRawSignedTransaction(self, addr_to, amount) -> str: txn_funded = self.createRawFundedTransaction(addr_to, amount) - return self.rpc_callback('signrawtransaction', [txn_funded])['hex'] + return self.rpc('signrawtransaction', [txn_funded])['hex'] def getBlockchainInfo(self): - rv = self.rpc_callback('getblockchaininfo') + rv = self.rpc('getblockchaininfo') synced = round(rv['verificationprogress'], 3) if synced >= 0.997: rv['verificationprogress'] = 1.0 @@ -278,7 +287,7 @@ class NAVInterface(BTCInterface): return pubkeyToAddress(self.chainparams_network()['script_address'], script) def find_prevout_info(self, txn_hex: str, txn_script: bytes): - txjs = self.rpc_callback('decoderawtransaction', [txn_hex]) + txjs = self.rpc('decoderawtransaction', [txn_hex]) n = getVoutByScriptPubKey(txjs, self.getScriptDest(txn_script).hex()) return { @@ -290,9 +299,9 @@ class NAVInterface(BTCInterface): } def getNewAddress(self, use_segwit: bool, label: str = 'swap_receive') -> str: - address: str = self.rpc_callback('getnewaddress', [label,]) + address: str = self.rpc('getnewaddress', [label,]) if use_segwit: - return self.rpc_callback('addwitnessaddress', [address,]) + return self.rpc('addwitnessaddress', [address,]) return address def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes) -> str: @@ -385,15 +394,15 @@ class NAVInterface(BTCInterface): chain_blocks: int = self.getChainHeight() current_height: int = chain_blocks - block_hash = self.rpc_callback('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_callback('getblockhash', [current_height]) + block_hash = self.rpc('getblockhash', [current_height]) - block = self.rpc_callback('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: @@ -403,8 +412,8 @@ class NAVInterface(BTCInterface): 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_callback('invalidateblock', [block_hash]) - self.rpc_callback('reconsiderblock', [block_hash]) + self.rpc('invalidateblock', [block_hash]) + self.rpc('reconsiderblock', [block_hash]) return current_height -= 1 @@ -419,7 +428,7 @@ class NAVInterface(BTCInterface): return_txid = True if txid is None else False if txid is None: - txns = self.rpc_callback('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: @@ -430,11 +439,11 @@ class NAVInterface(BTCInterface): return None try: - tx = self.rpc_callback('gettransaction', [txid.hex()]) + tx = self.rpc('gettransaction', [txid.hex()]) block_height = 0 if 'blockhash' in tx: - block_header = self.rpc_callback('getblockheader', [tx['blockhash']]) + block_header = self.rpc('getblockheader', [tx['blockhash']]) block_height = block_header['height'] rv = { @@ -446,7 +455,7 @@ class NAVInterface(BTCInterface): return None if find_index: - tx_obj = self.rpc_callback('decoderawtransaction', [tx['hex']]) + tx_obj = self.rpc('decoderawtransaction', [tx['hex']]) rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address) if return_txid: @@ -456,15 +465,15 @@ class NAVInterface(BTCInterface): def getBlockWithTxns(self, block_hash): # TODO: Bypass decoderawtransaction and getblockheader - block = self.rpc_callback('getblock', [block_hash, False]) - block_header = self.rpc_callback('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_callback('decoderawtransaction', [tx_hex]) + tx_dec = self.rpc('decoderawtransaction', [tx_hex]) if 'hex' not in tx_dec: tx_dec['hex'] = tx_hex @@ -505,7 +514,7 @@ class NAVInterface(BTCInterface): 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) -> bytes: self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex()) - wtx = self.rpc_callback('gettransaction', [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) @@ -550,7 +559,7 @@ class NAVInterface(BTCInterface): def findTxnByHash(self, txid_hex: str): # Only works for wallet txns try: - rv = self.rpc_callback('gettransaction', [txid_hex]) + rv = self.rpc('gettransaction', [txid_hex]) except Exception as ex: self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) return None @@ -573,10 +582,10 @@ class NAVInterface(BTCInterface): 'lockUnspents': lock_unspents, 'feeRate': feerate_str, } - rv = self.rpc_callback('fundrawtransaction', [tx_hex, options]) + rv = self.rpc('fundrawtransaction', [tx_hex, options]) # Sign transaction then strip witness data to fill scriptsig - rv = self.rpc_callback('signrawtransaction', [rv['hex']]) + rv = self.rpc('signrawtransaction', [rv['hex']]) tx_signed = self.loadTx(bytes.fromhex(rv['hex'])) if len(tx_signed.vin) != len(tx_signed.wit.vtxinwit): diff --git a/basicswap/interface/nmc.py b/basicswap/interface/nmc.py index 711dc50..b9bd8de 100644 --- a/basicswap/interface/nmc.py +++ b/basicswap/interface/nmc.py @@ -19,7 +19,7 @@ class NMCInterface(BTCInterface): def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index=False): self._log.debug('[rm] scantxoutset start') # scantxoutset is slow - ro = self.rpc_callback('scantxoutset', ['start', ['addr({})'.format(dest_address)]]) # TODO: Use combo(address) where possible + 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']: diff --git a/basicswap/interface/part.py b/basicswap/interface/part.py index d7f48b0..38d3f58 100644 --- a/basicswap/interface/part.py +++ b/basicswap/interface/part.py @@ -83,14 +83,14 @@ class PARTInterface(BTCInterface): return True def getNewAddress(self, use_segwit, label='swap_receive') -> str: - return self.rpc_callback('getnewaddress', [label]) + return self.rpc_wallet('getnewaddress', [label]) def getNewStealthAddress(self, label='swap_stealth') -> str: - return self.rpc_callback('getnewstealthaddress', [label]) + return self.rpc_wallet('getnewstealthaddress', [label]) def haveSpentIndex(self): version = self.getDaemonVersion() - index_info = self.rpc_callback('getinsightinfo' if int(str(version)[:2]) > 19 else 'getindexinfo') + index_info = self.rpc('getinsightinfo' if int(str(version)[:2]) > 19 else 'getindexinfo') return index_info['spentindex'] def initialiseWallet(self, key): @@ -98,14 +98,14 @@ class PARTInterface(BTCInterface): def withdrawCoin(self, value, addr_to, subfee): params = [addr_to, value, '', '', subfee, '', True, self._conf_target] - return self.rpc_callback('sendtoaddress', params) + 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_callback('sendtypeto', params) + return self.rpc_wallet('sendtypeto', params) def getScriptForPubkeyHash(self, pkh: bytes) -> CScript: return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]) @@ -122,7 +122,7 @@ class PARTInterface(BTCInterface): return length def getWalletRestoreHeight(self) -> int: - start_time = self.rpc_callback('getwalletinfo')['keypoololdest'] + start_time = self.rpc_wallet('getwalletinfo')['keypoololdest'] blockchaininfo = self.getBlockchainInfo() best_block = blockchaininfo['bestblockhash'] @@ -132,8 +132,8 @@ class PARTInterface(BTCInterface): raise ValueError('{} chain isn\'t synced.'.format(self.coin_name())) self._log.debug('Finding block at time: {}'.format(start_time)) - block_hash = self.rpc_callback('getblockhashafter', [start_time]) - block_header = self.rpc_callback('getblockheader', [block_hash]) + 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: @@ -171,16 +171,16 @@ class PARTInterfaceBlind(PARTInterface): if txo['type'] != 'blind': continue try: - blinded_info = self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()]) + blinded_info = self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()]) output_n = txo['n'] - self.rpc_callback('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))) continue # Should not be possible for commitment not to match - v = self.rpc_callback('verifycommitment', [txo['valueCommitment'], blinded_info['blind'], blinded_info['amount']]) + v = self.rpc('verifycommitment', [txo['valueCommitment'], blinded_info['blind'], blinded_info['amount']]) ensure(v['result'] is True, 'verifycommitment failed') return output_n, blinded_info @@ -195,7 +195,7 @@ class PARTInterfaceBlind(PARTInterface): inputs = [] outputs = [{'type': 'blind', 'amount': self.format_amount(value), 'address': p2wsh_addr, 'nonce': nonce.hex(), 'data': ephemeral_pubkey.hex()}] params = [inputs, outputs] - rv = self.rpc_callback('createrawparttransaction', params) + rv = self.rpc_wallet('createrawparttransaction', params) tx_bytes = bytes.fromhex(rv['hex']) return tx_bytes @@ -207,11 +207,11 @@ class PARTInterfaceBlind(PARTInterface): tx_hex = tx_bytes.hex() nonce = self.getScriptLockTxNonce(vkbv) - tx_obj = self.rpc_callback('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_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], nonce.hex()]) + 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()}} @@ -219,11 +219,11 @@ class PARTInterfaceBlind(PARTInterface): 'lockUnspents': True, 'feeRate': feerate_str, } - rv = self.rpc_callback('fundrawtransactionfrom', ['blind', tx_hex, {}, outputs_info, options]) + 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_callback('decoderawtransaction', [tx_lock_bytes.hex()]) + 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() @@ -244,7 +244,7 @@ class PARTInterfaceBlind(PARTInterface): 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_callback('createrawparttransaction', params) + rv = self.rpc_wallet('createrawparttransaction', params) lock_refund_tx_hex = rv['hex'] # Set dummy witness data for fee estimation @@ -261,7 +261,7 @@ class PARTInterfaceBlind(PARTInterface): 'feeRate': self.format_amount(tx_fee_rate), 'subtractFeeFromOutputs': [0, ] } - rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_refund_tx_hex, inputs_info, outputs_info, options]) + 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(): @@ -275,7 +275,7 @@ class PARTInterfaceBlind(PARTInterface): # 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_callback('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) @@ -285,7 +285,7 @@ class PARTInterfaceBlind(PARTInterface): tx_lock_refund_id = lock_refund_tx_obj['txid'] addr_out = self.pkh_to_address(pkh_refund_to) - addr_info = self.rpc_callback('getaddressinfo', [addr_out]) + 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 @@ -293,7 +293,7 @@ class PARTInterfaceBlind(PARTInterface): 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_callback('createrawparttransaction', params) + rv = self.rpc_wallet('createrawparttransaction', params) lock_refund_spend_tx_hex = rv['hex'] # Set dummy witness data for fee estimation @@ -311,7 +311,7 @@ class PARTInterfaceBlind(PARTInterface): 'subtractFeeFromOutputs': [0, ] } - rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_refund_spend_tx_hex, inputs_info, outputs_info, options]) + 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) @@ -321,7 +321,7 @@ class PARTInterfaceBlind(PARTInterface): Kal, Kaf, feerate, check_lock_tx_inputs, vkbv): - lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()]) + 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)) @@ -363,7 +363,7 @@ class PARTInterfaceBlind(PARTInterface): 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_callback('decoderawtransaction', [tx_bytes.hex()]) + 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)) @@ -396,10 +396,10 @@ class PARTInterfaceBlind(PARTInterface): ensure(C == Kaf, 'Bad script pubkey') # Check rangeproofs and commitments sum - lock_tx_obj = self.rpc_callback('decoderawtransaction', [lock_tx_bytes.hex()]) + 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_callback('verifyrawtransaction', [tx_bytes.hex(), prevtxns]) + rv = self.rpc('verifyrawtransaction', [tx_bytes.hex(), prevtxns]) ensure(rv['outputs_valid'] is True, 'Invalid outputs') ensure(rv['inputs_valid'] is True, 'Invalid inputs') @@ -422,7 +422,7 @@ class PARTInterfaceBlind(PARTInterface): lock_refund_tx_id, prevout_script, Kal, prevout_n, prevout_value, feerate, vkbv): - lock_refund_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()]) + 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)) @@ -441,10 +441,10 @@ class PARTInterfaceBlind(PARTInterface): # Follower is not concerned with them as they pay to leader # Check rangeproofs and commitments sum - lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [lock_refund_tx_bytes.hex()]) + 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_callback('verifyrawtransaction', [tx_bytes.hex(), prevtxns]) + rv = self.rpc('verifyrawtransaction', [tx_bytes.hex(), prevtxns]) ensure(rv['outputs_valid'] is True, 'Invalid outputs') ensure(rv['inputs_valid'] is True, 'Invalid inputs') @@ -459,28 +459,28 @@ class PARTInterfaceBlind(PARTInterface): return True def getLockTxSwapOutputValue(self, bid, xmr_swap): - lock_tx_obj = self.rpc_callback('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']) def getLockRefundTxSwapOutputValue(self, bid, xmr_swap): - lock_refund_tx_obj = self.rpc_callback('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']) def getLockRefundTxSwapOutput(self, xmr_swap): - lock_refund_tx_obj = self.rpc_callback('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 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_callback('decoderawtransaction', [tx_lock_bytes.hex()]) + 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') @@ -496,7 +496,7 @@ class PARTInterfaceBlind(PARTInterface): 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_callback('createrawparttransaction', params) + rv = self.rpc_wallet('createrawparttransaction', params) lock_spend_tx_hex = rv['hex'] # Set dummy witness data for fee estimation @@ -513,9 +513,9 @@ class PARTInterfaceBlind(PARTInterface): 'subtractFeeFromOutputs': [0, ] } - rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_spend_tx_hex, inputs_info, outputs_info, options]) + 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_callback('decoderawtransaction', [lock_spend_tx_hex]) + lock_spend_tx_obj = self.rpc('decoderawtransaction', [lock_spend_tx_hex]) pay_fee = make_int(lock_spend_tx_obj['vout'][0]['ct_fee']) # lock_spend_tx_hex does not include the dummy witness stack @@ -535,7 +535,7 @@ class PARTInterfaceBlind(PARTInterface): def verifySCLockSpendTx(self, tx_bytes, lock_tx_bytes, lock_tx_script, a_pk_f, feerate, vkbv): - lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()]) + 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)) @@ -543,7 +543,7 @@ class PARTInterfaceBlind(PARTInterface): 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_callback('decoderawtransaction', [lock_tx_bytes.hex()]) + 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 @@ -559,7 +559,7 @@ class PARTInterfaceBlind(PARTInterface): 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_callback('dumpprivkey', [addr_out]) + privkey = self.rpc_wallet('dumpprivkey', [addr_out]) # Find output: output_blinded_info = None @@ -568,7 +568,7 @@ class PARTInterfaceBlind(PARTInterface): if txo['type'] != 'blind': continue try: - output_blinded_info = self.rpc_callback('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], privkey, txo['data_hex']]) + output_blinded_info = self.rpc('rewindrangeproof', [txo['rangeproof'], txo['valueCommitment'], privkey, txo['data_hex']]) output_n = txo['n'] break except Exception as e: @@ -577,13 +577,13 @@ class PARTInterfaceBlind(PARTInterface): ensure(output_n is not None, 'Output not found in tx') # Commitment - v = self.rpc_callback('verifycommitment', [lock_spend_tx_obj['vout'][output_n]['valueCommitment'], output_blinded_info['blind'], output_blinded_info['amount']]) + 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_callback('verifyrawtransaction', [tx_bytes.hex(), prevtxns]) + rv = self.rpc('verifyrawtransaction', [tx_bytes.hex(), prevtxns]) ensure(rv['outputs_valid'] is True, 'Invalid outputs') ensure(rv['inputs_valid'] is True, 'Invalid inputs') @@ -607,7 +607,7 @@ class PARTInterfaceBlind(PARTInterface): def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv): # lock refund swipe tx # Sends the coinA locked coin to the follower - lock_refund_tx_obj = self.rpc_callback('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 @@ -616,7 +616,7 @@ class PARTInterfaceBlind(PARTInterface): tx_lock_refund_id = lock_refund_tx_obj['txid'] addr_out = self.pkh_to_address(pkh_dest) - addr_info = self.rpc_callback('getaddressinfo', [addr_out]) + addr_info = self.rpc_wallet('getaddressinfo', [addr_out]) output_pubkey_hex = addr_info['pubkey'] A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund) @@ -626,7 +626,7 @@ class PARTInterfaceBlind(PARTInterface): 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_callback('createrawparttransaction', params) + rv = self.rpc_wallet('createrawparttransaction', params) lock_refund_swipe_tx_hex = rv['hex'] @@ -645,13 +645,13 @@ class PARTInterfaceBlind(PARTInterface): 'subtractFeeFromOutputs': [0, ] } - rv = self.rpc_callback('fundrawtransactionfrom', ['blind', lock_refund_swipe_tx_hex, inputs_info, outputs_info, options]) + 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_callback('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, delay_for: int = 10, unlock_time: int = 0) -> bytes: Kbv = self.getPubkey(vkbv) @@ -664,7 +664,7 @@ class PARTInterfaceBlind(PARTInterface): '', '', self._anon_tx_ring_size, 1, False, {'conf_target': self._conf_target, 'blind_watchonly_visible': True}] - txid = self.rpc_callback('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): @@ -675,17 +675,17 @@ class PARTInterfaceBlind(PARTInterface): if bid_sender: cb_swap_value *= -1 else: - addr_info = self.rpc_callback('getaddressinfo', [sx_addr]) + addr_info = self.rpc_wallet('getaddressinfo', [sx_addr]) if not addr_info['iswatchonly']: wif_prefix = self.chainparams_network()['key_prefix'] wif_scan_key = toWIF(wif_prefix, kbv) - self.rpc_callback('importstealthaddress', [wif_scan_key, Kbs.hex()]) + 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_callback('rescanblockchain', [restore_height]) + self.rpc_wallet('rescanblockchain', [restore_height]) params = [{'include_watchonly': True, 'search': sx_addr}] - txns = self.rpc_callback('filtertransactions', params) + txns = self.rpc_wallet('filtertransactions', params) if len(txns) == 1: tx = txns[0] @@ -695,7 +695,7 @@ class PARTInterfaceBlind(PARTInterface): if make_int(tx['outputs'][0]['amount']) == cb_swap_value: height = 0 if tx['confirmations'] > 0: - chain_height = self.rpc_callback('getblockcount') + chain_height = self.rpc('getblockcount') height = chain_height - (tx['confirmations'] - 1) return {'txid': tx['txid'], 'amount': cb_swap_value, 'height': height} else: @@ -707,20 +707,20 @@ class PARTInterfaceBlind(PARTInterface): Kbv = self.getPubkey(kbv) Kbs = self.getPubkey(kbs) sx_addr = self.formatStealthAddress(Kbv, Kbs) - addr_info = self.rpc_callback('getaddressinfo', [sx_addr]) + addr_info = self.rpc_wallet('getaddressinfo', [sx_addr]) if not addr_info['ismine']: wif_prefix = self.chainparams_network()['key_prefix'] wif_scan_key = toWIF(wif_prefix, kbv) wif_spend_key = toWIF(wif_prefix, kbs) - self.rpc_callback('importstealthaddress', [wif_scan_key, wif_spend_key]) + 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_callback('rescanblockchain', [restore_height]) + self.rpc_wallet('rescanblockchain', [restore_height]) # TODO: Remove workaround - # utxos = self.rpc_callback('listunspentblind', [1, 9999999, [sx_addr]]) + # utxos = self.rpc_wallet('listunspentblind', [1, 9999999, [sx_addr]]) utxos = [] - all_utxos = self.rpc_callback('listunspentblind', [1, 9999999]) + all_utxos = self.rpc_wallet('listunspentblind', [1, 9999999]) for utxo in all_utxos: if utxo.get('stealth_address', '_') == sx_addr: utxos.append(utxo) @@ -741,14 +741,14 @@ class PARTInterfaceBlind(PARTInterface): [{'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_callback('sendtypeto', params) + 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_callback('getrawtransaction', [txid_hex, True]) + rv = self.rpc('getrawtransaction', [txid_hex, True]) except Exception as ex: self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) return None @@ -759,7 +759,7 @@ class PARTInterfaceBlind(PARTInterface): return None def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str: - txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) + txn = self.rpc_wallet('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) options = { 'lockUnspents': lock_unspents, @@ -767,7 +767,7 @@ class PARTInterfaceBlind(PARTInterface): } if sub_fee: options['subtractFeeFromOutputs'] = [0,] - return self.rpc_callback('fundrawtransactionfrom', ['blind', txn, options])['hex'] + return self.rpc_wallet('fundrawtransactionfrom', ['blind', txn, options])['hex'] class PARTInterfaceAnon(PARTInterface): @@ -801,7 +801,7 @@ class PARTInterfaceAnon(PARTInterface): '', '', self._anon_tx_ring_size, 1, False, {'conf_target': self._conf_target, 'blind_watchonly_visible': True}] - txid = self.rpc_callback('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): @@ -813,17 +813,17 @@ class PARTInterfaceAnon(PARTInterface): if bid_sender: cb_swap_value *= -1 else: - addr_info = self.rpc_callback('getaddressinfo', [sx_addr]) + addr_info = self.rpc_wallet('getaddressinfo', [sx_addr]) if not addr_info['iswatchonly']: wif_prefix = self.chainparams_network()['key_prefix'] wif_scan_key = toWIF(wif_prefix, kbv) - self.rpc_callback('importstealthaddress', [wif_scan_key, Kbs.hex()]) + 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_callback('rescanblockchain', [restore_height]) + self.rpc_wallet('rescanblockchain', [restore_height]) params = [{'include_watchonly': True, 'search': sx_addr}] - txns = self.rpc_callback('filtertransactions', params) + txns = self.rpc_wallet('filtertransactions', params) if len(txns) == 1: tx = txns[0] @@ -833,7 +833,7 @@ class PARTInterfaceAnon(PARTInterface): if make_int(tx['outputs'][0]['amount']) == cb_swap_value: height = 0 if tx['confirmations'] > 0: - chain_height = self.rpc_callback('getblockcount') + chain_height = self.rpc('getblockcount') height = chain_height - (tx['confirmations'] - 1) return {'txid': tx['txid'], 'amount': cb_swap_value, 'height': height} else: @@ -845,17 +845,17 @@ class PARTInterfaceAnon(PARTInterface): Kbv = self.getPubkey(kbv) Kbs = self.getPubkey(kbs) sx_addr = self.formatStealthAddress(Kbv, Kbs) - addr_info = self.rpc_callback('getaddressinfo', [sx_addr]) + addr_info = self.rpc_wallet('getaddressinfo', [sx_addr]) if not addr_info['ismine']: wif_prefix = self.chainparams_network()['key_prefix'] wif_scan_key = toWIF(wif_prefix, kbv) wif_spend_key = toWIF(wif_prefix, kbs) - self.rpc_callback('importstealthaddress', [wif_scan_key, wif_spend_key]) + 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_callback('rescanblockchain', [restore_height]) + self.rpc_wallet('rescanblockchain', [restore_height]) - autxos = self.rpc_callback('listunspentanon', [1, 9999999, [sx_addr]]) + autxos = self.rpc_wallet('listunspentanon', [1, 9999999, [sx_addr]]) if len(autxos) < 1: raise TemporaryError('No spendable outputs') @@ -874,14 +874,14 @@ class PARTInterfaceAnon(PARTInterface): [{'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_callback('sendtypeto', params) + 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_callback('getrawtransaction', [txid_hex, True]) + rv = self.rpc('getrawtransaction', [txid_hex, True]) except Exception as ex: self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) return None @@ -892,4 +892,4 @@ class PARTInterfaceAnon(PARTInterface): return None def getSpendableBalance(self) -> int: - return self.make_int(self.rpc_callback('getbalances')['mine']['anon_trusted']) + return self.make_int(self.rpc_wallet('getbalances')['mine']['anon_trusted']) diff --git a/basicswap/interface/pivx.py b/basicswap/interface/pivx.py index 6c1b017..5d29853 100644 --- a/basicswap/interface/pivx.py +++ b/basicswap/interface/pivx.py @@ -8,6 +8,7 @@ from io import BytesIO 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 ( @@ -29,12 +30,20 @@ class PIVXInterface(BTCInterface): def coin_type(): return Coins.PIVX + 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) + + def checkWallets(self) -> int: + return 1 + def signTxWithWallet(self, tx): - rv = self.rpc_callback('signrawtransaction', [tx.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_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) + 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}') options = { @@ -43,25 +52,25 @@ class PIVXInterface(BTCInterface): } if sub_fee: options['subtractFeeFromOutputs'] = [0,] - return self.rpc_callback('fundrawtransaction', [txn, options])['hex'] + return self.rpc('fundrawtransaction', [txn, options])['hex'] def createRawSignedTransaction(self, addr_to, amount) -> str: txn_funded = self.createRawFundedTransaction(addr_to, amount) - return self.rpc_callback('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_callback('getblock', [block_hash, False]) - block_header = self.rpc_callback('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_callback('decoderawtransaction', [ToHex(tx)]) + tx_dec = self.rpc('decoderawtransaction', [ToHex(tx)]) tx_rv.append(tx_dec) block_rv = { @@ -77,10 +86,10 @@ class PIVXInterface(BTCInterface): def withdrawCoin(self, value, addr_to, subfee): params = [addr_to, value, '', '', subfee] - return self.rpc_callback('sendtoaddress', params) + return self.rpc('sendtoaddress', params) def getSpendableBalance(self) -> int: - return self.make_int(self.rpc_callback('getwalletinfo')['balance']) + return self.make_int(self.rpc('getwalletinfo')['balance']) def loadTx(self, tx_bytes): # Load tx from bytes to internal representation @@ -101,13 +110,13 @@ class PIVXInterface(BTCInterface): def signTxWithKey(self, tx: bytes, key: bytes) -> bytes: key_wif = self.encodeKey(key) - rv = self.rpc_callback('signrawtransaction', [tx.hex(), [], [key_wif, ]]) + 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_callback('gettransaction', [txid_hex]) + rv = self.rpc('gettransaction', [txid_hex]) except Exception as ex: self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) return None diff --git a/basicswap/interface/xmr.py b/basicswap/interface/xmr.py index 4628e12..159f9c1 100644 --- a/basicswap/interface/xmr.py +++ b/basicswap/interface/xmr.py @@ -83,9 +83,9 @@ class XMRInterface(CoinInterface): daemon_login = None if coin_settings.get('rpcuser', '') != '': daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', '')) - self.rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1')) - self.rpc_cb2 = make_xmr_rpc2_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1')) # non-json endpoint - self.rpc_wallet_cb = make_xmr_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1')) + self.rpc = make_xmr_rpc_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1')) + self.rpc2 = make_xmr_rpc2_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1')) # 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')) self.blocks_confirmed = coin_settings['blocks_confirmed'] self._restore_height = coin_settings.get('restore_height', 0) @@ -95,6 +95,9 @@ class XMRInterface(CoinInterface): self._wallet_password = None self._have_checked_seed = False + def checkWallets(self) -> int: + return 1 + def setFeePriority(self, new_priority): ensure(new_priority >= 0 and new_priority < 4, 'Invalid fee_priority value') self._fee_priority = new_priority @@ -105,7 +108,7 @@ class XMRInterface(CoinInterface): def createWallet(self, params): if self._wallet_password is not None: params['password'] = self._wallet_password - rv = self.rpc_wallet_cb('generate_from_keys', params) + rv = self.rpc_wallet('generate_from_keys', params) self._log.info('generate_from_keys %s', dumpj(rv)) def openWallet(self, filename): @@ -115,10 +118,10 @@ class XMRInterface(CoinInterface): try: # Can't reopen the same wallet in windows, !is_keys_file_locked() - self.rpc_wallet_cb('close_wallet') + self.rpc_wallet('close_wallet') except Exception: pass - self.rpc_wallet_cb('open_wallet', params) + self.rpc_wallet('open_wallet', params) def initialiseWallet(self, key_view, key_spend, restore_height=None): with self._mx_wallet: @@ -147,14 +150,14 @@ class XMRInterface(CoinInterface): with self._mx_wallet: self.openWallet(self._wallet_filename) - def testDaemonRPC(self, with_wallet=True): - self.rpc_wallet_cb('get_languages') + def testDaemonRPC(self, with_wallet=True) -> None: + self.rpc_wallet('get_languages') def getDaemonVersion(self): - return self.rpc_wallet_cb('get_version')['version'] + return self.rpc_wallet('get_version')['version'] def getBlockchainInfo(self): - get_height = self.rpc_cb2('get_height', timeout=30) + get_height = self.rpc2('get_height', timeout=30) rv = { 'blocks': get_height['height'], 'verificationprogress': 0.0, @@ -165,7 +168,7 @@ class XMRInterface(CoinInterface): # get_block_count returns "Internal error" if bootstrap-daemon is active if get_height['untrusted'] is True: rv['bootstrapping'] = True - get_info = self.rpc_cb2('get_info', timeout=30) + get_info = self.rpc2('get_info', timeout=30) if 'height_without_bootstrap' in get_info: rv['blocks'] = get_info['height_without_bootstrap'] @@ -173,7 +176,7 @@ class XMRInterface(CoinInterface): if rv['known_block_count'] > rv['blocks']: rv['verificationprogress'] = rv['blocks'] / rv['known_block_count'] else: - rv['known_block_count'] = self.rpc_cb('get_block_count', timeout=30)['count'] + rv['known_block_count'] = self.rpc('get_block_count', timeout=30)['count'] rv['verificationprogress'] = rv['blocks'] / rv['known_block_count'] except Exception as e: self._log.warning('XMR get_block_count failed with: %s', str(e)) @@ -182,7 +185,7 @@ class XMRInterface(CoinInterface): return rv def getChainHeight(self): - return self.rpc_cb2('get_height', timeout=30)['height'] + return self.rpc2('get_height', timeout=30)['height'] def getWalletInfo(self): with self._mx_wallet: @@ -195,8 +198,8 @@ class XMRInterface(CoinInterface): raise e rv = {} - self.rpc_wallet_cb('refresh') - balance_info = self.rpc_wallet_cb('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 @@ -209,13 +212,13 @@ class XMRInterface(CoinInterface): def getMainWalletAddress(self) -> str: with self._mx_wallet: self.openWallet(self._wallet_filename) - return self.rpc_wallet_cb('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_cb('create_address', {'account_index': 0})['address'] - self.rpc_wallet_cb('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): @@ -280,7 +283,7 @@ class XMRInterface(CoinInterface): def publishBLockTx(self, kbv: bytes, Kbs: bytes, output_amount: int, feerate: int, delay_for: int = 10, unlock_time: int = 0) -> bytes: with self._mx_wallet: self.openWallet(self._wallet_filename) - self.rpc_wallet_cb('refresh') + self.rpc_wallet('refresh') Kbv = self.getPubkey(kbv) shared_addr = xmr_util.encode_address(Kbv, Kbs) @@ -288,7 +291,7 @@ class XMRInterface(CoinInterface): 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_cb('transfer', params) + 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']) @@ -296,7 +299,7 @@ class XMRInterface(CoinInterface): i = 0 while not self._sc.delay_event.is_set(): gt_params = {'out': True, 'pending': True, 'failed': True, 'pool': True, } - rv = self.rpc_wallet_cb('get_transfers', gt_params) + rv = self.rpc_wallet('get_transfers', gt_params) self._log.debug('get_transfers {}'.format(dumpj(rv))) if 'pending' not in rv: break @@ -325,26 +328,26 @@ class XMRInterface(CoinInterface): self.createWallet(params) self.openWallet(address_b58) - self.rpc_wallet_cb('refresh', timeout=600) + self.rpc_wallet('refresh', timeout=600) ''' # Debug try: - current_height = self.rpc_wallet_cb('get_height')['height'] + current_height = self.rpc_wallet('get_height')['height'] self._log.info('findTxB XMR current_height %d\nAddress: %s', current_height, address_b58) except Exception as e: - self._log.info('rpc_cb failed %s', str(e)) + 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_cb('incoming_transfers', params) + transfers = self.rpc_wallet('incoming_transfers', params) rv = None 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_cb('get_transfer_by_txid', {'txid': transfer['tx_hash']}) + 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)) @@ -360,17 +363,17 @@ class XMRInterface(CoinInterface): def findTxnByHash(self, txid): with self._mx_wallet: self.openWallet(self._wallet_filename) - self.rpc_wallet_cb('refresh', timeout=600) + self.rpc_wallet('refresh', timeout=600) try: - current_height = self.rpc_cb2('get_height', timeout=30)['height'] + current_height = self.rpc2('get_height', timeout=30)['height'] self._log.info('findTxnByHash XMR current_height %d\nhash: %s', current_height, txid) except Exception as e: - self._log.info('rpc_cb failed %s', str(e)) + 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_cb('incoming_transfers', params) + rv = self.rpc_wallet('incoming_transfers', params) if 'transfers' in rv: for transfer in rv['transfers']: if transfer['tx_hash'] == txid \ @@ -405,11 +408,11 @@ class XMRInterface(CoinInterface): self.createWallet(params) self.openWallet(wallet_filename) - self.rpc_wallet_cb('refresh') - rv = self.rpc_wallet_cb('get_balance') + 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_cb('get_transfers', {'out': True}) + txns = self.rpc_wallet('get_transfers', {'out': True}) if 'out' in txns: txns = txns['out'] if len(txns) > 0: @@ -434,7 +437,7 @@ class XMRInterface(CoinInterface): if self._fee_priority > 0: params['priority'] = self._fee_priority - rv = self.rpc_wallet_cb('sweep_all', params) + rv = self.rpc_wallet('sweep_all', params) self._log.debug('sweep_all {}'.format(json.dumps(rv))) return bytes.fromhex(rv['tx_hash_list'][0]) @@ -444,24 +447,24 @@ class XMRInterface(CoinInterface): value_sats = make_int(value, self.exp()) self.openWallet(self._wallet_filename) - self.rpc_wallet_cb('refresh') + self.rpc_wallet('refresh') if subfee: - balance = self.rpc_wallet_cb('get_balance') + balance = self.rpc_wallet('get_balance') diff = balance['unlocked_balance'] - value_sats if diff >= 0 and diff <= 10: self._log.info('subfee enabled and value close to total, using sweep_all.') params = {'address': addr_to} if self._fee_priority > 0: params['priority'] = self._fee_priority - rv = self.rpc_wallet_cb('sweep_all', params) + rv = self.rpc_wallet('sweep_all', params) return rv['tx_hash_list'][0] raise ValueError('Withdraw value must be close to total to use subfee/sweep_all.') params = {'destinations': [{'amount': value_sats, 'address': addr_to}]} if self._fee_priority > 0: params['priority'] = self._fee_priority - rv = self.rpc_wallet_cb('transfer', params) + rv = self.rpc_wallet('transfer', params) return rv['tx_hash'] def showLockTransfers(self, kbv, Kbs, restore_height): @@ -488,9 +491,9 @@ class XMRInterface(CoinInterface): self.createWallet(params) self.openWallet(address_b58) - self.rpc_wallet_cb('refresh') + self.rpc_wallet('refresh') - rv = self.rpc_wallet_cb('get_transfers', {'in': True, 'out': True, 'pending': True, 'failed': True}) + rv = self.rpc_wallet('get_transfers', {'in': True, 'out': True, 'pending': True, 'failed': True}) rv['filename'] = wallet_file return rv except Exception as e: @@ -500,8 +503,8 @@ class XMRInterface(CoinInterface): with self._mx_wallet: self.openWallet(self._wallet_filename) - self.rpc_wallet_cb('refresh') - balance_info = self.rpc_wallet_cb('get_balance') + self.rpc_wallet('refresh') + balance_info = self.rpc_wallet('get_balance') return balance_info['unlocked_balance'] def changeWalletPassword(self, old_password, new_password): @@ -511,7 +514,7 @@ class XMRInterface(CoinInterface): self._wallet_password = old_password try: self.openWallet(self._wallet_filename) - self.rpc_wallet_cb('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 @@ -536,4 +539,4 @@ class XMRInterface(CoinInterface): raise ValueError('Balance too low') def getTransaction(self, txid: bytes): - return self.rpc_cb2('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 9a28502..6d95456 100644 --- a/basicswap/js_server.py +++ b/basicswap/js_server.py @@ -63,6 +63,9 @@ def withdraw_coin(swap_client, coin_type, post_string, is_json): 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') + txid_hex = swap_client.withdrawLTC(type_from, value, address, subfee) else: txid_hex = swap_client.withdrawCoin(coin_type, value, address, subfee) @@ -92,6 +95,8 @@ def js_coins(self, url_split, post_string, is_json) -> bytes: entry['variant'] = 'Anon' elif coin == Coins.PART_BLIND: entry['variant'] = 'Blind' + elif coin == Coins.LTC_MWEB: + entry['variant'] = 'MWEB' coins.append(entry) return bytes(json.dumps(coins), 'UTF-8') @@ -108,19 +113,30 @@ def js_wallets(self, url_split, post_string, is_json): cmd = url_split[4] if cmd == 'withdraw': return bytes(json.dumps(withdraw_coin(swap_client, coin_type, post_string, is_json)), 'UTF-8') - if cmd == 'nextdepositaddr': + elif cmd == 'nextdepositaddr': return bytes(json.dumps(swap_client.cacheNewAddressForCoin(coin_type)), 'UTF-8') - if cmd == 'createutxo': + 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')) txid_hex, new_addr = ci.createUTXO(value) return bytes(json.dumps({'txid': txid_hex, 'address': new_addr}), 'UTF-8') - if cmd == 'reseed': + elif cmd == 'reseed': swap_client.reseedWallet(coin_type) 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': + 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('Unknown command') + if coin_type == Coins.LTC_MWEB: + coin_type = Coins.LTC rv = swap_client.getWalletInfo(coin_type) rv.update(swap_client.getBlockchainInfo(coin_type)) ci = swap_client.ci(coin_type) @@ -647,7 +663,7 @@ def js_setpassword(self, url_split, post_string, is_json) -> bytes: if have_data_entry(post_data, 'coin'): # Set password for one coin coin = getCoinType(get_data_entry(post_data, 'coin')) - if coin in (Coins.PART_ANON, Coins.PART_BLIND): + if coin in (Coins.PART_ANON, Coins.PART_BLIND, Coins.LTC_MWEB): raise ValueError('Invalid coin.') swap_client.changeWalletPasswords(old_password, new_password, coin) return bytes(json.dumps({'success': True}), 'UTF-8') diff --git a/basicswap/templates/style.html b/basicswap/templates/style.html new file mode 100644 index 0000000..f8db68b --- /dev/null +++ b/basicswap/templates/style.html @@ -0,0 +1,12 @@ +{% set select_box_arrow_svg = ' + +' %} + +{% set select_box_class = 'hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-white text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0' %} + +{% set circular_arrows_svg = ' + + + + +' %} diff --git a/basicswap/templates/wallet.html b/basicswap/templates/wallet.html index 728a7ae..4c615bd 100644 --- a/basicswap/templates/wallet.html +++ b/basicswap/templates/wallet.html @@ -1,4 +1,6 @@ {% include 'header.html' %} +{% from 'style.html' import select_box_arrow_svg, select_box_class, circular_arrows_svg %} +
@@ -47,12 +49,7 @@ @@ -165,7 +162,7 @@ {{ w.balance }} {{ w.ticker }} (){% if w.unconfirmed %} Unconfirmed: +{{ w.unconfirmed }} {{ w.ticker }}{% endif %} - {% if w.cid == '1' %} + {% if w.cid == '1' %} {# PART #} @@ -180,9 +177,21 @@ {{ w.name }} Anon Anon Balance: - {{ w.anon_balance }} {{ w.ticker }} () {% if w.anon_pending %} Pending: +{{ w.anon_pending }} {{ w.ticker }}{% endif %} + {{ w.anon_balance }} {{ w.ticker }} () {% if w.anon_pending %} Pending: +{{ w.anon_pending }} {{ w.ticker }}{% endif %} - {% endif %} + + {% endif %} {# / PART #} + {% if w.cid == '3' %} {# LTC #} + + + + {{ w.name }} MWEB + MWEB Balance: + + {{ w.mweb_balance }} {{ w.ticker }} () {% if w.mweb_pending %} Pending: +{{ w.mweb_pending }} {{ w.ticker }}{% endif %} + + {% endif %} {# / LTC #} + Blocks: {{ w.blocks }} {% if w.known_block_count %} / {{ w.known_block_count }} {% endif %} @@ -192,13 +201,13 @@ {% if w.bootstrapping %} Bootstrapping: {{ w.bootstrapping }} - {% endif %} + {% endif %} {# / bootstrapping #} {% if w.encrypted %} Locked: {{ w.locked }} - {% endif %} + {% endif %} {# / encrypted #} Expected Seed: {{ w.expected_seed }} {% if block_unknown_seeds and w.expected_seed != true %} {# Only show addresses if wallet seed is correct #} @@ -235,13 +244,22 @@ {% else %} - {% if w.cid == '1' %} + {% if w.cid == '1' %} {# PART #} Stealth Address {{ w.stealth_address }} - {% endif %} - {% if w.cid == '6' %} + {% endif %} {# / PART #} + {% if w.cid == '3' %} {# LTC #} + + + + + {{ w.mweb_address }} + + {% endif %} {# / LTC #} + {% if w.cid == '6' %} {# XMR #} Main Address {{ w.main_address }} @@ -249,12 +267,7 @@ + {{ circular_arrows_svg }} New {{ w.ticker }} Sub Address {{ w.deposit_address }} @@ -262,12 +275,7 @@ + {{ circular_arrows_svg }} New {{ w.ticker }} Deposit Address {{ w.deposit_address }} @@ -342,16 +350,14 @@ - {% if w.cid == '1' %} + {% if w.cid == '1' %} {# PART #} Type From -> To:
- - - - @@ -362,10 +368,8 @@
- - - - @@ -374,7 +378,23 @@
- {% endif %} + {% endif %} {# / PART #} + {% if w.cid == '3' %} {# LTC #} + + Type From: + +
+
+ {{ select_box_arrow_svg }} + +
+
+ + + {% endif %} {# / LTC #} Fee Rate: {{ w.fee_rate }} @@ -392,7 +412,7 @@
-{% if w.cid != '6' %} +{% if w.cid != '6' %} {# !XMR #} {% if w.show_utxo_groups %}
@@ -647,4 +667,4 @@ document.addEventListener('DOMContentLoaded', () => { } - \ No newline at end of file + diff --git a/basicswap/templates/wallets.html b/basicswap/templates/wallets.html index e666b73..7c3a3fc 100644 --- a/basicswap/templates/wallets.html +++ b/basicswap/templates/wallets.html @@ -113,7 +113,7 @@
{% endif %} - {% if w.cid == '1' %} + {% if w.cid == '1' %} {# PART #}

Blind Balance:

{{ w.blind_balance }} {{ w.ticker }} @@ -130,7 +130,7 @@

Blind Unconfirmed USD value:

-
+
{% endif %}

Anon Balance:

@@ -149,9 +149,30 @@

Anon Pending USD value:

-
+
{% endif %} + {% endif %} {# / PART #} + {% if w.cid == '3' %} {# LTC #} +
+

MWEB Balance:

+ {{ w.mweb_balance }} {{ w.ticker }} +
+
+

MWEB USD value:

+
+
+ {% if w.mweb_pending %} +
+

MWEB Pending:

+ + +{{ w.mweb_pending }} {{ w.ticker }} +
+
+

MWEB Pending USD value:

+
+
{% endif %} + {% endif %} {# / LTC #}

Blocks:

{{ w.blocks }}{% if w.known_block_count %} / {{ w.known_block_count }}{% endif %} @@ -409,4 +430,4 @@ window.onload = async () => { }; - \ No newline at end of file + diff --git a/basicswap/ui/page_wallet.py b/basicswap/ui/page_wallet.py index e1ec861..cfaf04e 100644 --- a/basicswap/ui/page_wallet.py +++ b/basicswap/ui/page_wallet.py @@ -51,12 +51,16 @@ def format_wallet_data(swap_client, ci, w): if ci.coin_type() == Coins.PART: wf['stealth_address'] = w.get('stealth_address', '?') - wf['blind_balance'] = "{:.8f}".format(float(w['blind_balance'])) + 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', '?') checkAddressesOwned(swap_client, ci, wf) return wf @@ -128,6 +132,8 @@ def page_wallet(self, url_split, post_string): if bytes('newaddr_' + cid, 'utf-8') in form_data: swap_client.cacheNewAddressForCoin(coin_id) + elif bytes('newmwebaddr_' + cid, 'utf-8') in form_data: + swap_client.cacheNewStealthAddressForCoin(coin_id) elif bytes('reseed_' + cid, 'utf-8') in form_data: try: swap_client.reseedWallet(coin_id) @@ -158,22 +164,28 @@ def page_wallet(self, url_split, post_string): page_data['wd_type_to_' + cid] = type_to except Exception as e: 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') if len(messages) == 0: ci = swap_client.ci(coin_id) ticker = ci.ticker() - if coin_id == Coins.PART: - try: + 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)) - except Exception as e: - err_messages.append(str(e)) - else: - try: + 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)) + else: txid = swap_client.withdrawCoin(coin_id, value, address, subfee) messages.append('Withdrew {} {} to address {}
In txid: {}'.format(value, ticker, address, txid)) - except Exception as e: - err_messages.append(str(e)) + except Exception as e: + err_messages.append(str(e)) swap_client.updateWalletsInfo(True, coin_id) elif have_data_entry(form_data, 'showutxogroups'): show_utxo_groups = True @@ -227,6 +239,8 @@ def page_wallet(self, url_split, post_string): if k == Coins.XMR: wallet_data['main_address'] = w.get('main_address', 'Refresh necessary') + elif k == Coins.LTC: + 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] diff --git a/basicswap/ui/util.py b/basicswap/ui/util.py index 4431dc0..b7f37e7 100644 --- a/basicswap/ui/util.py +++ b/basicswap/ui/util.py @@ -419,6 +419,8 @@ def getCoinName(c): return chainparams[Coins.PART]['name'].capitalize() + ' Anon' if c == Coins.PART_BLIND: return chainparams[Coins.PART]['name'].capitalize() + ' Blind' + if c == Coins.LTC_MWEB: + return chainparams[Coins.LTC]['name'].capitalize() + ' MWEB' coin_chainparams = chainparams[c] if coin_chainparams.get('use_ticker_as_name', False): @@ -441,6 +443,11 @@ def listAvailableCoins(swap_client, with_variants=True, split_from=False): coins.append((int(v), getCoinName(v))) 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, ): + coins.append((int(v), getCoinName(v))) + if split_from and v not in invalid_coins_from: + coins_from.append(coins[-1]) if split_from: return coins_from, coins return coins diff --git a/bin/basicswap_prepare.py b/bin/basicswap_prepare.py index c800bed..b726169 100755 --- a/bin/basicswap_prepare.py +++ b/bin/basicswap_prepare.py @@ -42,7 +42,7 @@ PARTICL_VERSION = os.getenv('PARTICL_VERSION', '23.2.7.0') PARTICL_VERSION_TAG = os.getenv('PARTICL_VERSION_TAG', '') PARTICL_LINUX_EXTRA = os.getenv('PARTICL_LINUX_EXTRA', 'nousb') -LITECOIN_VERSION = os.getenv('LITECOIN_VERSION', '0.21.2') +LITECOIN_VERSION = os.getenv('LITECOIN_VERSION', '0.21.2.2') LITECOIN_VERSION_TAG = os.getenv('LITECOIN_VERSION_TAG', '') BITCOIN_VERSION = os.getenv('BITCOIN_VERSION', '23.0') @@ -602,7 +602,7 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}): 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://download.litecoin.org/litecoin-{}/{}/{}'.format(version, os_name, release_filename) - assert_filename = '{}-core-{}-{}-build.assert'.format(coin, os_name, version.rsplit('.', 1)[0]) + 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) @@ -1141,6 +1141,12 @@ def test_particl_encryption(data_dir, settings, chain, use_tor_proxy): finalise_daemon(d) +def encrypt_wallet(swap_client, coin_type) -> None: + ci = swap_client.ci(coin_type) + ci.changeWalletPassword('', WALLET_ENCRYPTION_PWD) + ci.unlockWallet(WALLET_ENCRYPTION_PWD) + + def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, chain, use_tor_proxy): swap_client = None daemons = [] @@ -1185,16 +1191,18 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, if len(wallets) < 1: logger.info('Creating wallet.dat for {}.'.format(getCoinName(c))) - if c == Coins.BTC: + if c in (Coins.BTC, Coins.LTC): # wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors - swap_client.callcoinrpc(c, 'createwallet', ['wallet.dat', False, True, '', 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 != '': + encrypt_wallet(swap_client, c) - if WALLET_ENCRYPTION_PWD != '': - ci = swap_client.ci(c) - ci.changeWalletPassword('', WALLET_ENCRYPTION_PWD) - ci.unlockWallet(WALLET_ENCRYPTION_PWD) + if c == Coins.LTC: + password = WALLET_ENCRYPTION_PWD if WALLET_ENCRYPTION_PWD != '' else None + swap_client.ci(Coins.LTC_MWEB).init_wallet(password) if c == Coins.PART: if 'particl' in with_coins: diff --git a/doc/release-notes.md b/doc/release-notes.md index 8737896..7dbe039 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,3 +1,11 @@ + +0.12.4 +============== + +- LTC creates a new wallet to hold MWEB balance. + - MWEB wallet should be be automatically created at startup or when unlocked if system is encrypted. + + 0.12.3 ============== diff --git a/tests/basicswap/extended/test_dash.py b/tests/basicswap/extended/test_dash.py index c303b8b..2931f1f 100644 --- a/tests/basicswap/extended/test_dash.py +++ b/tests/basicswap/extended/test_dash.py @@ -672,7 +672,7 @@ class Test(unittest.TestCase): # Verify expected inputs were used bid, offer = swap_clients[2].getBidAndOffer(bid_id) assert (bid.initiate_tx) - wtx = ci_from.rpc_callback('gettransaction', [bid.initiate_tx.txid.hex(),]) + wtx = ci_from.rpc_wallet('gettransaction', [bid.initiate_tx.txid.hex(),]) itx_after = ci_from.describeTx(wtx['hex']) assert (len(itx_after['vin']) == len(itx_decoded['vin'])) for i, txin in enumerate(itx_decoded['vin']): diff --git a/tests/basicswap/extended/test_nav.py b/tests/basicswap/extended/test_nav.py index a6e0627..1da6a2b 100644 --- a/tests/basicswap/extended/test_nav.py +++ b/tests/basicswap/extended/test_nav.py @@ -902,7 +902,7 @@ class Test(TestFunctions): # Verify expected inputs were used bid, offer = swap_clients[2].getBidAndOffer(bid_id) assert (bid.initiate_tx) - wtx = ci_from.rpc_callback('gettransaction', [bid.initiate_tx.txid.hex(),]) + wtx = ci_from.rpc('gettransaction', [bid.initiate_tx.txid.hex(),]) itx_after = ci_from.describeTx(wtx['hex']) assert (len(itx_after['vin']) == len(itx_decoded['vin'])) for i, txin in enumerate(itx_decoded['vin']): diff --git a/tests/basicswap/extended/test_pivx.py b/tests/basicswap/extended/test_pivx.py index 1b17687..98eaf73 100644 --- a/tests/basicswap/extended/test_pivx.py +++ b/tests/basicswap/extended/test_pivx.py @@ -677,7 +677,7 @@ class Test(unittest.TestCase): # Verify expected inputs were used bid, offer = swap_clients[2].getBidAndOffer(bid_id) assert (bid.initiate_tx) - wtx = ci_from.rpc_callback('gettransaction', [bid.initiate_tx.txid.hex(),]) + wtx = ci_from.rpc('gettransaction', [bid.initiate_tx.txid.hex(),]) itx_after = ci_from.describeTx(wtx['hex']) assert (len(itx_after['vin']) == len(itx_decoded['vin'])) for i, txin in enumerate(itx_decoded['vin']): diff --git a/tests/basicswap/test_btc_xmr.py b/tests/basicswap/test_btc_xmr.py index 01b0c2e..05ab447 100644 --- a/tests/basicswap/test_btc_xmr.py +++ b/tests/basicswap/test_btc_xmr.py @@ -60,6 +60,16 @@ class TestFunctions(BaseTest): node_a_id = 0 node_b_id = 1 + def callnoderpc(self, method, params=[], wallet=None, node_id=0): + return callnoderpc(node_id, method, params, wallet, self.base_rpc_port) + + def mineBlock(self, num_blocks=1): + self.callnoderpc('generatetoaddress', [num_blocks, self.btc_addr]) + + def check_softfork_active(self, feature_name): + deploymentinfo = self.callnoderpc('getdeploymentinfo') + assert (deploymentinfo['deployments'][feature_name]['active'] is True) + def getBalance(self, js_wallets, coin) -> float: if coin == Coins.PART_BLIND: coin_ticker: str = 'PART' @@ -79,12 +89,6 @@ class TestFunctions(BaseTest): return float(js_wallets[coin_ticker][balance_type]) + float(js_wallets[coin_ticker][unconfirmed_name]) - def callnoderpc(self, method, params=[], wallet=None, node_id=0): - return callnoderpc(node_id, method, params, wallet, self.base_rpc_port) - - def mineBlock(self, num_blocks=1): - self.callnoderpc('generatetoaddress', [num_blocks, self.btc_addr]) - def prepare_balance(self, coin, amount: float, port_target_node: int, port_take_from_node: int, test_balance: bool = True) -> None: delay_iterations = 100 if coin == Coins.NAV else 20 delay_time = 5 if coin == Coins.NAV else 3 @@ -149,8 +153,8 @@ class TestFunctions(BaseTest): js_1 = read_json_api(1800 + id_bidder, 'wallets') node1_from_before: float = self.getBalance(js_1, coin_from) - node0_sent_messages_before: int = ci_part0.rpc_callback('smsgoutbox', ['count',])['num_messages'] - node1_sent_messages_before: int = ci_part1.rpc_callback('smsgoutbox', ['count',])['num_messages'] + node0_sent_messages_before: int = ci_part0.rpc('smsgoutbox', ['count',])['num_messages'] + node1_sent_messages_before: int = ci_part1.rpc('smsgoutbox', ['count',])['num_messages'] amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1) rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1) @@ -224,8 +228,8 @@ class TestFunctions(BaseTest): if False: # TODO: set stakeaddress and xmr rewards to non wallet addresses assert (node1_to_after < node1_to_before - amount_to_float) - node0_sent_messages_after: int = ci_part0.rpc_callback('smsgoutbox', ['count',])['num_messages'] - node1_sent_messages_after: int = ci_part1.rpc_callback('smsgoutbox', ['count',])['num_messages'] + node0_sent_messages_after: int = ci_part0.rpc('smsgoutbox', ['count',])['num_messages'] + node1_sent_messages_after: int = ci_part1.rpc('smsgoutbox', ['count',])['num_messages'] node0_sent_messages: int = node0_sent_messages_after - node0_sent_messages_before node1_sent_messages: int = node1_sent_messages_after - node1_sent_messages_before split_msgs: int = 2 if (ci_from.curve_type() != Curves.secp256k1 or ci_to.curve_type() != Curves.secp256k1) else 0 @@ -434,21 +438,22 @@ class BasicSwapTest(TestFunctions): def test_001_nested_segwit(self): # p2sh-p2wpkh logging.info('---------- Test {} p2sh nested segwit'.format(self.test_coin_from.name)) + ci = self.swap_clients[0].ci(self.test_coin_from) - addr_p2sh_segwit = self.callnoderpc('getnewaddress', ['segwit test', 'p2sh-segwit']) - addr_info = self.callnoderpc('getaddressinfo', [addr_p2sh_segwit, ]) + addr_p2sh_segwit = ci.rpc_wallet('getnewaddress', ['segwit test', 'p2sh-segwit']) + addr_info = ci.rpc_wallet('getaddressinfo', [addr_p2sh_segwit, ]) assert addr_info['script'] == 'witness_v0_keyhash' - txid = self.callnoderpc('sendtoaddress', [addr_p2sh_segwit, 1.0]) + txid = ci.rpc_wallet('sendtoaddress', [addr_p2sh_segwit, 1.0]) assert len(txid) == 64 self.mineBlock() - ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_p2sh_segwit)]]) + ro = ci.rpc('scantxoutset', ['start', ['addr({})'.format(addr_p2sh_segwit)]]) assert (len(ro['unspents']) == 1) assert (ro['unspents'][0]['txid'] == txid) - tx_wallet = self.callnoderpc('gettransaction', [txid, ])['hex'] - tx = self.callnoderpc('decoderawtransaction', [tx_wallet, ]) + tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])['hex'] + tx = ci.rpc('decoderawtransaction', [tx_wallet, ]) prevout_n = -1 for txo in tx['vout']: @@ -457,14 +462,14 @@ class BasicSwapTest(TestFunctions): break assert prevout_n > -1 - tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_p2sh_segwit: 0.99}]) - tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded, ])['hex'] - tx_funded_decoded = self.callnoderpc('decoderawtransaction', [tx_funded, ]) - tx_signed_decoded = self.callnoderpc('decoderawtransaction', [tx_signed, ]) + tx_funded = ci.rpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_p2sh_segwit: 0.99}]) + tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded, ])['hex'] + tx_funded_decoded = ci.rpc('decoderawtransaction', [tx_funded, ]) + tx_signed_decoded = ci.rpc('decoderawtransaction', [tx_signed, ]) assert tx_funded_decoded['txid'] != tx_signed_decoded['txid'] # Add scriptsig for txids to match - addr_p2sh_segwit_info = self.callnoderpc('getaddressinfo', [addr_p2sh_segwit, ]) + addr_p2sh_segwit_info = ci.rpc_wallet('getaddressinfo', [addr_p2sh_segwit, ]) decoded_tx = FromHex(CTransaction(), tx_funded) decoded_tx.vin[0].scriptSig = bytes.fromhex('16' + addr_p2sh_segwit_info['hex']) txid_with_scriptsig = decoded_tx.rehash() @@ -473,18 +478,19 @@ class BasicSwapTest(TestFunctions): def test_002_native_segwit(self): # p2wpkh logging.info('---------- Test {} p2sh native segwit'.format(self.test_coin_from.name)) + ci = self.swap_clients[0].ci(self.test_coin_from) - addr_segwit = self.callnoderpc('getnewaddress', ['segwit test', 'bech32']) - addr_info = self.callnoderpc('getaddressinfo', [addr_segwit, ]) + addr_segwit = ci.rpc_wallet('getnewaddress', ['segwit test', 'bech32']) + addr_info = ci.rpc_wallet('getaddressinfo', [addr_segwit, ]) assert addr_info['iswitness'] is True - txid = self.callnoderpc('sendtoaddress', [addr_segwit, 1.0]) + txid = ci.rpc_wallet('sendtoaddress', [addr_segwit, 1.0]) assert len(txid) == 64 - tx_wallet = self.callnoderpc('gettransaction', [txid, ])['hex'] - tx = self.callnoderpc('decoderawtransaction', [tx_wallet, ]) + tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])['hex'] + tx = ci.rpc('decoderawtransaction', [tx_wallet, ]) self.mineBlock() - ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_segwit)]]) + ro = ci.rpc('scantxoutset', ['start', ['addr({})'.format(addr_segwit)]]) assert (len(ro['unspents']) == 1) assert (ro['unspents'][0]['txid'] == txid) @@ -495,19 +501,17 @@ class BasicSwapTest(TestFunctions): break assert prevout_n > -1 - tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_segwit: 0.99}]) - tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded, ])['hex'] - tx_funded_decoded = self.callnoderpc('decoderawtransaction', [tx_funded, ]) - tx_signed_decoded = self.callnoderpc('decoderawtransaction', [tx_signed, ]) + tx_funded = ci.rpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_segwit: 0.99}]) + tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded, ])['hex'] + tx_funded_decoded = ci.rpc('decoderawtransaction', [tx_funded, ]) + tx_signed_decoded = ci.rpc('decoderawtransaction', [tx_signed, ]) assert tx_funded_decoded['txid'] == tx_signed_decoded['txid'] def test_003_cltv(self): logging.info('---------- Test {} cltv'.format(self.test_coin_from.name)) ci = self.swap_clients[0].ci(self.test_coin_from) - deploymentinfo = self.callnoderpc('getdeploymentinfo') - bip65_active = deploymentinfo['deployments']['bip65']['active'] - assert (bip65_active) + self.check_softfork_active('bip65') chain_height = self.callnoderpc('getblockcount') script = CScript([chain_height + 3, OP_CHECKLOCKTIMEVERIFY, ]) @@ -517,12 +521,12 @@ class BasicSwapTest(TestFunctions): tx.nVersion = ci.txVersion() tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest)) tx_hex = ToHex(tx) - tx_funded = self.callnoderpc('fundrawtransaction', [tx_hex]) + tx_funded = ci.rpc_wallet('fundrawtransaction', [tx_hex]) utxo_pos = 0 if tx_funded['changepos'] == 1 else 1 - tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex'] - txid = self.callnoderpc('sendrawtransaction', [tx_signed, ]) + tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex'] + txid = ci.rpc('sendrawtransaction', [tx_signed, ]) - addr_out = self.callnoderpc('getnewaddress', ['cltv test', 'bech32']) + addr_out = ci.rpc_wallet('getnewaddress', ['cltv test', 'bech32']) pkh = ci.decodeSegwitAddress(addr_out) script_out = ci.getScriptForPubkeyHash(pkh) @@ -548,15 +552,15 @@ class BasicSwapTest(TestFunctions): self.mineBlock(5) try: - txid = self.callnoderpc('sendrawtransaction', [tx_spend_invalid_hex, ]) + txid = ci.rpc('sendrawtransaction', [tx_spend_invalid_hex, ]) except Exception as e: assert ('Locktime requirement not satisfied' in str(e)) else: assert False, 'Should fail' - txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ]) + txid = ci.rpc('sendrawtransaction', [tx_spend_hex, ]) self.mineBlock() - ro = self.callnoderpc('listreceivedbyaddress', [0, ]) + ro = ci.rpc_wallet('listreceivedbyaddress', [0, ]) sum_addr = 0 for entry in ro: if entry['address'] == addr_out: @@ -564,7 +568,7 @@ class BasicSwapTest(TestFunctions): assert (sum_addr == 1.0999) # Ensure tx was mined - tx_wallet = self.callnoderpc('gettransaction', [txid, ]) + tx_wallet = ci.rpc_wallet('gettransaction', [txid, ]) assert (len(tx_wallet['blockhash']) == 64) def test_004_csv(self): @@ -572,6 +576,8 @@ class BasicSwapTest(TestFunctions): swap_clients = self.swap_clients ci = self.swap_clients[0].ci(self.test_coin_from) + self.check_softfork_active('bip66') + script = CScript([3, OP_CHECKSEQUENCEVERIFY, ]) script_dest = ci.getScriptDest(script) @@ -579,17 +585,17 @@ class BasicSwapTest(TestFunctions): tx.nVersion = ci.txVersion() tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest)) tx_hex = ToHex(tx) - tx_funded = self.callnoderpc('fundrawtransaction', [tx_hex]) + tx_funded = ci.rpc_wallet('fundrawtransaction', [tx_hex]) utxo_pos = 0 if tx_funded['changepos'] == 1 else 1 - tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex'] - txid = self.callnoderpc('sendrawtransaction', [tx_signed, ]) + tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex'] + txid = ci.rpc('sendrawtransaction', [tx_signed, ]) - addr_out = self.callnoderpc('getnewaddress', ['csv test', 'bech32']) + addr_out = ci.rpc_wallet('getnewaddress', ['csv test', 'bech32']) pkh = ci.decodeSegwitAddress(addr_out) script_out = ci.getScriptForPubkeyHash(pkh) # Double check output type - prev_tx = self.callnoderpc('decoderawtransaction', [tx_signed, ]) + prev_tx = ci.rpc('decoderawtransaction', [tx_signed, ]) assert (prev_tx['vout'][utxo_pos]['scriptPubKey']['type'] == 'witness_v0_scripthash') tx_spend = CTransaction() @@ -601,16 +607,16 @@ class BasicSwapTest(TestFunctions): tx_spend.wit.vtxinwit[0].scriptWitness.stack = [script, ] tx_spend_hex = ToHex(tx_spend) try: - txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ]) + txid = ci.rpc('sendrawtransaction', [tx_spend_hex, ]) except Exception as e: assert ('non-BIP68-final' in str(e)) else: assert False, 'Should fail' self.mineBlock(3) - txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ]) + txid = ci.rpc('sendrawtransaction', [tx_spend_hex, ]) self.mineBlock(1) - ro = self.callnoderpc('listreceivedbyaddress', [0, ]) + ro = ci.rpc_wallet('listreceivedbyaddress', [0, ]) sum_addr = 0 for entry in ro: if entry['address'] == addr_out: @@ -618,20 +624,22 @@ class BasicSwapTest(TestFunctions): assert (sum_addr == 1.0999) # Ensure tx was mined - tx_wallet = self.callnoderpc('gettransaction', [txid, ]) + tx_wallet = ci.rpc_wallet('gettransaction', [txid, ]) assert (len(tx_wallet['blockhash']) == 64) def test_005_watchonly(self): logging.info('---------- Test {} watchonly'.format(self.test_coin_from.name)) + ci = self.swap_clients[0].ci(self.test_coin_from) + ci1 = self.swap_clients[1].ci(self.test_coin_from) - addr = self.callnoderpc('getnewaddress', ['watchonly test', 'bech32']) - ro = self.callnoderpc('importaddress', [addr, '', False], node_id=1) - txid = self.callnoderpc('sendtoaddress', [addr, 1.0]) - tx_hex = self.callnoderpc('getrawtransaction', [txid, ]) - self.callnoderpc('sendrawtransaction', [tx_hex, ], node_id=1) - ro = self.callnoderpc('gettransaction', [txid, ], node_id=1) + addr = ci.rpc_wallet('getnewaddress', ['watchonly test', 'bech32']) + ro = ci1.rpc_wallet('importaddress', [addr, '', False]) + txid = ci.rpc_wallet('sendtoaddress', [addr, 1.0]) + tx_hex = ci.rpc('getrawtransaction', [txid, ]) + ci1.rpc_wallet('sendrawtransaction', [tx_hex, ]) + ro = ci1.rpc_wallet('gettransaction', [txid, ]) assert (ro['txid'] == txid) - balances = self.callnoderpc('getbalances', node_id=1) + balances = ci1.rpc_wallet('getbalances') assert (balances['watchonly']['trusted'] + balances['watchonly']['untrusted_pending'] >= 1.0) def test_006_getblock_verbosity(self): @@ -643,6 +651,7 @@ class BasicSwapTest(TestFunctions): def test_007_hdwallet(self): logging.info('---------- Test {} hdwallet'.format(self.test_coin_from.name)) + ci = self.swap_clients[0].ci(self.test_coin_from) test_seed = '8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b' test_wif = self.swap_clients[0].ci(self.test_coin_from).encodeKey(bytes.fromhex(test_seed)) @@ -657,7 +666,7 @@ class BasicSwapTest(TestFunctions): self.swap_clients[0].initialiseWallet(Coins.BTC, raise_errors=True) assert self.swap_clients[0].checkWalletSeed(Coins.BTC) is True for i in range(1500): - self.callnoderpc('getnewaddress') + ci.rpc_wallet('getnewaddress') assert self.swap_clients[0].checkWalletSeed(Coins.BTC) is True rv = read_json_api(1800, 'getcoinseed', {'coin': 'XMR'}) @@ -667,38 +676,44 @@ class BasicSwapTest(TestFunctions): logging.info('---------- Test {} gettxout'.format(self.test_coin_from.name)) swap_client = self.swap_clients[0] + ci = swap_client.ci(self.test_coin_from) - addr_1 = self.callnoderpc('getnewaddress', ['gettxout test 1',]) - txid = self.callnoderpc('sendtoaddress', [addr_1, 1.0]) + addr_1 = ci.rpc_wallet('getnewaddress', ['gettxout test 1',]) + txid = ci.rpc_wallet('sendtoaddress', [addr_1, 1.0]) assert len(txid) == 64 self.mineBlock() - unspents = self.callnoderpc('listunspent', [0, 999999999, [addr_1,]]) + unspents = ci.rpc_wallet('listunspent', [0, 999999999, [addr_1,]]) assert (len(unspents) == 1) utxo = unspents[0] - txout = self.callnoderpc('gettxout', [utxo['txid'], utxo['vout']]) - assert (addr_1 == txout['scriptPubKey']['address']) + txout = ci.rpc('gettxout', [utxo['txid'], utxo['vout']]) + if 'address' in txout: + assert (addr_1 == txout['scriptPubKey']['address']) + else: + assert (addr_1 in txout['scriptPubKey']['addresses']) # Spend - addr_2 = self.callnoderpc('getnewaddress', ['gettxout test 2',]) - tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': utxo['txid'], 'vout': utxo['vout']}], {addr_2: 0.99}]) - tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded,])['hex'] - self.callnoderpc('sendrawtransaction', [tx_signed,]) + addr_2 = ci.rpc_wallet('getnewaddress', ['gettxout test 2',]) + tx_funded = ci.rpc('createrawtransaction', [[{'txid': utxo['txid'], 'vout': utxo['vout']}], {addr_2: 0.99}]) + tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded,])['hex'] + ci.rpc('sendrawtransaction', [tx_signed,]) # utxo should be unavailable when spent in the mempool - txout = self.callnoderpc('gettxout', [utxo['txid'], utxo['vout']]) + txout = ci.rpc('gettxout', [utxo['txid'], utxo['vout']]) assert (txout is None) def test_009_scantxoutset(self): logging.info('---------- Test {} scantxoutset'.format(self.test_coin_from.name)) - addr_1 = self.callnoderpc('getnewaddress', ['scantxoutset test', ]) - txid = self.callnoderpc('sendtoaddress', [addr_1, 1.0]) + ci = self.swap_clients[0].ci(self.test_coin_from) + + addr_1 = ci.rpc_wallet('getnewaddress', ['scantxoutset test', ]) + txid = ci.rpc_wallet('sendtoaddress', [addr_1, 1.0]) assert len(txid) == 64 self.mineBlock() - ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_1)]]) + ro = ci.rpc('scantxoutset', ['start', ['addr({})'.format(addr_1)]]) assert (len(ro['unspents']) == 1) assert (ro['unspents'][0]['txid'] == txid) @@ -712,7 +727,7 @@ class BasicSwapTest(TestFunctions): amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1) # Record unspents before createSCLockTx as the used ones will be locked - unspents = self.callnoderpc('listunspent') + unspents = ci.rpc_wallet('listunspent') # fee_rate is in sats/kvB fee_rate: int = 1000 @@ -728,10 +743,10 @@ class BasicSwapTest(TestFunctions): lock_tx = ci.fundSCLockTx(lock_tx, fee_rate) lock_tx = ci.signTxWithWallet(lock_tx) - unspents_after = self.callnoderpc('listunspent') + unspents_after = ci.rpc_wallet('listunspent') assert (len(unspents) > len(unspents_after)) - tx_decoded = self.callnoderpc('decoderawtransaction', [lock_tx.hex()]) + tx_decoded = ci.rpc('decoderawtransaction', [lock_tx.hex()]) txid = tx_decoded['txid'] vsize = tx_decoded['vsize'] @@ -752,8 +767,8 @@ class BasicSwapTest(TestFunctions): break fee_value = in_value - out_value - self.callnoderpc('sendrawtransaction', [lock_tx.hex()]) - rv = self.callnoderpc('gettransaction', [txid]) + ci.rpc('sendrawtransaction', [lock_tx.hex()]) + rv = ci.rpc_wallet('gettransaction', [txid]) wallet_tx_fee = -ci.make_int(rv['fee']) assert (wallet_tx_fee == fee_value) @@ -765,7 +780,7 @@ class BasicSwapTest(TestFunctions): lock_spend_tx = ci.createSCLockSpendTx(lock_tx, lock_tx_script, pkh_out, fee_rate, fee_info=fee_info) vsize_estimated: int = fee_info['vsize'] - tx_decoded = self.callnoderpc('decoderawtransaction', [lock_spend_tx.hex()]) + tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()]) txid = tx_decoded['txid'] witness_stack = [ @@ -775,11 +790,11 @@ class BasicSwapTest(TestFunctions): lock_tx_script, ] lock_spend_tx = ci.setTxSignature(lock_spend_tx, witness_stack) - tx_decoded = self.callnoderpc('decoderawtransaction', [lock_spend_tx.hex()]) + tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()]) vsize_actual: int = tx_decoded['vsize'] assert (vsize_actual <= vsize_estimated and vsize_estimated - vsize_actual < 4) - assert (self.callnoderpc('sendrawtransaction', [lock_spend_tx.hex()]) == txid) + assert (ci.rpc('sendrawtransaction', [lock_spend_tx.hex()]) == txid) expect_vsize: int = ci.xmr_swap_a_lock_spend_tx_vsize() assert (expect_vsize >= vsize_actual) @@ -796,7 +811,7 @@ class BasicSwapTest(TestFunctions): lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid) if lock_tx_b_spend is None: lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid) - lock_tx_b_spend_decoded = self.callnoderpc('decoderawtransaction', [lock_tx_b_spend.hex()]) + lock_tx_b_spend_decoded = ci.rpc('decoderawtransaction', [lock_tx_b_spend.hex()]) expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize() assert (expect_vsize >= lock_tx_b_spend_decoded['vsize']) @@ -816,17 +831,17 @@ class BasicSwapTest(TestFunctions): tx.nVersion = ci.txVersion() tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest)) tx_hex = ToHex(tx) - tx_funded = self.callnoderpc('fundrawtransaction', [tx_hex]) + tx_funded = ci.rpc_wallet('fundrawtransaction', [tx_hex]) utxo_pos = 0 if tx_funded['changepos'] == 1 else 1 - tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex'] - txid = self.callnoderpc('sendrawtransaction', [tx_signed, ]) + tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex'] + txid = ci.rpc('sendrawtransaction', [tx_signed, ]) - addr_out = self.callnoderpc('getnewaddress', ['csv test', 'bech32']) + addr_out = ci.rpc_wallet('getnewaddress', ['csv test', 'bech32']) pkh = ci.decodeSegwitAddress(addr_out) script_out = ci.getScriptForPubkeyHash(pkh) # Double check output type - prev_tx = self.callnoderpc('decoderawtransaction', [tx_signed, ]) + prev_tx = ci.rpc('decoderawtransaction', [tx_signed, ]) assert (prev_tx['vout'][utxo_pos]['scriptPubKey']['type'] == 'scripthash') tx_spend = CTransaction() @@ -836,9 +851,9 @@ class BasicSwapTest(TestFunctions): tx_spend.vout.append(ci.txoType()(ci.make_int(1.0999), script_out)) tx_spend_hex = ToHex(tx_spend) - txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ]) + txid = ci.rpc('sendrawtransaction', [tx_spend_hex, ]) self.mineBlock(1) - ro = self.callnoderpc('listreceivedbyaddress', [0, ]) + ro = ci.rpc_wallet('listreceivedbyaddress', [0, ]) sum_addr = 0 for entry in ro: if entry['address'] == addr_out: @@ -846,7 +861,7 @@ class BasicSwapTest(TestFunctions): assert (sum_addr == 1.0999) # Ensure tx was mined - tx_wallet = self.callnoderpc('gettransaction', [txid, ]) + tx_wallet = ci.rpc_wallet('gettransaction', [txid, ]) assert (len(tx_wallet['blockhash']) == 64) def test_012_p2sh_p2wsh(self): @@ -863,17 +878,17 @@ class BasicSwapTest(TestFunctions): tx.nVersion = ci.txVersion() tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest)) tx_hex = ToHex(tx) - tx_funded = self.callnoderpc('fundrawtransaction', [tx_hex]) + tx_funded = ci.rpc_wallet('fundrawtransaction', [tx_hex]) utxo_pos = 0 if tx_funded['changepos'] == 1 else 1 - tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex'] - txid = self.callnoderpc('sendrawtransaction', [tx_signed, ]) + tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex'] + txid = ci.rpc('sendrawtransaction', [tx_signed, ]) - addr_out = self.callnoderpc('getnewaddress', ['csv test', 'bech32']) + addr_out = ci.rpc_wallet('getnewaddress', ['csv test', 'bech32']) pkh = ci.decodeSegwitAddress(addr_out) script_out = ci.getScriptForPubkeyHash(pkh) # Double check output type - prev_tx = self.callnoderpc('decoderawtransaction', [tx_signed, ]) + prev_tx = ci.rpc('decoderawtransaction', [tx_signed, ]) assert (prev_tx['vout'][utxo_pos]['scriptPubKey']['type'] == 'scripthash') tx_spend = CTransaction() @@ -885,9 +900,9 @@ class BasicSwapTest(TestFunctions): tx_spend.wit.vtxinwit[0].scriptWitness.stack = [script, ] tx_spend_hex = ToHex(tx_spend) - txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ]) + txid = ci.rpc('sendrawtransaction', [tx_spend_hex, ]) self.mineBlock(1) - ro = self.callnoderpc('listreceivedbyaddress', [0, ]) + ro = ci.rpc_wallet('listreceivedbyaddress', [0, ]) sum_addr = 0 for entry in ro: if entry['address'] == addr_out: @@ -895,7 +910,7 @@ class BasicSwapTest(TestFunctions): assert (sum_addr == 1.0999) # Ensure tx was mined - tx_wallet = self.callnoderpc('gettransaction', [txid, ]) + tx_wallet = ci.rpc_wallet('gettransaction', [txid, ]) assert (len(tx_wallet['blockhash']) == 64) def test_01_a_full_swap(self): @@ -1045,7 +1060,7 @@ class BasicSwapTest(TestFunctions): # Verify expected inputs were used bid, _, _, _, _ = swap_clients[2].getXmrBidAndOffer(bid_id) assert (bid.xmr_a_lock_tx) - wtx = ci.rpc_callback('gettransaction', [bid.xmr_a_lock_tx.txid.hex(),]) + wtx = ci.rpc_wallet('gettransaction', [bid.xmr_a_lock_tx.txid.hex(),]) itx_after = ci.describeTx(wtx['hex']) assert (len(itx_after['vin']) == len(itx_decoded['vin'])) for i, txin in enumerate(itx_decoded['vin']): diff --git a/tests/basicswap/test_ltc_xmr.py b/tests/basicswap/test_ltc_xmr.py index 643ab4e..c894977 100644 --- a/tests/basicswap/test_ltc_xmr.py +++ b/tests/basicswap/test_ltc_xmr.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright (c) 2021-2022 tecnovert +# Copyright (c) 2021-2023 tecnovert # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -24,9 +24,10 @@ from tests.basicswap.common import ( wait_for_bid, wait_for_offer, wait_for_in_progress, + TEST_HTTP_PORT, LTC_BASE_RPC_PORT, ) -from .test_btc_xmr import BasicSwapTest, test_delay_event +from .test_btc_xmr import BasicSwapTest, test_delay_event, callnoderpc logger = logging.getLogger() @@ -37,9 +38,20 @@ class TestLTC(BasicSwapTest): start_ltc_nodes = True base_rpc_port = LTC_BASE_RPC_PORT + @classmethod + def prepareExtraCoins(cls): + logging.info('Mining {} chain to height 1352 to activate CVS (BIP66)'.format(cls.test_coin_from.name)) + chain_height = callnoderpc(0, 'getblockcount', base_rpc_port=LTC_BASE_RPC_PORT) + num_blocks: int = 1352 - chain_height + callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT) + def mineBlock(self, num_blocks=1): self.callnoderpc('generatetoaddress', [num_blocks, self.ltc_addr]) + def check_softfork_active(self, feature_name): + deploymentinfo = self.callnoderpc('getblockchaininfo') + assert (deploymentinfo['softforks'][feature_name]['active'] is True) + def test_001_nested_segwit(self): logging.info('---------- Test {} p2sh nested segwit'.format(self.test_coin_from.name)) logging.info('Skipped') @@ -47,17 +59,18 @@ class TestLTC(BasicSwapTest): def test_002_native_segwit(self): logging.info('---------- Test {} p2sh native segwit'.format(self.test_coin_from.name)) - addr_segwit = self.callnoderpc('getnewaddress', ['segwit test', 'bech32']) - addr_info = self.callnoderpc('getaddressinfo', [addr_segwit, ]) + ci = self.swap_clients[0].ci(self.test_coin_from) + addr_segwit = ci.rpc_wallet('getnewaddress', ['segwit test', 'bech32']) + addr_info = ci.rpc_wallet('getaddressinfo', [addr_segwit, ]) assert addr_info['iswitness'] is True - txid = self.callnoderpc('sendtoaddress', [addr_segwit, 1.0]) + txid = ci.rpc_wallet('sendtoaddress', [addr_segwit, 1.0]) assert len(txid) == 64 - tx_wallet = self.callnoderpc('gettransaction', [txid, ])['hex'] - tx = self.callnoderpc('decoderawtransaction', [tx_wallet, ]) + tx_wallet = ci.rpc_wallet('gettransaction', [txid, ])['hex'] + tx = ci.rpc('decoderawtransaction', [tx_wallet, ]) self.mineBlock() - ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_segwit)]]) + ro = ci.rpc('scantxoutset', ['start', ['addr({})'.format(addr_segwit)]]) assert (len(ro['unspents']) == 1) assert (ro['unspents'][0]['txid'] == txid) @@ -68,10 +81,10 @@ class TestLTC(BasicSwapTest): break assert prevout_n > -1 - tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_segwit: 0.99}]) - tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded, ])['hex'] - tx_funded_decoded = self.callnoderpc('decoderawtransaction', [tx_funded, ]) - tx_signed_decoded = self.callnoderpc('decoderawtransaction', [tx_signed, ]) + tx_funded = ci.rpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_segwit: 0.99}]) + tx_signed = ci.rpc_wallet('signrawtransactionwithwallet', [tx_funded, ])['hex'] + tx_funded_decoded = ci.rpc('decoderawtransaction', [tx_funded, ]) + tx_signed_decoded = ci.rpc('decoderawtransaction', [tx_signed, ]) assert tx_funded_decoded['txid'] == tx_signed_decoded['txid'] def test_007_hdwallet(self): @@ -108,6 +121,115 @@ class TestLTC(BasicSwapTest): assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0) assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0) + def test_21_mweb(self): + logging.info('---------- Test MWEB {}'.format(self.test_coin_from.name)) + swap_clients = self.swap_clients + + ci0 = swap_clients[0].ci(self.test_coin_from) + ci1 = swap_clients[1].ci(self.test_coin_from) + + mweb_addr_0 = ci0.rpc_wallet('getnewaddress', ['mweb addr test 0', 'mweb']) + mweb_addr_1 = ci1.rpc_wallet('getnewaddress', ['mweb addr test 1', 'mweb']) + + addr_info0 = ci0.rpc_wallet('getaddressinfo', [mweb_addr_0,]) + assert (addr_info0['ismweb'] is True) + + addr_info1 = ci1.rpc_wallet('getaddressinfo', [mweb_addr_1,]) + assert (addr_info1['ismweb'] is True) + + txid = ci0.rpc_wallet('sendtoaddress', [mweb_addr_0, 10.0]) + + self.mineBlock() + + txns = ci0.rpc_wallet('listtransactions') + + utxos = ci0.rpc_wallet('listunspent') + balances = ci0.rpc_wallet('getbalances') + wi = ci0.rpc_wallet('getwalletinfo') + + txid = ci0.rpc_wallet('sendtoaddress', [mweb_addr_1, 10.0]) + + self.mineBlock() + + txns = ci1.rpc_wallet('listtransactions') + + utxos = ci1.rpc_wallet('listunspent') + balances = ci1.rpc_wallet('getbalances') + wi = ci1.rpc_wallet('getwalletinfo') + + mweb_tx = None + for utxo in utxos: + if utxo.get('address', '') == mweb_addr_1: + mweb_tx = utxo + assert (mweb_tx is not None) + + tx = ci1.rpc_wallet('gettransaction', [mweb_tx['txid'],]) + + blockhash = tx['blockhash'] + block = ci1.rpc('getblock', [blockhash, 3]) + block = ci1.rpc('getblock', [blockhash, 0]) + + # TODO + + def test_22_mweb_balance(self): + logging.info('---------- Test MWEB balance {}'.format(self.test_coin_from.name)) + swap_clients = self.swap_clients + + ci_mweb = swap_clients[0].ci(Coins.LTC_MWEB) + mweb_addr_0 = ci_mweb.getNewAddress() + addr_info0 = ci_mweb.rpc_wallet('getaddressinfo', [mweb_addr_0,]) + assert (addr_info0['ismweb'] is True) + + ltc_addr = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc/nextdepositaddr') + ltc_mweb_addr = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc_mweb/nextdepositaddr') + ltc_mweb_addr2 = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc/newmwebaddress') + + assert (ci_mweb.rpc_wallet('getaddressinfo', [ltc_addr,])['ismweb'] is False) + assert (ci_mweb.rpc_wallet('getaddressinfo', [ltc_mweb_addr,])['ismweb'] is True) + assert (ci_mweb.rpc_wallet('getaddressinfo', [ltc_mweb_addr2,])['ismweb'] is True) + + post_json = { + 'value': 10, + 'address': ltc_mweb_addr, + 'subfee': False, + } + json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc/withdraw', post_json) + assert (len(json_rv['txid']) == 64) + + self.mineBlock() + + json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc', post_json) + assert (json_rv['mweb_balance'] == 10.0) + mweb_address = json_rv['mweb_address'] + + post_json = { + 'value': 11, + 'address': mweb_address, + 'subfee': False, + } + json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc/withdraw', post_json) + assert (len(json_rv['txid']) == 64) + + self.mineBlock() + + json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc_mweb', post_json) + assert (json_rv['mweb_balance'] == 21.0) + assert (json_rv['mweb_address'] == mweb_address) + ltc_address = json_rv['deposit_address'] + + # Check that spending the mweb balance takes from the correct wallet + post_json = { + 'value': 1, + 'address': ltc_address, + 'subfee': False, + 'type_from': 'mweb', + } + json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc/withdraw', post_json) + assert (len(json_rv['txid']) == 64) + + json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/ltc', post_json) + assert (json_rv['mweb_balance'] <= 20.0) + if __name__ == '__main__': unittest.main() diff --git a/tests/basicswap/test_partblind_xmr.py b/tests/basicswap/test_partblind_xmr.py index ede543d..1268e64 100644 --- a/tests/basicswap/test_partblind_xmr.py +++ b/tests/basicswap/test_partblind_xmr.py @@ -101,7 +101,7 @@ class Test(BaseTest): nonlocal ci i = 0 while not delay_event.is_set(): - unspents = ci.rpc_callback('listunspentblind') + unspents = ci.rpc_wallet('listunspentblind') if len(unspents) >= 1: return delay_event.wait(delay_time) @@ -113,8 +113,8 @@ class Test(BaseTest): amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1) # Record unspents before createSCLockTx as the used ones will be locked - unspents = ci.rpc_callback('listunspentblind') - locked_utxos_before = ci.rpc_callback('listlockunspent') + unspents = ci.rpc_wallet('listunspentblind') + locked_utxos_before = ci.rpc_wallet('listlockunspent') # fee_rate is in sats/kvB fee_rate: int = 1000 @@ -131,33 +131,33 @@ class Test(BaseTest): lock_tx = ci.fundSCLockTx(lock_tx, fee_rate, vkbv) lock_tx = ci.signTxWithWallet(lock_tx) - unspents_after = ci.rpc_callback('listunspentblind') - locked_utxos_after = ci.rpc_callback('listlockunspent') + unspents_after = ci.rpc_wallet('listunspentblind') + locked_utxos_after = ci.rpc_wallet('listlockunspent') assert (len(unspents) > len(unspents_after)) assert (len(locked_utxos_after) > len(locked_utxos_before)) - lock_tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_tx.hex()]) + lock_tx_decoded = ci.rpc_wallet('decoderawtransaction', [lock_tx.hex()]) txid = lock_tx_decoded['txid'] vsize = lock_tx_decoded['vsize'] expect_fee_int = round(fee_rate * vsize / 1000) expect_fee = ci.format_amount(expect_fee_int) - ci.rpc_callback('sendrawtransaction', [lock_tx.hex()]) - rv = ci.rpc_callback('gettransaction', [txid]) + ci.rpc_wallet('sendrawtransaction', [lock_tx.hex()]) + rv = ci.rpc_wallet('gettransaction', [txid]) wallet_tx_fee = -ci.make_int(rv['details'][0]['fee']) assert (wallet_tx_fee >= expect_fee_int) assert (wallet_tx_fee - expect_fee_int < 20) addr_out = ci.getNewAddress(True) - addrinfo = ci.rpc_callback('getaddressinfo', [addr_out,]) + addrinfo = ci.rpc_wallet('getaddressinfo', [addr_out,]) pk_out = bytes.fromhex(addrinfo['pubkey']) fee_info = {} lock_spend_tx = ci.createSCLockSpendTx(lock_tx, lock_tx_script, pk_out, fee_rate, vkbv, fee_info=fee_info) vsize_estimated: int = fee_info['vsize'] - spend_tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_spend_tx.hex()]) + spend_tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()]) txid = spend_tx_decoded['txid'] nonce = ci.getScriptLockTxNonce(vkbv) @@ -172,12 +172,12 @@ class Test(BaseTest): lock_tx_script, ] lock_spend_tx = ci.setTxSignature(lock_spend_tx, witness_stack) - tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_spend_tx.hex()]) + tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()]) vsize_actual: int = tx_decoded['vsize'] # Note: The fee is set allowing 9 bytes for the encoded fee amount, causing a small overestimate assert (vsize_actual <= vsize_estimated and vsize_estimated - vsize_actual < 10) - assert (ci.rpc_callback('sendrawtransaction', [lock_spend_tx.hex()]) == txid) + assert (ci.rpc('sendrawtransaction', [lock_spend_tx.hex()]) == txid) # Test chain b (no-script) lock tx size v = ci.getNewSecretKey() @@ -198,7 +198,7 @@ class Test(BaseTest): lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid) if lock_tx_b_spend is None: lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid) - lock_tx_b_spend_decoded = ci.rpc_callback('decoderawtransaction', [lock_tx_b_spend.hex()]) + lock_tx_b_spend_decoded = ci.rpc('decoderawtransaction', [lock_tx_b_spend.hex()]) expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize() assert (expect_vsize >= lock_tx_b_spend_decoded['vsize']) @@ -472,7 +472,7 @@ class Test(BaseTest): # Verify expected inputs were used bid, _, _, _, _ = swap_clients[2].getXmrBidAndOffer(bid_id) assert (bid.xmr_a_lock_tx) - wtx = ci.rpc_callback('gettransaction', [bid.xmr_a_lock_tx.txid.hex(),]) + wtx = ci.rpc_wallet('gettransaction', [bid.xmr_a_lock_tx.txid.hex(),]) itx_after = ci.describeTx(wtx['hex']) assert (len(itx_after['vin']) == len(itx_decoded['vin'])) for i, txin in enumerate(itx_decoded['vin']): diff --git a/tests/basicswap/test_run.py b/tests/basicswap/test_run.py index 4925802..b19937e 100644 --- a/tests/basicswap/test_run.py +++ b/tests/basicswap/test_run.py @@ -80,10 +80,10 @@ class Test(BaseTest): super(Test, cls).setUpClass() btc_addr1 = callnoderpc(1, 'getnewaddress', ['initial funds', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT) - ltc_addr1 = callnoderpc(1, 'getnewaddress', ['initial funds', 'bech32'], base_rpc_port=LTC_BASE_RPC_PORT) + ltc_addr1 = callnoderpc(1, 'getnewaddress', ['initial funds', 'bech32'], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat') callnoderpc(0, 'sendtoaddress', [btc_addr1, 1000], base_rpc_port=BTC_BASE_RPC_PORT) - callnoderpc(0, 'sendtoaddress', [ltc_addr1, 1000], base_rpc_port=LTC_BASE_RPC_PORT) + callnoderpc(0, 'sendtoaddress', [ltc_addr1, 1000], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat') wait_for_balance(test_delay_event, 'http://127.0.0.1:1801/json/wallets/btc', 'balance', 1000.0) wait_for_balance(test_delay_event, 'http://127.0.0.1:1801/json/wallets/ltc', 'balance', 1000.0) @@ -182,6 +182,9 @@ class Test(BaseTest): rv = read_json_api(1800, 'automationstrategies/1') assert (rv['label'] == 'Accept All') + sx_addr = read_json_api(1800, 'wallets/part/newstealthaddress') + assert (callnoderpc(0, 'getaddressinfo', [sx_addr, ])['isstealthaddress'] is True) + def test_004_validateSwapType(self): logging.info('---------- Test validateSwapType') @@ -570,7 +573,7 @@ class Test(BaseTest): def test_12_withdrawal(self): logging.info('---------- Test LTC withdrawals') - ltc_addr = callnoderpc(0, 'getnewaddress', ['Withdrawal test', 'legacy'], base_rpc_port=LTC_BASE_RPC_PORT) + ltc_addr = callnoderpc(0, 'getnewaddress', ['Withdrawal test', 'legacy'], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat') wallets0 = read_json_api(TEST_HTTP_PORT + 0, 'wallets') assert (float(wallets0['LTC']['balance']) > 100) @@ -712,7 +715,7 @@ class Test(BaseTest): # Verify expected inputs were used bid, offer = swap_clients[2].getBidAndOffer(bid_id) assert (bid.initiate_tx) - wtx = ci.rpc_callback('gettransaction', [bid.initiate_tx.txid.hex(),]) + wtx = ci.rpc('gettransaction', [bid.initiate_tx.txid.hex(),]) itx_after = ci.describeTx(wtx['hex']) assert (len(itx_after['vin']) == len(itx_decoded['vin'])) for i, txin in enumerate(itx_decoded['vin']): diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index d577c19..ccffb28 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -530,29 +530,29 @@ class BaseTest(unittest.TestCase): if cls.start_ltc_nodes: num_blocks = 400 - cls.ltc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=LTC_BASE_RPC_PORT) + cls.ltc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat') logging.info('Mining %d Litecoin blocks to %s', num_blocks, cls.ltc_addr) - callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT) + callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat') num_blocks = 31 cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey) logging.info('Mining %d Litecoin blocks to %s', num_blocks, cls.ltc_addr) - callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT) + callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat') # https://github.com/litecoin-project/litecoin/issues/807 # Block 432 is when MWEB activates. It requires a peg-in. You'll need to generate an mweb address and send some coins to it. Then it will allow you to mine the next block. - mweb_addr = callnoderpc(2, 'getnewaddress', ['mweb_addr', 'mweb'], base_rpc_port=LTC_BASE_RPC_PORT) - callnoderpc(0, 'sendtoaddress', [mweb_addr, 1], base_rpc_port=LTC_BASE_RPC_PORT) + mweb_addr = callnoderpc(2, 'getnewaddress', ['mweb_addr', 'mweb'], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat') + callnoderpc(0, 'sendtoaddress', [mweb_addr, 1], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat') - ltc_addr1 = callnoderpc(1, 'getnewaddress', ['initial addr'], base_rpc_port=LTC_BASE_RPC_PORT) + ltc_addr1 = callnoderpc(1, 'getnewaddress', ['initial addr'], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat') for i in range(5): - callnoderpc(0, 'sendtoaddress', [ltc_addr1, 100], base_rpc_port=LTC_BASE_RPC_PORT) + callnoderpc(0, 'sendtoaddress', [ltc_addr1, 100], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat') num_blocks = 69 cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey) - callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT) + callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat') - checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=LTC_BASE_RPC_PORT)) + checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=LTC_BASE_RPC_PORT, wallet='wallet.dat')) num_blocks = 100 if cls.start_xmr_nodes: @@ -682,7 +682,7 @@ class Test(BaseTest): amount: int = ci.make_int(random.uniform(0.1, 2.0), r=1) # Record unspents before createSCLockTx as the used ones will be locked - unspents = ci.rpc_callback('listunspent') + unspents = ci.rpc('listunspent') # fee_rate is in sats/kvB fee_rate: int = 1000 @@ -698,10 +698,10 @@ class Test(BaseTest): lock_tx = ci.fundSCLockTx(lock_tx, fee_rate) lock_tx = ci.signTxWithWallet(lock_tx) - unspents_after = ci.rpc_callback('listunspent') + unspents_after = ci.rpc('listunspent') assert (len(unspents) > len(unspents_after)) - tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_tx.hex()]) + tx_decoded = ci.rpc('decoderawtransaction', [lock_tx.hex()]) txid = tx_decoded['txid'] vsize = tx_decoded['vsize'] @@ -722,8 +722,8 @@ class Test(BaseTest): break fee_value = in_value - out_value - ci.rpc_callback('sendrawtransaction', [lock_tx.hex()]) - rv = ci.rpc_callback('gettransaction', [txid]) + ci.rpc('sendrawtransaction', [lock_tx.hex()]) + rv = ci.rpc('gettransaction', [txid]) wallet_tx_fee = -ci.make_int(rv['fee']) assert (wallet_tx_fee == fee_value) @@ -735,7 +735,7 @@ class Test(BaseTest): lock_spend_tx = ci.createSCLockSpendTx(lock_tx, lock_tx_script, pkh_out, fee_rate, fee_info=fee_info) vsize_estimated: int = fee_info['vsize'] - tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_spend_tx.hex()]) + tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()]) txid = tx_decoded['txid'] witness_stack = [ @@ -745,11 +745,11 @@ class Test(BaseTest): lock_tx_script, ] lock_spend_tx = ci.setTxSignature(lock_spend_tx, witness_stack) - tx_decoded = ci.rpc_callback('decoderawtransaction', [lock_spend_tx.hex()]) + tx_decoded = ci.rpc('decoderawtransaction', [lock_spend_tx.hex()]) vsize_actual: int = tx_decoded['vsize'] assert (vsize_actual <= vsize_estimated and vsize_estimated - vsize_actual < 4) - assert (ci.rpc_callback('sendrawtransaction', [lock_spend_tx.hex()]) == txid) + assert (ci.rpc('sendrawtransaction', [lock_spend_tx.hex()]) == txid) expect_vsize: int = ci.xmr_swap_a_lock_spend_tx_vsize() assert (expect_vsize >= vsize_actual) @@ -766,7 +766,7 @@ class Test(BaseTest): lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid) if lock_tx_b_spend is None: lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid) - lock_tx_b_spend_decoded = ci.rpc_callback('decoderawtransaction', [lock_tx_b_spend.hex()]) + lock_tx_b_spend_decoded = ci.rpc('decoderawtransaction', [lock_tx_b_spend.hex()]) expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize() assert (expect_vsize >= lock_tx_b_spend_decoded['vsize']) @@ -1354,7 +1354,7 @@ class Test(BaseTest): lock_tx_b_spend = ci.getTransaction(lock_tx_b_spend_txid) if lock_tx_b_spend is None: lock_tx_b_spend = ci.getWalletTransaction(lock_tx_b_spend_txid) - lock_tx_b_spend_decoded = ci.rpc_callback('decoderawtransaction', [lock_tx_b_spend.hex()]) + lock_tx_b_spend_decoded = ci.rpc('decoderawtransaction', [lock_tx_b_spend.hex()]) expect_vsize: int = ci.xmr_swap_b_lock_spend_tx_vsize() assert (expect_vsize >= lock_tx_b_spend_decoded['vsize']) @@ -1501,7 +1501,7 @@ class Test(BaseTest): # Verify expected inputs were used bid, _, _, _, _ = swap_clients[2].getXmrBidAndOffer(bid_id) assert (bid.xmr_a_lock_tx) - wtx = ci.rpc_callback('gettransaction', [bid.xmr_a_lock_tx.txid.hex(),]) + wtx = ci.rpc('gettransaction', [bid.xmr_a_lock_tx.txid.hex(),]) itx_after = ci.describeTx(wtx['hex']) assert (len(itx_after['vin']) == len(itx_decoded['vin'])) for i, txin in enumerate(itx_decoded['vin']):