From dc4f0ac2d3b15609d73cb6b6b60c99c6ab170caf Mon Sep 17 00:00:00 2001 From: tecnovert Date: Sat, 25 May 2024 21:50:39 +0200 Subject: [PATCH] wownero: deduplicate --- basicswap/basicswap.py | 5 +- basicswap/chainparams.py | 2 +- basicswap/http_server.py | 18 +- basicswap/interface/wow.py | 564 +------------------------------------ basicswap/interface/xmr.py | 12 +- basicswap/rpc_wow.py | 258 ----------------- bin/basicswap_prepare.py | 4 +- bin/basicswap_run.py | 4 +- 8 files changed, 23 insertions(+), 844 deletions(-) delete mode 100644 basicswap/rpc_wow.py diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index e99b431..2cdde06 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -34,7 +34,6 @@ from .interface.part import PARTInterface, PARTInterfaceAnon, PARTInterfaceBlind from . import __version__ from .rpc import escape_rpcauth from .rpc_xmr import make_xmr_rpc2_func -from .rpc_wow import make_wow_rpc2_func from .ui.util import getCoinName, known_chart_coins from .util import ( AutomationConstraint, @@ -601,10 +600,8 @@ class BasicSwap(BaseApp): if proxy_host: self.log.info(f'Connecting through proxy at {proxy_host}.') - if coin == Coins.XMR: + if coin in (Coins.XMR, Coins.WOW): return make_xmr_rpc2_func(rpcport, daemon_login, rpchost, proxy_host=proxy_host, proxy_port=proxy_port) - if coin == Coins.WOW: - return make_wow_rpc2_func(rpcport, daemon_login, rpchost, proxy_host=proxy_host, proxy_port=proxy_port) daemon_login = None if coin_settings.get('rpcuser', '') != '': diff --git a/basicswap/chainparams.py b/basicswap/chainparams.py index ba8bc58..65f070f 100644 --- a/basicswap/chainparams.py +++ b/basicswap/chainparams.py @@ -29,7 +29,7 @@ class Coins(IntEnum): FIRO = 13 NAV = 14 LTC_MWEB = 15 - # ZANO = 16 was 9 + # ZANO = 16 chainparams = { diff --git a/basicswap/http_server.py b/basicswap/http_server.py index 6a2d846..ad5cadf 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -295,7 +295,7 @@ class HttpHandler(BaseHTTPRequestHandler): cmd = get_data_entry(form_data, 'cmd') except Exception: raise ValueError('Invalid command') - if coin_type in (Coins.XMR, ): + if coin_type in (Coins.XMR, Coins.WOW): ci = swap_client.ci(coin_type) arr = cmd.split(None, 1) method = arr[0] @@ -311,22 +311,6 @@ class HttpHandler(BaseHTTPRequestHandler): else: raise ValueError('Unknown RPC variant') result = json.dumps(rv, indent=4) - elif coin_type == Coins.WOW: - ci = swap_client.ci(coin_type) - arr = cmd.split(None, 1) - method = arr[0] - params = json.loads(arr[1]) if len(arr) > 1 else [] - if coin_id == -8: - rv = ci.rpc_wallet(method, params) - elif coin_id == -7: - rv = ci.rpc(method, params) - elif coin_id == -6: - if params == []: - params = None - rv = ci.rpc2(method, params) - else: - raise ValueError('Unknown WOW RPC variant') - result = json.dumps(rv, indent=4) else: if call_type == 'http': ci = swap_client.ci(coin_type) diff --git a/basicswap/interface/wow.py b/basicswap/interface/wow.py index 9d66a45..94df954 100644 --- a/basicswap/interface/wow.py +++ b/basicswap/interface/wow.py @@ -1,54 +1,23 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (c) 2020-2024 tecnovert # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. -import json -import logging - -import basicswap.contrib.ed25519_fast as edf -import basicswap.ed25519_fast_util as edu -import basicswap.util_xmr as wow_util -from coincurve.ed25519 import ( - ed25519_add, - ed25519_get_pubkey, - ed25519_scalar_add, -) -from coincurve.keys import PrivateKey -from coincurve.dleag import ( - dleag_prove, - dleag_verify, - dleag_proof_len, - verify_ed25519_point, -) - -from basicswap.interface import ( - Curves) -from basicswap.util import ( - i2b, b2i, b2h, - dumpj, - ensure, - make_int, - TemporaryError) -from basicswap.util.network import ( - is_private_ip_address) -from basicswap.rpc_wow import ( - make_wow_rpc_func, - make_wow_rpc2_func) -from basicswap.chainparams import WOW_COIN, CoinInterface, Coins +from basicswap.chainparams import WOW_COIN, Coins +from .xmr import XMRInterface -class WOWInterface(CoinInterface): - @staticmethod - def curve_type(): - return Curves.ed25519 +class WOWInterface(XMRInterface): @staticmethod def coin_type(): return Coins.WOW + @staticmethod + def ticker_str() -> int: + return Coins.WOW.name + @staticmethod def COIN(): return WOW_COIN @@ -56,522 +25,3 @@ class WOWInterface(CoinInterface): @staticmethod def exp() -> int: return 11 - - @staticmethod - def nbk() -> int: - return 32 - - @staticmethod - def nbK() -> int: # No. of bytes requires to encode a public key - return 32 - - @staticmethod - def depth_spendable() -> int: - return 10 - - @staticmethod - def xmr_swap_a_lock_spend_tx_vsize() -> int: - raise ValueError('Not possible') - - @staticmethod - def xmr_swap_b_lock_spend_tx_vsize() -> int: - # TODO: Estimate with ringsize - return 1604 - - def __init__(self, coin_settings, network, swap_client=None): - super().__init__(network) - - self._addr_prefix = self.chainparams_network()['address_prefix'] - - self.blocks_confirmed = coin_settings['blocks_confirmed'] - self._restore_height = coin_settings.get('restore_height', 0) - self.setFeePriority(coin_settings.get('fee_priority', 0)) - self._sc = swap_client - self._log = self._sc.log if self._sc and self._sc.log else logging - self._wallet_password = None - self._have_checked_seed = False - - daemon_login = None - if coin_settings.get('rpcuser', '') != '': - daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', '')) - - rpchost = coin_settings.get('rpchost', '127.0.0.1') - proxy_host = None - proxy_port = None - # Connect to the daemon over a proxy if not running locally - if swap_client: - chain_client_settings = swap_client.getChainClientSettings(self.coin_type()) - manage_daemon: bool = chain_client_settings['manage_daemon'] - if swap_client.use_tor_proxy: - if manage_daemon is False: - log_str: str = '' - have_cc_tor_opt = 'use_tor' in chain_client_settings - if have_cc_tor_opt and chain_client_settings['use_tor'] is False: - log_str = ' bypassing proxy (use_tor false for WOW)' - elif have_cc_tor_opt is False and is_private_ip_address(rpchost): - log_str = ' bypassing proxy (private ip address)' - else: - proxy_host = swap_client.tor_proxy_host - proxy_port = swap_client.tor_proxy_port - log_str = f' through proxy at {proxy_host}' - self._log.info(f'Connecting to remote {self.coin_name()} daemon at {rpchost}{log_str}.') - else: - self._log.info(f'Not connecting to local {self.coin_name()} daemon through proxy.') - elif manage_daemon is False: - self._log.info(f'Connecting to remote {self.coin_name()} daemon at {rpchost}.') - - self._rpctimeout = coin_settings.get('rpctimeout', 60) - self._walletrpctimeout = coin_settings.get('walletrpctimeout', 120) - self._walletrpctimeoutlong = coin_settings.get('walletrpctimeoutlong', 600) - - self.rpc = make_wow_rpc_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port, default_timeout=self._rpctimeout, tag='Node(j) ') - self.rpc2 = make_wow_rpc2_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port, default_timeout=self._rpctimeout, tag='Node ') # non-json endpoint - self.rpc_wallet = make_wow_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'), default_timeout=self._walletrpctimeout, tag='Wallet ') - - 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 - - def setWalletFilename(self, wallet_filename): - self._wallet_filename = wallet_filename - - def createWallet(self, params): - if self._wallet_password is not None: - params['password'] = self._wallet_password - rv = self.rpc_wallet('generate_from_keys', params) - self._log.info('generate_from_keys %s', dumpj(rv)) - - def openWallet(self, filename): - params = {'filename': filename} - if self._wallet_password is not None: - params['password'] = self._wallet_password - - try: - # Can't reopen the same wallet in windows, !is_keys_file_locked() - self.rpc_wallet('close_wallet') - except Exception: - pass - self.rpc_wallet('open_wallet', params) - - def initialiseWallet(self, key_view, key_spend, restore_height=None): - with self._mx_wallet: - try: - self.openWallet(self._wallet_filename) - # TODO: Check address - return # Wallet exists - except Exception as e: - pass - - Kbv = self.getPubkey(key_view) - Kbs = self.getPubkey(key_spend) - address_b58 = wow_util.encode_address(Kbv, Kbs, self._addr_prefix) - - params = { - 'filename': self._wallet_filename, - 'address': address_b58, - 'viewkey': b2h(key_view[::-1]), - 'spendkey': b2h(key_spend[::-1]), - 'restore_height': self._restore_height, - } - self.createWallet(params) - self.openWallet(self._wallet_filename) - - def ensureWalletExists(self) -> None: - with self._mx_wallet: - self.openWallet(self._wallet_filename) - - def testDaemonRPC(self, with_wallet=True) -> None: - self.rpc_wallet('get_languages') - - def getDaemonVersion(self): - return self.rpc_wallet('get_version')['version'] - - def getBlockchainInfo(self): - get_height = self.rpc2('get_height', timeout=self._rpctimeout) - rv = { - 'blocks': get_height['height'], - 'verificationprogress': 0.0, - } - - try: - # get_block_count.block_count is how many blocks are in the longest chain known to the node. - # get_block_count returns "Internal error" if bootstrap-daemon is active - if get_height['untrusted'] is True: - rv['bootstrapping'] = True - get_info = self.rpc2('get_info', timeout=self._rpctimeout) - if 'height_without_bootstrap' in get_info: - rv['blocks'] = get_info['height_without_bootstrap'] - - rv['known_block_count'] = get_info['height'] - if rv['known_block_count'] > rv['blocks']: - rv['verificationprogress'] = rv['blocks'] / rv['known_block_count'] - else: - rv['known_block_count'] = self.rpc('get_block_count', timeout=self._rpctimeout)['count'] - rv['verificationprogress'] = rv['blocks'] / rv['known_block_count'] - except Exception as e: - self._log.warning('WOW get_block_count failed with: %s', str(e)) - rv['verificationprogress'] = 0.0 - - return rv - - def getChainHeight(self): - return self.rpc2('get_height', timeout=self._rpctimeout)['height'] - - def getWalletInfo(self): - with self._mx_wallet: - try: - self.openWallet(self._wallet_filename) - except Exception as e: - if 'Failed to open wallet' in str(e): - rv = {'encrypted': True, 'locked': True, 'balance': 0, 'unconfirmed_balance': 0} - return rv - raise e - - rv = {} - self.rpc_wallet('refresh') - balance_info = self.rpc_wallet('get_balance') - - rv['balance'] = self.format_amount(balance_info['unlocked_balance']) - rv['unconfirmed_balance'] = self.format_amount(balance_info['balance'] - balance_info['unlocked_balance']) - rv['encrypted'] = False if self._wallet_password is None else True - rv['locked'] = False - return rv - - def walletRestoreHeight(self): - return self._restore_height - - def getMainWalletAddress(self) -> str: - with self._mx_wallet: - self.openWallet(self._wallet_filename) - return self.rpc_wallet('get_address')['address'] - - def getNewAddress(self, placeholder) -> str: - with self._mx_wallet: - self.openWallet(self._wallet_filename) - new_address = self.rpc_wallet('create_address', {'account_index': 0})['address'] - self.rpc_wallet('store') - return new_address - - def get_fee_rate(self, conf_target: int = 2): - # fees - array of unsigned int; Represents the base fees at different priorities [slow, normal, fast, fastest]. - fee_est = self.rpc('get_fee_estimate') - if conf_target <= 1: - conf_target = 1 # normal - else: - conf_target = 0 # slow - fee_per_k_bytes = fee_est['fees'][conf_target] * 1000 - - return float(self.format_amount(fee_per_k_bytes)), 'get_fee_estimate' - - def getNewSecretKey(self) -> bytes: - # Note: Returned bytes are in big endian order - return i2b(edu.get_secret()) - - def pubkey(self, key: bytes) -> bytes: - return edf.scalarmult_B(key) - - def encodeKey(self, vk: bytes) -> str: - return vk[::-1].hex() - - def decodeKey(self, k_hex: str) -> bytes: - return bytes.fromhex(k_hex)[::-1] - - def encodePubkey(self, pk: bytes) -> str: - return edu.encodepoint(pk) - - def decodePubkey(self, pke): - return edf.decodepoint(pke) - - def getPubkey(self, privkey): - return ed25519_get_pubkey(privkey) - - def getAddressFromKeys(self, key_view: bytes, key_spend: bytes) -> str: - pk_view = self.getPubkey(key_view) - pk_spend = self.getPubkey(key_spend) - return wow_util.encode_address(pk_view, pk_spend, self._addr_prefix) - - def verifyKey(self, k: int) -> bool: - i = b2i(k) - return (i < edf.l and i > 8) - - def verifyPubkey(self, pubkey_bytes): - # Calls ed25519_decode_check_point() in secp256k1 - # Checks for small order - return verify_ed25519_point(pubkey_bytes) - - def proveDLEAG(self, key: bytes) -> bytes: - privkey = PrivateKey(key) - return dleag_prove(privkey) - - def verifyDLEAG(self, dleag_bytes: bytes) -> bool: - return dleag_verify(dleag_bytes) - - def lengthDLEAG(self) -> int: - return dleag_proof_len() - - def sumKeys(self, ka: bytes, kb: bytes) -> bytes: - return ed25519_scalar_add(ka, kb) - - def sumPubkeys(self, Ka: bytes, Kb: bytes) -> bytes: - return ed25519_add(Ka, Kb) - - def encodeSharedAddress(self, Kbv: bytes, Kbs: bytes) -> str: - return wow_util.encode_address(Kbv, Kbs, self._addr_prefix) - - def publishBLockTx(self, kbv: bytes, Kbs: bytes, output_amount: int, feerate: int, unlock_time: int = 0) -> bytes: - with self._mx_wallet: - self.openWallet(self._wallet_filename) - self.rpc_wallet('refresh') - - Kbv = self.getPubkey(kbv) - shared_addr = wow_util.encode_address(Kbv, Kbs, self._addr_prefix) - - params = {'destinations': [{'amount': output_amount, 'address': shared_addr}], 'unlock_time': unlock_time} - if self._fee_priority > 0: - params['priority'] = self._fee_priority - rv = self.rpc_wallet('transfer', params) - self._log.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr) - tx_hash = bytes.fromhex(rv['tx_hash']) - - return tx_hash - - def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender): - with self._mx_wallet: - Kbv = self.getPubkey(kbv) - address_b58 = wow_util.encode_address(Kbv, Kbs, self._addr_prefix) - - kbv_le = kbv[::-1] - params = { - 'restore_height': restore_height, - 'filename': address_b58, - 'address': address_b58, - 'viewkey': b2h(kbv_le), - } - - try: - self.openWallet(address_b58) - except Exception as e: - self.createWallet(params) - self.openWallet(address_b58) - - self.rpc_wallet('refresh', timeout=self._walletrpctimeoutlong) - - ''' - # Debug - try: - current_height = self.rpc_wallet('get_height')['height'] - self._log.info('findTxB WOW current_height %d\nAddress: %s', current_height, address_b58) - except Exception as e: - self._log.info('rpc failed %s', str(e)) - current_height = None # If the transfer is available it will be deep enough - # and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed): - ''' - params = {'transfer_type': 'available'} - transfers = self.rpc_wallet('incoming_transfers', params) - 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('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)) - rv = -1 - continue - if transfer['amount'] == cb_swap_value: - return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': 0 if 'block_height' not in transfer else transfer['block_height']} - else: - self._log.warning('Incorrect amount detected for coin b lock txn: {}'.format(transfer['tx_hash'])) - rv = -1 - return rv - - def findTxnByHash(self, txid): - with self._mx_wallet: - self.openWallet(self._wallet_filename) - self.rpc_wallet('refresh', timeout=self._walletrpctimeoutlong) - - try: - current_height = self.rpc2('get_height', timeout=self._rpctimeout)['height'] - self._log.info('findTxnByHash WOW current_height %d\nhash: %s', current_height, txid) - except Exception as e: - self._log.info('rpc failed %s', str(e)) - current_height = None # If the transfer is available it will be deep enough - - params = {'transfer_type': 'available'} - rv = self.rpc_wallet('incoming_transfers', params) - if 'transfers' in rv: - for transfer in rv['transfers']: - if transfer['tx_hash'] == txid \ - and (current_height is None or current_height - transfer['block_height'] > self.blocks_confirmed): - return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': transfer['block_height']} - - return None - - def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee_rate: int, restore_height: int, spend_actual_balance: bool = False) -> bytes: - ''' - Notes: - "Error: No unlocked balance in the specified subaddress(es)" can mean not enough funds after tx fee. - ''' - with self._mx_wallet: - Kbv = self.getPubkey(kbv) - Kbs = self.getPubkey(kbs) - address_b58 = wow_util.encode_address(Kbv, Kbs, self._addr_prefix) - - wallet_filename = address_b58 + '_spend' - - params = { - 'filename': wallet_filename, - 'address': address_b58, - 'viewkey': b2h(kbv[::-1]), - 'spendkey': b2h(kbs[::-1]), - 'restore_height': restore_height, - } - - try: - self.openWallet(wallet_filename) - except Exception as e: - self.createWallet(params) - self.openWallet(wallet_filename) - - self.rpc_wallet('refresh') - rv = self.rpc_wallet('get_balance') - if rv['balance'] < cb_swap_value: - self._log.warning('Balance is too low, checking for existing spend.') - txns = self.rpc_wallet('get_transfers', {'out': True}) - if 'out' in txns: - txns = txns['out'] - if len(txns) > 0: - txid = txns[0]['txid'] - self._log.warning(f'spendBLockTx detected spending tx: {txid}.') - if txns[0]['address'] == address_b58: - return bytes.fromhex(txid) - - self._log.error('wallet {} balance {}, expected {}'.format(wallet_filename, rv['balance'], cb_swap_value)) - - if not spend_actual_balance: - raise TemporaryError('Invalid balance') - - if spend_actual_balance and rv['balance'] != cb_swap_value: - self._log.warning('Spending actual balance {}, not swap value {}.'.format(rv['balance'], cb_swap_value)) - cb_swap_value = rv['balance'] - if rv['unlocked_balance'] < cb_swap_value: - self._log.error('wallet {} balance {}, expected {}, blocks_to_unlock {}'.format(wallet_filename, rv['unlocked_balance'], cb_swap_value, rv['blocks_to_unlock'])) - raise TemporaryError('Invalid unlocked_balance') - - params = {'address': address_to} - if self._fee_priority > 0: - params['priority'] = self._fee_priority - - rv = self.rpc_wallet('sweep_all', params) - self._log.debug('sweep_all {}'.format(json.dumps(rv))) - - return bytes.fromhex(rv['tx_hash_list'][0]) - - def withdrawCoin(self, value, addr_to: str, sweepall: bool, estimate_fee: bool = False) -> str: - with self._mx_wallet: - self.openWallet(self._wallet_filename) - self.rpc_wallet('refresh') - - if sweepall: - balance = self.rpc_wallet('get_balance') - if balance['balance'] != balance['unlocked_balance']: - raise ValueError('Balance must be fully confirmed to use sweep all.') - self._log.info('WOW {} sweep_all.'.format('estimate fee' if estimate_fee else 'withdraw')) - self._log.debug('WOW balance: {}'.format(balance['balance'])) - params = {'address': addr_to, 'do_not_relay': estimate_fee} - if self._fee_priority > 0: - params['priority'] = self._fee_priority - rv = self.rpc_wallet('sweep_all', params) - if estimate_fee: - return {'num_txns': len(rv['fee_list']), 'sum_amount': sum(rv['amount_list']), 'sum_fee': sum(rv['fee_list']), 'sum_weight': sum(rv['weight_list'])} - return rv['tx_hash_list'][0] - - value_sats: int = make_int(value, self.exp()) - params = {'destinations': [{'amount': value_sats, 'address': addr_to}], 'do_not_relay': estimate_fee} - if self._fee_priority > 0: - params['priority'] = self._fee_priority - rv = self.rpc_wallet('transfer', params) - if estimate_fee: - return {'num_txns': 1, 'sum_amount': rv['amount'], 'sum_fee': rv['fee'], 'sum_weight': rv['weight']} - return rv['tx_hash'] - - def estimateFee(self, value: int, addr_to: str, sweepall: bool) -> str: - return self.withdrawCoin(value, addr_to, sweepall, estimate_fee=True) - - def showLockTransfers(self, kbv, Kbs, restore_height): - with self._mx_wallet: - try: - Kbv = self.getPubkey(kbv) - address_b58 = wow_util.encode_address(Kbv, Kbs, self._addr_prefix) - wallet_file = address_b58 + '_spend' - try: - self.openWallet(wallet_file) - except Exception: - wallet_file = address_b58 - try: - self.openWallet(wallet_file) - except Exception: - self._log.info(f'showLockTransfers trying to create wallet for address {address_b58}.') - kbv_le = kbv[::-1] - params = { - 'restore_height': restore_height, - 'filename': address_b58, - 'address': address_b58, - 'viewkey': b2h(kbv_le), - } - self.createWallet(params) - self.openWallet(address_b58) - - self.rpc_wallet('refresh') - - rv = self.rpc_wallet('get_transfers', {'in': True, 'out': True, 'pending': True, 'failed': True}) - rv['filename'] = wallet_file - return rv - except Exception as e: - return {'error': str(e)} - - def getSpendableBalance(self) -> int: - with self._mx_wallet: - self.openWallet(self._wallet_filename) - - self.rpc_wallet('refresh') - balance_info = self.rpc_wallet('get_balance') - return balance_info['unlocked_balance'] - - def changeWalletPassword(self, old_password, new_password): - self._log.info('changeWalletPassword - {}'.format(self.ticker())) - orig_password = self._wallet_password - if old_password != '': - self._wallet_password = old_password - try: - self.openWallet(self._wallet_filename) - self.rpc_wallet('change_wallet_password', {'old_password': old_password, 'new_password': new_password}) - except Exception as e: - self._wallet_password = orig_password - raise e - - def unlockWallet(self, password: str) -> None: - self._log.info('unlockWallet - {}'.format(self.ticker())) - self._wallet_password = password - - if not self._have_checked_seed: - self._sc.checkWalletSeed(self.coin_type()) - - def lockWallet(self) -> None: - self._log.info('lockWallet - {}'.format(self.ticker())) - self._wallet_password = None - - def isAddressMine(self, address): - # TODO - return True - - def ensureFunds(self, amount: int) -> None: - if self.getSpendableBalance() < amount: - raise ValueError('Balance too low') - - def getTransaction(self, txid: bytes): - return self.rpc2('get_transactions', {'txs_hashes': [txid.hex(), ]}) diff --git a/basicswap/interface/xmr.py b/basicswap/interface/xmr.py index 387ba0b..efc31a7 100644 --- a/basicswap/interface/xmr.py +++ b/basicswap/interface/xmr.py @@ -50,6 +50,10 @@ class XMRInterface(CoinInterface): def coin_type(): return Coins.XMR + @staticmethod + def ticker_str() -> int: + return Coins.XMR.name + @staticmethod def COIN(): return XMR_COIN @@ -210,7 +214,7 @@ class XMRInterface(CoinInterface): rv['known_block_count'] = self.rpc('get_block_count', timeout=self._rpctimeout)['count'] rv['verificationprogress'] = rv['blocks'] / rv['known_block_count'] except Exception as e: - self._log.warning('XMR get_block_count failed with: %s', str(e)) + self._log.warning(f'{self.ticker_str()} get_block_count failed with: {e}') rv['verificationprogress'] = 0.0 return rv @@ -391,7 +395,7 @@ class XMRInterface(CoinInterface): try: current_height = self.rpc2('get_height', timeout=self._rpctimeout)['height'] - self._log.info('findTxnByHash XMR current_height %d\nhash: %s', current_height, txid) + self._log.info(f'findTxnByHash {self.ticker_str()} current_height {current_height}\nhash: {txid}') except Exception as e: self._log.info('rpc failed %s', str(e)) current_height = None # If the transfer is available it will be deep enough @@ -475,8 +479,8 @@ class XMRInterface(CoinInterface): balance = self.rpc_wallet('get_balance') if balance['balance'] != balance['unlocked_balance']: raise ValueError('Balance must be fully confirmed to use sweep all.') - self._log.info('XMR {} sweep_all.'.format('estimate fee' if estimate_fee else 'withdraw')) - self._log.debug('XMR balance: {}'.format(balance['balance'])) + self._log.info('{} {} sweep_all.'.format(self.ticker_str(), 'estimate fee' if estimate_fee else 'withdraw')) + self._log.debug('{} balance: {}'.format(self.ticker_str(), balance['balance'])) params = {'address': addr_to, 'do_not_relay': estimate_fee} if self._fee_priority > 0: params['priority'] = self._fee_priority diff --git a/basicswap/rpc_wow.py b/basicswap/rpc_wow.py deleted file mode 100644 index 21ccac3..0000000 --- a/basicswap/rpc_wow.py +++ /dev/null @@ -1,258 +0,0 @@ -# -*- coding: utf-8 -*- - -import os -import json -import socks -import time -import urllib -import hashlib -from xmlrpc.client import ( - Fault, - Transport, - SafeTransport, -) -from sockshandler import SocksiPyConnection -from .util import jsonDecimal - - -class SocksTransport(Transport): - - def set_proxy(self, proxy_host, proxy_port): - self.proxy_host = proxy_host - self.proxy_port = proxy_port - - self.proxy_type = socks.PROXY_TYPE_SOCKS5 - self.proxy_rdns = True - self.proxy_username = None - self.proxy_password = None - - def make_connection(self, host): - # return an existing connection if possible. This allows - # HTTP/1.1 keep-alive. - if self._connection and host == self._connection[0]: - return self._connection[1] - # create a HTTP connection object from a host descriptor - chost, self._extra_headers, x509 = self.get_host_info(host) - self._connection = host, SocksiPyConnection(self.proxy_type, self.proxy_host, self.proxy_port, self.proxy_rdns, self.proxy_username, self.proxy_password, chost) - return self._connection[1] - - -class JsonrpcDigest(): - # __getattr__ complicates extending ServerProxy - def __init__(self, uri, transport=None, encoding=None, verbose=False, - allow_none=False, use_datetime=False, use_builtin_types=False, - *, context=None): - - parsed = urllib.parse.urlparse(uri) - if parsed.scheme not in ('http', 'https'): - raise OSError('unsupported XML-RPC protocol') - self.__host = parsed.netloc - self.__handler = parsed.path - - if transport is None: - handler = SafeTransport if parsed.scheme == 'https' else Transport - extra_kwargs = {} - transport = handler(use_datetime=use_datetime, - use_builtin_types=use_builtin_types, - **extra_kwargs) - self.__transport = transport - - self.__encoding = encoding or 'utf-8' - self.__verbose = verbose - self.__allow_none = allow_none - - self.__request_id = 0 - - def close(self): - if self.__transport is not None: - self.__transport.close() - - def request_id(self): - return self.__request_id - - def post_request(self, method, params, timeout=None): - try: - connection = self.__transport.make_connection(self.__host) - if timeout: - connection.timeout = timeout - headers = self.__transport._extra_headers[:] - - connection.putrequest('POST', self.__handler) - headers.append(('Content-Type', 'application/json')) - headers.append(('User-Agent', 'jsonrpc')) - self.__transport.send_headers(connection, headers) - self.__transport.send_content(connection, '' if params is None else json.dumps(params, default=jsonDecimal).encode('utf-8')) - self.__request_id += 1 - - resp = connection.getresponse() - return resp.read() - - except Fault: - raise - except Exception: - self.__transport.close() - raise - - def json_request(self, request_body, username='', password='', timeout=None): - try: - connection = self.__transport.make_connection(self.__host) - if timeout: - connection.timeout = timeout - - headers = self.__transport._extra_headers[:] - - connection.putrequest('POST', self.__handler) - headers.append(('Content-Type', 'application/json')) - headers.append(('Connection', 'keep-alive')) - self.__transport.send_headers(connection, headers) - self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '') - resp = connection.getresponse() - - if resp.status == 401: - resp_headers = resp.getheaders() - v = resp.read() - - algorithm = '' - realm = '' - nonce = '' - for h in resp_headers: - if h[0] != 'WWW-authenticate': - continue - fields = h[1].split(',') - for f in fields: - key, value = f.split('=', 1) - if key == 'algorithm' and value != 'MD5': - break - if key == 'realm': - realm = value.strip('"') - if key == 'nonce': - nonce = value.strip('"') - if realm != '' and nonce != '': - break - - if realm == '' or nonce == '': - raise ValueError('Authenticate header not found.') - - path = self.__handler - HA1 = hashlib.md5(f'{username}:{realm}:{password}'.encode('utf-8')).hexdigest() - - http_method = 'POST' - HA2 = hashlib.md5(f'{http_method}:{path}'.encode('utf-8')).hexdigest() - - ncvalue = '{:08x}'.format(1) - s = ncvalue.encode('utf-8') - s += nonce.encode('utf-8') - s += time.ctime().encode('utf-8') - s += os.urandom(8) - cnonce = (hashlib.sha1(s).hexdigest()[:16]) - - # MD5-SESS - HA1 = hashlib.md5(f'{HA1}:{nonce}:{cnonce}'.encode('utf-8')).hexdigest() - - respdig = hashlib.md5(f'{HA1}:{nonce}:{ncvalue}:{cnonce}:auth:{HA2}'.encode('utf-8')).hexdigest() - - header_value = f'Digest username="{username}", realm="{realm}", nonce="{nonce}", uri="{path}", response="{respdig}", algorithm="MD5-sess", qop="auth", nc={ncvalue}, cnonce="{cnonce}"' - headers = self.__transport._extra_headers[:] - headers.append(('Authorization', header_value)) - - connection.putrequest('POST', self.__handler) - headers.append(('Content-Type', 'application/json')) - headers.append(('Connection', 'keep-alive')) - self.__transport.send_headers(connection, headers) - self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '') - resp = connection.getresponse() - - self.__request_id += 1 - return resp.read() - - except Fault: - raise - except Exception: - self.__transport.close() - raise - - -def callrpc_wow(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rpc', auth=None, timeout=120, transport=None, tag=''): - # auth is a tuple: (username, password) - try: - if rpc_host.count('://') > 0: - url = '{}:{}/{}'.format(rpc_host, rpc_port, path) - else: - url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, path) - - x = JsonrpcDigest(url, transport=transport) - request_body = { - 'method': method, - 'params': params, - 'jsonrpc': '2.0', - 'id': x.request_id() - } - if auth: - v = x.json_request(request_body, username=auth[0], password=auth[1], timeout=timeout) - else: - v = x.json_request(request_body, timeout=timeout) - x.close() - r = json.loads(v.decode('utf-8')) - except Exception as ex: - raise ValueError('{}RPC Server Error: {}'.format(tag, str(ex))) - - if 'error' in r and r['error'] is not None: - raise ValueError(tag + 'RPC error ' + str(r['error'])) - - return r['result'] - - -def callrpc_wow2(rpc_port: int, method: str, params=None, auth=None, rpc_host='127.0.0.1', timeout=120, transport=None, tag=''): - try: - if rpc_host.count('://') > 0: - url = '{}:{}/{}'.format(rpc_host, rpc_port, method) - else: - url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, method) - - x = JsonrpcDigest(url, transport=transport) - if auth: - v = x.json_request(params, username=auth[0], password=auth[1], timeout=timeout) - else: - v = x.json_request(params, timeout=timeout) - x.close() - r = json.loads(v.decode('utf-8')) - except Exception as ex: - raise ValueError('{}RPC Server Error: {}'.format(tag, str(ex))) - - return r - - -def make_wow_rpc2_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None, default_timeout=120, tag=''): - port = port - auth = auth - host = host - transport = None - default_timeout = default_timeout - tag = tag - - if proxy_host: - transport = SocksTransport() - transport.set_proxy(proxy_host, proxy_port) - - def rpc_func(method, params=None, wallet=None, timeout=default_timeout): - nonlocal port, auth, host, transport, tag - return callrpc_wow2(port, method, params, auth=auth, rpc_host=host, timeout=timeout, transport=transport, tag=tag) - return rpc_func - - -def make_wow_rpc_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None, default_timeout=120, tag=''): - port = port - auth = auth - host = host - transport = None - default_timeout = default_timeout - tag = tag - - if proxy_host: - transport = SocksTransport() - transport.set_proxy(proxy_host, proxy_port) - - def rpc_func(method, params=None, wallet=None, timeout=default_timeout): - nonlocal port, auth, host, transport, tag - return callrpc_wow(port, method, params, rpc_host=host, auth=auth, timeout=timeout, transport=transport, tag=tag) - return rpc_func diff --git a/bin/basicswap_prepare.py b/bin/basicswap_prepare.py index 60905af..22dda0f 100755 --- a/bin/basicswap_prepare.py +++ b/bin/basicswap_prepare.py @@ -85,8 +85,9 @@ SKIP_GPG_VALIDATION = toBool(os.getenv('SKIP_GPG_VALIDATION', 'false')) known_coins = { 'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)), - 'litecoin': (LITECOIN_VERSION, LITECOIN_VERSION_TAG, ('davidburkett38',)), 'bitcoin': (BITCOIN_VERSION, BITCOIN_VERSION_TAG, ('laanwj',)), + 'litecoin': (LITECOIN_VERSION, LITECOIN_VERSION_TAG, ('davidburkett38',)), + 'decred': (DCR_VERSION, DCR_VERSION_TAG, ('decred_release',)), 'namecoin': ('0.18.0', '', ('JeremyRand',)), 'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)), 'wownero': (WOWNERO_VERSION, WOWNERO_VERSION_TAG, ('wowario',)), @@ -94,7 +95,6 @@ known_coins = { 'dash': (DASH_VERSION, DASH_VERSION_TAG, ('pasta',)), 'firo': (FIRO_VERSION, FIRO_VERSION_TAG, ('reuben',)), 'navcoin': (NAV_VERSION, NAV_VERSION_TAG, ('nav_builder',)), - 'decred': (DCR_VERSION, DCR_VERSION_TAG, ('decred_release',)), } disabled_coins = [ diff --git a/bin/basicswap_run.py b/bin/basicswap_run.py index 56937c8..a868e0e 100755 --- a/bin/basicswap_run.py +++ b/bin/basicswap_run.py @@ -118,7 +118,9 @@ def startXmrWalletDaemon(node_dir, bin_dir, wallet_bin, opts=[]): config_to_remove = ['daemon-address=', 'untrusted-daemon=', 'trusted-daemon=', 'proxy='] data_dir = os.path.expanduser(node_dir) - config_path = os.path.join(data_dir, 'monero_wallet.conf') + + wallet_config_filename = 'wownero-wallet-rpc.conf' if wallet_bin.startswith('wow') else 'monero_wallet.conf' + config_path = os.path.join(data_dir, wallet_config_filename) if os.path.exists(config_path): args += ['--config-file=' + config_path] with open(config_path) as fp: