diff --git a/basicswap/__init__.py b/basicswap/__init__.py index 40ef697..84acba5 100644 --- a/basicswap/__init__.py +++ b/basicswap/__init__.py @@ -1,3 +1,3 @@ name = "basicswap" -__version__ = "0.0.4" +__version__ = "0.0.5" diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index dc1284b..e3e328e 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -22,6 +22,7 @@ from enum import IntEnum, auto from .interface_part import PARTInterface from .interface_btc import BTCInterface from .interface_ltc import LTCInterface +from .interface_nmc import NMCInterface from .interface_xmr import XMRInterface from . import __version__ @@ -29,6 +30,7 @@ from .util import ( COIN, pubkeyToAddress, format8, + format_amount, encodeAddress, decodeAddress, SerialiseNum, @@ -80,12 +82,15 @@ class MessageTypes(IntEnum): BID = auto() BID_ACCEPT = auto() + XMR_OFFER = auto() + class SwapTypes(IntEnum): SELLER_FIRST = auto() BUYER_FIRST = auto() SELLER_FIRST_2MSG = auto() BUYER_FIRST_2MSG = auto() + XMR_SWAP = auto() class OfferStates(IntEnum): @@ -317,7 +322,7 @@ class BasicSwap(BaseApp): self.network_pubkey = self.settings['network_pubkey'] self.network_addr = pubkeyToAddress(chainparams[Coins.PART][self.chain]['pubkey_address'], bytearray.fromhex(self.network_pubkey)) - self.wallet = self.settings.get('wallet', None) # TODO: Move to coin_clients + #self.wallet = self.settings.get('wallet', None) # TODO: Move to coin_clients self.sqlite_file = os.path.join(self.data_dir, 'db{}.sqlite'.format('' if self.chain == 'mainnet' else ('_' + self.chain))) db_exists = os.path.exists(self.sqlite_file) @@ -341,6 +346,20 @@ class BasicSwap(BaseApp): self._contract_count = session.query(DBKVInt).filter_by(key='contract_count').first().value except Exception: self._contract_count = 0 + session.add(DBKVInt( + key='contract_count', + value=self._contract_count + )) + session.commit() + try: + self._offer_count = session.query(DBKVInt).filter_by(key='offer_count').first().value + except Exception: + self._offer_count = 0 + session.add(DBKVInt( + key='offer_count', + value=self._offer_count + )) + session.commit() session.close() session.remove() @@ -426,7 +445,7 @@ class BasicSwap(BaseApp): if coin == Coins.XMR: self.coin_clients[coin]['walletrpcport'] = chain_client_settings.get('walletrpcport', chainparams[coin][self.chain]['walletrpcport']) if 'walletrpcpassword' in chain_client_settings: - self.coin_clients[coin]['walletrpcauth'] = chain_client_settings['walletrpcuser'] + ':' + chain_client_settings['walletrpcpassword'] + self.coin_clients[coin]['walletrpcauth'] = (chain_client_settings['walletrpcuser'], chain_client_settings['walletrpcpassword']) else: raise ValueError('Missing XMR wallet rpc credentials.') self.coin_clients[coin]['interface'] = self.createInterface(coin) @@ -438,6 +457,8 @@ class BasicSwap(BaseApp): return BTCInterface(self.coin_clients[coin]) elif coin == Coins.LTC: return LTCInterface(self.coin_clients[coin]) + elif coin == Coins.NMC: + return NMCInterface(self.coin_clients[coin]) elif coin == Coins.XMR: return XMRInterface(self.coin_clients[coin]) else: @@ -445,6 +466,8 @@ class BasicSwap(BaseApp): def setCoinRunParams(self, coin): cc = self.coin_clients[coin] + if coin == Coins.XMR: + return if cc['connection_type'] == 'rpc' and cc['rpcauth'] is None: chain_client_settings = self.getChainClientSettings(coin) authcookiepath = os.path.join(self.getChainDatadirPath(coin), '.cookie') @@ -474,6 +497,10 @@ class BasicSwap(BaseApp): self.log.error('Unable to read authcookie for %s, %s, datadir pid %d, daemon pid %s', str(coin), authcookiepath, datadir_pid, cc['pid']) raise ValueError('Error, terminating') + def createCoinInterface(self, coin): + if self.coin_clients[coin]['connection_type'] == 'rpc': + self.coin_clients[coin]['interface'] = self.createInterface(coin) + def start(self): self.log.info('Starting BasicSwap %s\n\n', __version__) self.log.info('sqlalchemy version %s', sa.__version__) @@ -482,9 +509,11 @@ class BasicSwap(BaseApp): for c in Coins: self.setCoinRunParams(c) + self.createCoinInterface(c) + if self.coin_clients[c]['connection_type'] == 'rpc': self.waitForDaemonRPC(c) - core_version = self.callcoinrpc(c, 'getnetworkinfo')['version'] + core_version = self.coin_clients[c]['interface'].getDaemonVersion() self.log.info('%s Core version %d', chainparams[c]['name'].capitalize(), core_version) self.coin_clients[c]['core_version'] = core_version @@ -540,7 +569,7 @@ class BasicSwap(BaseApp): if not self.is_running: return try: - self.callcoinrpc(coin_type, 'getwalletinfo', [], self.wallet) + self.coin_clients[coin_type]['interface'].testDaemonRPC() return except Exception as ex: self.log.warning('Can\'t connect to %s RPC: %s. Trying again in %d second/s.', coin_type, str(ex), (1 + i)) @@ -707,10 +736,19 @@ class BasicSwap(BaseApp): self.mxDB.acquire() try: self.checkSynced(coin_from_t, coin_to_t) + # TODO: require proof of funds on offers? proof_addr, proof_sig = self.getProofOfFunds(coin_from_t, amount) - # TODO: require prrof of funds on offers? + offer_addr = self.callrpc('getnewaddress') if addr_send_from is None else addr_send_from + + offer_created_at = int(time.time()) + if swap_type == SwapTypes.XMR_SWAP: + msg_buf = XmrOfferMessage() + + key_path = "44445555h/999999/{}/{}/{}/{}".format(int(coin_from), int(coin_to), offer_created_at, ) + + else: + msg_buf = OfferMessage() - msg_buf = OfferMessage() msg_buf.coin_from = int(coin_from) msg_buf.coin_to = int(coin_to) msg_buf.amount_from = int(amount) @@ -725,10 +763,6 @@ class BasicSwap(BaseApp): offer_bytes = msg_buf.SerializeToString() payload_hex = str.format('{:02x}', MessageTypes.OFFER) + offer_bytes.hex() - if addr_send_from is None: - offer_addr = self.callrpc('getnewaddress') - else: - offer_addr = addr_send_from self.callrpc('smsgaddlocaladdress', [offer_addr]) # Enable receiving smsg options = {'decodehex': True, 'ttl_is_seconds': True} msg_valid = self.SMSG_SECONDS_IN_HOUR * 1 @@ -752,8 +786,8 @@ class BasicSwap(BaseApp): swap_type=msg_buf.swap_type, addr_from=offer_addr, - created_at=int(time.time()), - expire_at=int(time.time()) + msg_buf.time_valid, + created_at=offer_created_at, + expire_at=offer_created_at + msg_buf.time_valid, was_sent=True, auto_accept_bids=auto_accept_bids,) offer.setState(OfferStates.OFFER_SENT) @@ -850,15 +884,7 @@ class BasicSwap(BaseApp): self.mxDB.release() def getReceiveAddressForCoin(self, coin_type): - if coin_type == Coins.PART: - new_addr = self.callcoinrpc(Coins.PART, 'getnewaddress') - elif coin_type == Coins.LTC or coin_type == Coins.BTC or coin_type == Coins.NMC: - args = [] - if self.coin_clients[coin_type]['use_segwit']: - args = ['swap_receive', 'bech32'] - new_addr = self.callcoinrpc(coin_type, 'getnewaddress', args) - else: - raise ValueError('Unknown coin type.') + new_addr = self.coin_clients[coin_type]['interface'].getNewAddress(self.coin_clients[coin_type]['use_segwit']) self.log.debug('Generated new receive address %s for %s', new_addr, str(coin_type)) return new_addr @@ -2101,6 +2127,10 @@ class BasicSwap(BaseApp): assert(len(offer_data.secret_hash) == 0), 'Unexpected data' elif offer_data.swap_type == SwapTypes.BUYER_FIRST: raise ValueError('TODO') + elif offer_data.swap_type == SwapTypes.XMR_SWAP: + assert(coin_from != Coins.XMR) + assert(coin_to == Coins.XMR) + logging.debug('TODO - More restrictions') else: raise ValueError('Unknown swap type {}.'.format(offer_data.swap_type)) @@ -2461,15 +2491,17 @@ class BasicSwap(BaseApp): def getWalletInfo(self, coin): - blockchaininfo = self.callcoinrpc(coin, 'getblockchaininfo') - walletinfo = self.callcoinrpc(coin, 'getwalletinfo') + blockchaininfo = self.coin_clients[coin]['interface'].getBlockchainInfo() + walletinfo = self.coin_clients[coin]['interface'].getWalletInfo() + + scale = chainparams[coin]['decimal_places'] rv = { 'version': self.coin_clients[coin]['core_version'], 'deposit_address': self.getCachedAddressForCoin(coin), 'name': chainparams[coin]['name'].capitalize(), 'blocks': blockchaininfo['blocks'], - 'balance': format8(make_int(walletinfo['balance'])), - 'unconfirmed': format8(make_int(walletinfo.get('unconfirmed_balance'))), + 'balance': format_amount(make_int(walletinfo['balance'], scale), scale), + 'unconfirmed': format_amount(make_int(walletinfo.get('unconfirmed_balance'), scale), scale), 'synced': '{0:.2f}'.format(round(blockchaininfo['verificationprogress'], 2)), } return rv diff --git a/basicswap/chainparams.py b/basicswap/chainparams.py index d778f10..5ea3399 100644 --- a/basicswap/chainparams.py +++ b/basicswap/chainparams.py @@ -9,6 +9,8 @@ from .util import ( COIN, ) +XMR_COIN = 10 ** 12 + class Coins(IntEnum): PART = 1 @@ -25,6 +27,7 @@ chainparams = { 'ticker': 'PART', 'message_magic': 'Bitcoin Signed Message:\n', 'blocks_target': 60 * 2, + 'decimal_places': 8, 'mainnet': { 'rpcport': 51735, 'pubkey_address': 0x38, @@ -61,6 +64,7 @@ chainparams = { 'ticker': 'BTC', 'message_magic': 'Bitcoin Signed Message:\n', 'blocks_target': 60 * 10, + 'decimal_places': 8, 'mainnet': { 'rpcport': 8332, 'pubkey_address': 0, @@ -95,6 +99,7 @@ chainparams = { 'ticker': 'LTC', 'message_magic': 'Litecoin Signed Message:\n', 'blocks_target': 60 * 1, + 'decimal_places': 8, 'mainnet': { 'rpcport': 9332, 'pubkey_address': 48, @@ -129,6 +134,7 @@ chainparams = { 'ticker': 'NMC', 'message_magic': 'Namecoin Signed Message:\n', 'blocks_target': 60 * 10, + 'decimal_places': 8, 'mainnet': { 'rpcport': 8336, 'pubkey_address': 52, @@ -162,17 +168,24 @@ chainparams = { 'name': 'monero', 'ticker': 'XMR', 'client': 'xmr', + 'decimal_places': 12, 'mainnet': { 'rpcport': 18081, 'walletrpcport': 18082, + 'min_amount': 100000, + 'max_amount': 10000 * XMR_COIN, }, 'testnet': { 'rpcport': 28081, 'walletrpcport': 28082, + 'min_amount': 100000, + 'max_amount': 10000 * XMR_COIN, }, 'regtest': { 'rpcport': 18081, 'walletrpcport': 18082, + 'min_amount': 100000, + 'max_amount': 10000 * XMR_COIN, } } } diff --git a/basicswap/config.py b/basicswap/config.py index 74d4b89..0838f05 100644 --- a/basicswap/config.py +++ b/basicswap/config.py @@ -32,3 +32,7 @@ NAMECOIN_BINDIR = os.path.expanduser(os.getenv('NAMECOIN_BINDIR', '')) NAMECOIND = os.getenv('NAMECOIND', 'namecoind' + bin_suffix) NAMECOIN_CLI = os.getenv('NAMECOIN_CLI', 'namecoin-cli' + bin_suffix) NAMECOIN_TX = os.getenv('NAMECOIN_TX', 'namecoin-tx' + bin_suffix) + +XMR_BINDIR = os.path.expanduser(os.getenv('XMR_BINDIR', '')) +XMRD = os.getenv('XMRD', 'monerod' + bin_suffix) +XMR_WALLET_RPC = os.getenv('XMR_WALLET_RPC', 'monero-wallet-rpc' + bin_suffix) diff --git a/basicswap/contrib/rpcauth.py b/basicswap/contrib/rpcauth.py new file mode 100755 index 0000000..f919bf5 --- /dev/null +++ b/basicswap/contrib/rpcauth.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from argparse import ArgumentParser +from base64 import urlsafe_b64encode +from binascii import hexlify +from getpass import getpass +from os import urandom + +import hmac + + +def generate_salt(size): + """Create size byte hex salt""" + return hexlify(urandom(size)).decode() + + +def generate_password(): + """Create 32 byte b64 password""" + return urlsafe_b64encode(urandom(32)).decode('utf-8') + + +def password_to_hmac(salt, password): + m = hmac.new(bytearray(salt, 'utf-8'), bytearray(password, 'utf-8'), 'SHA256') + return m.hexdigest() + + +def main(): + parser = ArgumentParser(description='Create login credentials for a JSON-RPC user') + parser.add_argument('username', help='the username for authentication') + parser.add_argument('password', help='leave empty to generate a random password or specify "-" to prompt for password', nargs='?') + args = parser.parse_args() + + if not args.password: + args.password = generate_password() + elif args.password == '-': + args.password = getpass() + + # Create 16 byte hex salt + salt = generate_salt(16) + password_hmac = password_to_hmac(salt, args.password) + + print('String to be appended to bitcoin.conf:') + print('rpcauth={0}:{1}${2}'.format(args.username, salt, password_hmac)) + print('Your password:\n{0}'.format(args.password)) + + +if __name__ == '__main__': + main() diff --git a/basicswap/interface_btc.py b/basicswap/interface_btc.py index 7c989e8..3a6a61c 100644 --- a/basicswap/interface_btc.py +++ b/basicswap/interface_btc.py @@ -102,6 +102,24 @@ class BTCInterface(CoinInterface): self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth']) self.txoType = CTxOut + def testDaemonRPC(self): + self.rpc_callback('getwalletinfo', []) + + def getDaemonVersion(self): + return self.rpc_callback('getnetworkinfo')['version'] + + def getBlockchainInfo(self): + return self.rpc_callback('getblockchaininfo') + + def getWalletInfo(self): + return self.rpc_callback('getwalletinfo') + + def getNewAddress(self, use_segwit): + args = ['swap_receive'] + if use_segwit: + args.append('bech32') + return self.rpc_callback('getnewaddress', args) + def getNewSecretKey(self): return getSecretInt() diff --git a/basicswap/interface_nmc.py b/basicswap/interface_nmc.py new file mode 100644 index 0000000..c88a6d3 --- /dev/null +++ b/basicswap/interface_nmc.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +from .interface_btc import BTCInterface + + +class NMCInterface(BTCInterface): + pass diff --git a/basicswap/interface_part.py b/basicswap/interface_part.py index a12ce97..cef941c 100644 --- a/basicswap/interface_part.py +++ b/basicswap/interface_part.py @@ -26,3 +26,6 @@ class PARTInterface(BTCInterface): def __init__(self, coin_settings): self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth']) self.txoType = CTxOutPart + + def getNewAddress(self, use_segwit): + return self.rpc_callback('getnewaddress', ['swap_receive']) diff --git a/basicswap/interface_xmr.py b/basicswap/interface_xmr.py index 59289e1..2812c04 100644 --- a/basicswap/interface_xmr.py +++ b/basicswap/interface_xmr.py @@ -10,6 +10,9 @@ import logging from .chainparams import CoinInterface from .rpc_xmr import make_xmr_rpc_func, make_xmr_wallet_rpc_func +from .util import ( + format_amount +) XMR_COIN = 10 ** 12 @@ -31,9 +34,32 @@ class XMRInterface(CoinInterface): rpc_cb = make_xmr_rpc_func(coin_settings['rpcport']) rpc_wallet_cb = make_xmr_wallet_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth']) - self.rpc_cb = rpc_cb # Not essential + self.rpc_cb = rpc_cb self.rpc_wallet_cb = rpc_wallet_cb + def testDaemonRPC(self): + self.rpc_wallet_cb('get_languages') + + def getDaemonVersion(self): + return self.rpc_cb('get_version')['version'] + + def getBlockchainInfo(self): + rv = {} + rv['blocks'] = self.rpc_cb('get_block_count')['count'] + rv['verificationprogress'] = 0 # TODO + return rv + + def getWalletInfo(self): + rv = {} + balance_info = self.rpc_wallet_cb('get_balance') + rv['balance'] = format_amount(balance_info['unlocked_balance'], XMRInterface.exp()) + rv['unconfirmed_balance'] = format_amount(balance_info['balance'] - balance_info['unlocked_balance'], XMRInterface.exp()) + return rv + + def getNewAddress(self, placeholder): + logging.debug('TODO - subaddress?') + return self.rpc_wallet_cb('get_address')['address'] + def getNewSecretKey(self): return edu.get_secret() diff --git a/basicswap/messages.proto b/basicswap/messages.proto index c946143..db8e252 100644 --- a/basicswap/messages.proto +++ b/basicswap/messages.proto @@ -46,3 +46,31 @@ message BidAcceptMessage { bytes initiate_txid = 2; bytes contract_script = 3; } + + +message XmrOfferMessage { + uint32 coin_from = 1; + uint32 coin_to = 2; + uint64 amount_from = 3; + uint64 rate = 4; + uint64 min_bid_amount = 5; + uint64 time_valid = 6; + enum LockType { + NOT_SET = 0; + SEQUENCE_LOCK_BLOCKS = 1; + SEQUENCE_LOCK_TIME = 2; + ABS_LOCK_BLOCKS = 3; + ABS_LOCK_TIME = 4; + } + LockType lock_type = 7; + uint32 lock_value = 8; + uint32 swap_type = 9; + + + + /* optional */ + string proof_address = 10; + string proof_signature = 11; + bytes pkhash_seller = 12; + bytes secret_hash = 13; +} diff --git a/basicswap/messages_pb2.py b/basicswap/messages_pb2.py index a31221a..686d918 100644 --- a/basicswap/messages_pb2.py +++ b/basicswap/messages_pb2.py @@ -2,8 +2,6 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # source: messages.proto -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message from google.protobuf import reflection as _reflection @@ -20,7 +18,8 @@ DESCRIPTOR = _descriptor.FileDescriptor( package='basicswap', syntax='proto3', serialized_options=None, - serialized_pb=_b('\n\x0emessages.proto\x12\tbasicswap\"\x84\x03\n\x0cOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x33\n\tlock_type\x18\x07 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\"I\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\"\x8c\x01\n\nBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x04 \x01(\x0c\x12\x15\n\rproof_address\x18\x05 \x01(\t\x12\x17\n\x0fproof_signature\x18\x06 \x01(\t\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\x62\x06proto3') + create_key=_descriptor._internal_create_key, + serialized_pb=b'\n\x0emessages.proto\x12\tbasicswap\"\xac\x03\n\x0cOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x33\n\tlock_type\x18\x07 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\x8c\x01\n\nBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x04 \x01(\x0c\x12\x15\n\rproof_address\x18\x05 \x01(\t\x12\x17\n\x0fproof_signature\x18\x06 \x01(\t\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\"\xb2\x03\n\x0fXmrOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x36\n\tlock_type\x18\x07 \x01(\x0e\x32#.basicswap.XmrOfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\x62\x06proto3' ) @@ -30,27 +29,81 @@ _OFFERMESSAGE_LOCKTYPE = _descriptor.EnumDescriptor( full_name='basicswap.OfferMessage.LockType', filename=None, file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, values=[ _descriptor.EnumValueDescriptor( name='NOT_SET', index=0, number=0, serialized_options=None, - type=None), + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='SEQUENCE_LOCK_BLOCKS', index=1, number=1, serialized_options=None, - type=None), + type=None, + create_key=_descriptor._internal_create_key), _descriptor.EnumValueDescriptor( name='SEQUENCE_LOCK_TIME', index=2, number=2, serialized_options=None, - type=None), + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ABS_LOCK_BLOCKS', index=3, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ABS_LOCK_TIME', index=4, number=4, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), ], containing_type=None, serialized_options=None, serialized_start=345, - serialized_end=418, + serialized_end=458, ) _sym_db.RegisterEnumDescriptor(_OFFERMESSAGE_LOCKTYPE) +_XMROFFERMESSAGE_LOCKTYPE = _descriptor.EnumDescriptor( + name='LockType', + full_name='basicswap.XmrOfferMessage.LockType', + filename=None, + file=DESCRIPTOR, + create_key=_descriptor._internal_create_key, + values=[ + _descriptor.EnumValueDescriptor( + name='NOT_SET', index=0, number=0, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='SEQUENCE_LOCK_BLOCKS', index=1, number=1, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='SEQUENCE_LOCK_TIME', index=2, number=2, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ABS_LOCK_BLOCKS', index=3, number=3, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + _descriptor.EnumValueDescriptor( + name='ABS_LOCK_TIME', index=4, number=4, + serialized_options=None, + type=None, + create_key=_descriptor._internal_create_key), + ], + containing_type=None, + serialized_options=None, + serialized_start=345, + serialized_end=458, +) +_sym_db.RegisterEnumDescriptor(_XMROFFERMESSAGE_LOCKTYPE) + _OFFERMESSAGE = _descriptor.Descriptor( name='OfferMessage', @@ -58,6 +111,7 @@ _OFFERMESSAGE = _descriptor.Descriptor( filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='coin_from', full_name='basicswap.OfferMessage.coin_from', index=0, @@ -65,91 +119,91 @@ _OFFERMESSAGE = _descriptor.Descriptor( has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='coin_to', full_name='basicswap.OfferMessage.coin_to', index=1, number=2, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='amount_from', full_name='basicswap.OfferMessage.amount_from', index=2, number=3, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='rate', full_name='basicswap.OfferMessage.rate', index=3, number=4, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='min_bid_amount', full_name='basicswap.OfferMessage.min_bid_amount', index=4, number=5, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='time_valid', full_name='basicswap.OfferMessage.time_valid', index=5, number=6, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='lock_type', full_name='basicswap.OfferMessage.lock_type', index=6, number=7, type=14, cpp_type=8, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='lock_value', full_name='basicswap.OfferMessage.lock_value', index=7, number=8, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='swap_type', full_name='basicswap.OfferMessage.swap_type', index=8, number=9, type=13, cpp_type=3, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='proof_address', full_name='basicswap.OfferMessage.proof_address', index=9, number=10, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='proof_signature', full_name='basicswap.OfferMessage.proof_signature', index=10, number=11, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='pkhash_seller', full_name='basicswap.OfferMessage.pkhash_seller', index=11, number=12, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='secret_hash', full_name='basicswap.OfferMessage.secret_hash', index=12, number=13, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], @@ -164,7 +218,7 @@ _OFFERMESSAGE = _descriptor.Descriptor( oneofs=[ ], serialized_start=30, - serialized_end=418, + serialized_end=458, ) @@ -174,49 +228,50 @@ _BIDMESSAGE = _descriptor.Descriptor( filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='offer_msg_id', full_name='basicswap.BidMessage.offer_msg_id', index=0, number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='time_valid', full_name='basicswap.BidMessage.time_valid', index=1, number=2, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='amount', full_name='basicswap.BidMessage.amount', index=2, number=3, type=4, cpp_type=4, label=1, has_default_value=False, default_value=0, message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='pkhash_buyer', full_name='basicswap.BidMessage.pkhash_buyer', index=3, number=4, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='proof_address', full_name='basicswap.BidMessage.proof_address', index=4, number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='proof_signature', full_name='basicswap.BidMessage.proof_signature', index=5, number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), + has_default_value=False, default_value=b"".decode('utf-8'), message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], @@ -229,8 +284,8 @@ _BIDMESSAGE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=421, - serialized_end=561, + serialized_start=461, + serialized_end=601, ) @@ -240,28 +295,29 @@ _BIDACCEPTMESSAGE = _descriptor.Descriptor( filename=None, file=DESCRIPTOR, containing_type=None, + create_key=_descriptor._internal_create_key, fields=[ _descriptor.FieldDescriptor( name='bid_msg_id', full_name='basicswap.BidAcceptMessage.bid_msg_id', index=0, number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='initiate_txid', full_name='basicswap.BidAcceptMessage.initiate_txid', index=1, number=2, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), _descriptor.FieldDescriptor( name='contract_script', full_name='basicswap.BidAcceptMessage.contract_script', index=2, number=3, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), + has_default_value=False, default_value=b"", message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), ], extensions=[ ], @@ -274,37 +330,164 @@ _BIDACCEPTMESSAGE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=563, - serialized_end=649, + serialized_start=603, + serialized_end=689, +) + + +_XMROFFERMESSAGE = _descriptor.Descriptor( + name='XmrOfferMessage', + full_name='basicswap.XmrOfferMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='coin_from', full_name='basicswap.XmrOfferMessage.coin_from', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='coin_to', full_name='basicswap.XmrOfferMessage.coin_to', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='amount_from', full_name='basicswap.XmrOfferMessage.amount_from', index=2, + number=3, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='rate', full_name='basicswap.XmrOfferMessage.rate', index=3, + number=4, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='min_bid_amount', full_name='basicswap.XmrOfferMessage.min_bid_amount', index=4, + number=5, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='time_valid', full_name='basicswap.XmrOfferMessage.time_valid', index=5, + number=6, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='lock_type', full_name='basicswap.XmrOfferMessage.lock_type', index=6, + number=7, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='lock_value', full_name='basicswap.XmrOfferMessage.lock_value', index=7, + number=8, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='swap_type', full_name='basicswap.XmrOfferMessage.swap_type', index=8, + number=9, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='proof_address', full_name='basicswap.XmrOfferMessage.proof_address', index=9, + number=10, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='proof_signature', full_name='basicswap.XmrOfferMessage.proof_signature', index=10, + number=11, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=b"".decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='pkhash_seller', full_name='basicswap.XmrOfferMessage.pkhash_seller', index=11, + number=12, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='secret_hash', full_name='basicswap.XmrOfferMessage.secret_hash', index=12, + number=13, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _XMROFFERMESSAGE_LOCKTYPE, + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=692, + serialized_end=1126, ) _OFFERMESSAGE.fields_by_name['lock_type'].enum_type = _OFFERMESSAGE_LOCKTYPE _OFFERMESSAGE_LOCKTYPE.containing_type = _OFFERMESSAGE +_XMROFFERMESSAGE.fields_by_name['lock_type'].enum_type = _XMROFFERMESSAGE_LOCKTYPE +_XMROFFERMESSAGE_LOCKTYPE.containing_type = _XMROFFERMESSAGE DESCRIPTOR.message_types_by_name['OfferMessage'] = _OFFERMESSAGE DESCRIPTOR.message_types_by_name['BidMessage'] = _BIDMESSAGE DESCRIPTOR.message_types_by_name['BidAcceptMessage'] = _BIDACCEPTMESSAGE +DESCRIPTOR.message_types_by_name['XmrOfferMessage'] = _XMROFFERMESSAGE _sym_db.RegisterFileDescriptor(DESCRIPTOR) -OfferMessage = _reflection.GeneratedProtocolMessageType('OfferMessage', (_message.Message,), dict( - DESCRIPTOR = _OFFERMESSAGE, - __module__ = 'messages_pb2' +OfferMessage = _reflection.GeneratedProtocolMessageType('OfferMessage', (_message.Message,), { + 'DESCRIPTOR' : _OFFERMESSAGE, + '__module__' : 'messages_pb2' # @@protoc_insertion_point(class_scope:basicswap.OfferMessage) - )) + }) _sym_db.RegisterMessage(OfferMessage) -BidMessage = _reflection.GeneratedProtocolMessageType('BidMessage', (_message.Message,), dict( - DESCRIPTOR = _BIDMESSAGE, - __module__ = 'messages_pb2' +BidMessage = _reflection.GeneratedProtocolMessageType('BidMessage', (_message.Message,), { + 'DESCRIPTOR' : _BIDMESSAGE, + '__module__' : 'messages_pb2' # @@protoc_insertion_point(class_scope:basicswap.BidMessage) - )) + }) _sym_db.RegisterMessage(BidMessage) -BidAcceptMessage = _reflection.GeneratedProtocolMessageType('BidAcceptMessage', (_message.Message,), dict( - DESCRIPTOR = _BIDACCEPTMESSAGE, - __module__ = 'messages_pb2' +BidAcceptMessage = _reflection.GeneratedProtocolMessageType('BidAcceptMessage', (_message.Message,), { + 'DESCRIPTOR' : _BIDACCEPTMESSAGE, + '__module__' : 'messages_pb2' # @@protoc_insertion_point(class_scope:basicswap.BidAcceptMessage) - )) + }) _sym_db.RegisterMessage(BidAcceptMessage) +XmrOfferMessage = _reflection.GeneratedProtocolMessageType('XmrOfferMessage', (_message.Message,), { + 'DESCRIPTOR' : _XMROFFERMESSAGE, + '__module__' : 'messages_pb2' + # @@protoc_insertion_point(class_scope:basicswap.XmrOfferMessage) + }) +_sym_db.RegisterMessage(XmrOfferMessage) + # @@protoc_insertion_point(module_scope) diff --git a/basicswap/rpc.py b/basicswap/rpc.py index bcd7b59..68000a1 100644 --- a/basicswap/rpc.py +++ b/basicswap/rpc.py @@ -18,14 +18,15 @@ from xmlrpc.client import ( from .util import jsonDecimal -def waitForRPC(rpc_func, wallet=None): - for i in range(5): +def waitForRPC(rpc_func, wallet=None, max_tries=7): + for i in range(max_tries + 1): try: rpc_func('getwalletinfo') return except Exception as ex: - logging.warning('Can\'t connect to daemon RPC: %s. Trying again in %d second/s.', str(ex), (1 + i)) - time.sleep(1 + i) + if i < max_tries: + logging.warning('Can\'t connect to RPC: %s. Retrying in %d second/s.', str(ex), (i + 1)) + time.sleep(i + 1) raise ValueError('waitForRPC failed') diff --git a/basicswap/util.py b/basicswap/util.py index 024a864..8429e58 100644 --- a/basicswap/util.py +++ b/basicswap/util.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2018-2019 tecnovert +# Copyright (c) 2018-2020 tecnovert # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. -import decimal import json import hashlib from .contrib.segwit_addr import bech32_decode, convertbits, bech32_encode @@ -12,7 +11,6 @@ from .contrib.segwit_addr import bech32_decode, convertbits, bech32_encode OP_1 = 0x51 OP_16 = 0x60 COIN = 100000000 -DCOIN = decimal.Decimal(COIN) def assert_cond(v, err='Bad opcode'): @@ -34,10 +32,6 @@ def toBool(s): return s.lower() in ["1", "true"] -def dquantize(n, places=8): - return n.quantize(decimal.Decimal(10) ** -places) - - def jsonDecimal(obj): if isinstance(obj, decimal.Decimal): return str(obj) @@ -229,13 +223,13 @@ def getCompactSizeLen(v): raise ValueError('Value too large') -def make_int(v, precision=8, r=0): # r = 0, no rounding, fail, r > 0 round up, r < 0 floor +def make_int(v, scale=8, r=0): # r = 0, no rounding, fail, r > 0 round up, r < 0 floor if type(v) == float: v = str(v) elif type(v) == int: - return v * 10 ** precision + return v * 10 ** scale - ep = 10 ** precision + ep = 10 ** scale have_dp = False rv = 0 for c in v: @@ -264,7 +258,7 @@ def make_int(v, precision=8, r=0): # r = 0, no rounding, fail, r > 0 round up, return rv -def validate_amount(amount, precision=8): +def validate_amount(amount, scale=8): str_amount = str(amount) has_decimal = False for c in str_amount: @@ -275,21 +269,21 @@ def validate_amount(amount, precision=8): raise ValueError('Invalid amount') ar = str_amount.split('.') - if len(ar) > 1 and len(ar[1]) > precision: + if len(ar) > 1 and len(ar[1]) > scale: raise ValueError('Too many decimal places in amount {}'.format(str_amount)) return True -def format_amount(i, display_precision, precision=None): - if precision is None: - precision = display_precision - ep = 10 ** precision +def format_amount(i, display_scale, scale=None): + if scale is None: + scale = display_scale + ep = 10 ** scale n = abs(i) quotient = n // ep remainder = n % ep - if display_precision != precision: - remainder %= (10 ** display_precision) - rv = '{}.{:0>{prec}}'.format(quotient, remainder, prec=display_precision) + if display_scale != scale: + remainder %= (10 ** display_scale) + rv = '{}.{:0>{prec}}'.format(quotient, remainder, prec=display_scale) if i < 0: rv = '-' + rv return rv diff --git a/bin/basicswap_prepare.py b/bin/basicswap_prepare.py index 185cec9..a277a55 100755 --- a/bin/basicswap_prepare.py +++ b/bin/basicswap_prepare.py @@ -46,6 +46,7 @@ known_coins = { 'litecoin': '0.18.1', 'bitcoin': '0.20.1', 'namecoin': '0.18.0', + 'monero': '0.17.0.1', } logger = logging.getLogger() @@ -124,6 +125,15 @@ def prepareCore(coin, version, settings, data_dir): os_dir_name = 'linux' os_name = 'linux' + if coin == 'monero': + url = 'https://downloads.getmonero.org/cli/monero-linux-x64-v${}.tar.bz2'.format(version) + + release_path = os.path.join(bin_dir, release_filename) + if not os.path.exists(release_path): + downloadFile(release_url, release_path) + + raise ValueError('TODO') + release_filename = '{}-{}-{}'.format(coin, version, BIN_ARCH) if coin == 'particl': signing_key_name = 'tecnovert' diff --git a/bin/basicswap_run.py b/bin/basicswap_run.py index 420bec4..59fb368 100644 --- a/bin/basicswap_run.py +++ b/bin/basicswap_run.py @@ -45,7 +45,7 @@ def startDaemon(node_dir, bin_dir, daemon_bin, opts=[]): daemon_bin = os.path.expanduser(os.path.join(bin_dir, daemon_bin)) args = [daemon_bin, '-datadir=' + os.path.expanduser(node_dir)] + opts - logger.info('Starting node ' + daemon_bin + ' ' + '-datadir=' + node_dir) + logging.info('Starting node ' + daemon_bin + ' ' + '-datadir=' + node_dir) return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -118,9 +118,13 @@ def runClient(fp, data_dir, chain): closed_pids = [] for d in daemons: int_pid = d.pid - logger.info('Terminating {}'.format(int_pid)) + logging.info('Interrupting {}'.format(int_pid)) + try: + d.send_signal(signal.SIGINT) + except Exception as e: + logging.info('Interrupting %d, error %s', d.pid, str(e)) + for d in daemons: try: - d.terminate() d.wait(timeout=120) if d.stdout: d.stdout.close() diff --git a/setup.py b/setup.py index deed653..ac9726a 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ setuptools.setup( "Operating System :: Linux", ], install_requires=[ + "wheel", "pyzmq", "protobuf", "sqlalchemy", diff --git a/tests/basicswap/common.py b/tests/basicswap/common.py index 3139587..eb40515 100644 --- a/tests/basicswap/common.py +++ b/tests/basicswap/common.py @@ -5,6 +5,13 @@ # Distributed under the MIT software license, see the accompanying # file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php. +import os + + +TEST_HTTP_HOST = os.getenv('TEST_HTTP_HOST', '127.0.0.1') # Set to 0.0.0.0 when used in docker +TEST_HTTP_PORT = 1800 + + def checkForks(ro): if 'bip9_softforks' in ro: assert(ro['bip9_softforks']['csv']['status'] == 'active') diff --git a/tests/basicswap/test_nmc.py b/tests/basicswap/test_nmc.py index 3fa4e59..0a11d3d 100644 --- a/tests/basicswap/test_nmc.py +++ b/tests/basicswap/test_nmc.py @@ -44,6 +44,10 @@ from basicswap.contrib.key import ( from basicswap.http_server import ( HttpThread, ) +from tests.basicswap.common import ( + TEST_HTTP_HOST, + TEST_HTTP_PORT, +) from bin.basicswap_run import startDaemon import basicswap.config as cfg @@ -58,7 +62,6 @@ BASE_PORT = 14792 BASE_RPC_PORT = 19792 BASE_ZMQ_PORT = 20792 PREFIX_SECRET_KEY_REGTEST = 0x2e -TEST_HTML_PORT = 1800 NMC_NODE = 3 BTC_NODE = 4 stop_test = False @@ -278,9 +281,8 @@ class Test(unittest.TestCase): print('nmcRpc', ro) cls.http_threads = [] - host = '0.0.0.0' # All interfaces (docker) for i in range(3): - t = HttpThread(cls.swap_clients[i].fp, host, TEST_HTML_PORT + i, False, cls.swap_clients[i]) + t = HttpThread(cls.swap_clients[i].fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, cls.swap_clients[i]) cls.http_threads.append(t) t.start() diff --git a/tests/basicswap/test_run.py b/tests/basicswap/test_run.py index 43a0426..fa9a7d4 100644 --- a/tests/basicswap/test_run.py +++ b/tests/basicswap/test_run.py @@ -50,6 +50,8 @@ from basicswap.http_server import ( ) from tests.basicswap.common import ( checkForks, + TEST_HTTP_HOST, + TEST_HTTP_PORT, ) from bin.basicswap_run import startDaemon @@ -63,7 +65,6 @@ BASE_PORT = 14792 BASE_RPC_PORT = 19792 BASE_ZMQ_PORT = 20792 PREFIX_SECRET_KEY_REGTEST = 0x2e -TEST_HTML_PORT = 1800 LTC_NODE = 3 BTC_NODE = 4 stop_test = False @@ -247,11 +248,12 @@ class Test(unittest.TestCase): with open(settings_path) as fs: settings = json.load(fs) fp = open(os.path.join(basicswap_dir, 'basicswap.log'), 'w') - cls.swap_clients.append(BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i))) - cls.swap_clients[-1].setDaemonPID(Coins.BTC, cls.daemons[0].pid) - cls.swap_clients[-1].setDaemonPID(Coins.LTC, cls.daemons[1].pid) - cls.swap_clients[-1].setDaemonPID(Coins.PART, cls.daemons[2 + i].pid) - cls.swap_clients[-1].start() + sc = BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i)) + sc.setDaemonPID(Coins.BTC, cls.daemons[0].pid) + sc.setDaemonPID(Coins.LTC, cls.daemons[1].pid) + sc.setDaemonPID(Coins.PART, cls.daemons[2 + i].pid) + sc.start() + cls.swap_clients.append(sc) cls.swap_clients[0].callrpc('extkeyimportmaster', ['abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb']) cls.swap_clients[1].callrpc('extkeyimportmaster', ['pact mammal barrel matrix local final lecture chunk wasp survey bid various book strong spread fall ozone daring like topple door fatigue limb olympic', '', 'true']) cls.swap_clients[1].callrpc('getnewextaddress', ['lblExtTest']) @@ -278,9 +280,8 @@ class Test(unittest.TestCase): print('ltcRpc', ro) cls.http_threads = [] - host = '0.0.0.0' # All interfaces (docker) for i in range(3): - t = HttpThread(cls.swap_clients[i].fp, host, TEST_HTML_PORT + i, False, cls.swap_clients[i]) + t = HttpThread(cls.swap_clients[i].fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, cls.swap_clients[i]) cls.http_threads.append(t) t.start() @@ -300,8 +301,12 @@ class Test(unittest.TestCase): for c in cls.swap_clients: c.fp.close() for d in cls.daemons: - logging.info('Terminating %d', d.pid) - d.terminate() + logging.info('Interrupting %d', d.pid) + try: + d.send_signal(signal.SIGINT) + except Exception as e: + logging.info('Interrupting %d, error %s', d.pid, str(e)) + for d in cls.daemons: d.wait(timeout=10) if d.stdout: d.stdout.close() diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index 06933cc..feec1ba 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -7,13 +7,15 @@ import os import sys -import unittest import json -import logging -import shutil import time +import shutil import signal +import logging +import unittest +import traceback import threading +import subprocess from urllib.request import urlopen from coincurve.ecdsaotves import ( ecdsaotves_enc_sign, @@ -37,45 +39,100 @@ from basicswap.util import ( COIN, toWIF, dumpje, + make_int, ) from basicswap.rpc import ( + callrpc, callrpc_cli, waitForRPC, ) +from basicswap.rpc_xmr import ( + callrpc_xmr_na, + callrpc_xmr, +) +from basicswap.interface_xmr import ( + XMR_COIN, +) from basicswap.contrib.key import ( ECKey, ) from basicswap.http_server import ( HttpThread, ) +from tests.basicswap.common import ( + checkForks, + TEST_HTTP_HOST, + TEST_HTTP_PORT, +) +from basicswap.contrib.rpcauth import generate_salt, password_to_hmac from bin.basicswap_run import startDaemon logger = logging.getLogger() -logger.level = logging.DEBUG -if not len(logger.handlers): - logger.addHandler(logging.StreamHandler(sys.stdout)) NUM_NODES = 3 +NUM_XMR_NODES = 3 +NUM_BTC_NODES = 3 +TEST_DIR = cfg.TEST_DATADIRS + BASE_PORT = 14792 BASE_RPC_PORT = 19792 BASE_ZMQ_PORT = 20792 + +BTC_BASE_PORT = 114792 +BTC_BASE_RPC_PORT = 119792 +BTC_BASE_ZMQ_PORT = 120792 + +XMR_BASE_P2P_PORT = 17792 +XMR_BASE_RPC_PORT = 21792 +XMR_BASE_ZMQ_PORT = 22792 +XMR_BASE_WALLET_RPC_PORT = 23792 + PREFIX_SECRET_KEY_REGTEST = 0x2e -TEST_HTML_PORT = 1800 stop_test = False - -def prepareOtherDir(datadir, nodeId, conf_file='litecoin.conf'): - node_dir = os.path.join(datadir, str(nodeId)) +def prepareXmrDataDir(datadir, node_id, conf_file): + node_dir = os.path.join(datadir, 'xmr_' + str(node_id)) if not os.path.exists(node_dir): os.makedirs(node_dir) - filePath = os.path.join(node_dir, conf_file) + cfg_file_path = os.path.join(node_dir, conf_file) + if os.path.exists(cfg_file_path): + return + with open(cfg_file_path, 'w+') as fp: + fp.write('regtest=1\n') + fp.write('keep-fakechain=1\n') + fp.write('data-dir={}\n'.format(node_dir)) + fp.write('fixed-difficulty=1\n') + # fp.write('offline=1\n') + fp.write('p2p-bind-port={}\n'.format(XMR_BASE_P2P_PORT + node_id)) + fp.write('rpc-bind-port={}\n'.format(XMR_BASE_RPC_PORT + node_id)) + fp.write('p2p-bind-ip=127.0.0.1\n') + fp.write('rpc-bind-ip=127.0.0.1\n') - with open(filePath, 'w+') as fp: + fp.write('zmq-rpc-bind-port={}\n'.format(XMR_BASE_ZMQ_PORT + node_id)) + fp.write('zmq-rpc-bind-ip=127.0.0.1\n') + + for i in range(0, NUM_XMR_NODES): + if node_id == i: + continue + fp.write('add-exclusive-node=127.0.0.1:{}\n'.format(XMR_BASE_P2P_PORT + i)) + + +def prepareDataDir(datadir, node_id, conf_file, dir_prefix, base_p2p_port=BASE_PORT, base_rpc_port=BASE_RPC_PORT): + node_dir = os.path.join(datadir, dir_prefix + str(node_id)) + if not os.path.exists(node_dir): + os.makedirs(node_dir) + cfg_file_path = os.path.join(node_dir, conf_file) + if os.path.exists(cfg_file_path): + return + with open(cfg_file_path, 'w+') as fp: fp.write('regtest=1\n') fp.write('[regtest]\n') - fp.write('port=' + str(BASE_PORT + nodeId) + '\n') - fp.write('rpcport=' + str(BASE_RPC_PORT + nodeId) + '\n') + fp.write('port=' + str(base_p2p_port + node_id) + '\n') + fp.write('rpcport=' + str(base_rpc_port + node_id) + '\n') + + salt = generate_salt(16) + fp.write('rpcauth={}:{}${}\n'.format('test' + str(node_id), salt, password_to_hmac(salt, 'test_pass' + str(node_id)))) fp.write('daemon=0\n') fp.write('printtoconsole=0\n') @@ -83,88 +140,100 @@ def prepareOtherDir(datadir, nodeId, conf_file='litecoin.conf'): fp.write('discover=0\n') fp.write('listenonion=0\n') fp.write('bind=127.0.0.1\n') - fp.write('findpeers=0\n') fp.write('debug=1\n') fp.write('debugexclude=libevent\n') - fp.write('fallbackfee=0.0002\n') + fp.write('fallbackfee=0.01\n') fp.write('acceptnonstdtxn=0\n') + fp.write('txindex=1\n') - -def prepareDir(datadir, nodeId, network_key, network_pubkey): - node_dir = os.path.join(datadir, str(nodeId)) - if not os.path.exists(node_dir): - os.makedirs(node_dir) - filePath = os.path.join(node_dir, 'particl.conf') - - with open(filePath, 'w+') as fp: - fp.write('regtest=1\n') - fp.write('[regtest]\n') - fp.write('port=' + str(BASE_PORT + nodeId) + '\n') - fp.write('rpcport=' + str(BASE_RPC_PORT + nodeId) + '\n') - - fp.write('daemon=0\n') - fp.write('printtoconsole=0\n') - fp.write('server=1\n') - fp.write('discover=0\n') - fp.write('listenonion=0\n') - fp.write('bind=127.0.0.1\n') fp.write('findpeers=0\n') - fp.write('debug=1\n') - fp.write('debugexclude=libevent\n') - fp.write('zmqpubsmsg=tcp://127.0.0.1:' + str(BASE_ZMQ_PORT + nodeId) + '\n') + # minstakeinterval=5 # Using walletsettings stakelimit instead - fp.write('acceptnonstdtxn=0\n') - fp.write('minstakeinterval=5\n') + if base_p2p_port == BASE_PORT: # Particl + fp.write('zmqpubsmsg=tcp://127.0.0.1:{}\n'.format(BASE_ZMQ_PORT + node_id)) for i in range(0, NUM_NODES): - if nodeId == i: + if node_id == i: continue - fp.write('addnode=127.0.0.1:%d\n' % (BASE_PORT + i)) + fp.write('addnode=127.0.0.1:{}\n'.format(base_p2p_port + i)) - if nodeId < 2: - fp.write('spentindex=1\n') - fp.write('txindex=1\n') - basicswap_dir = os.path.join(datadir, str(nodeId), 'basicswap') +def startXmrDaemon(node_dir, bin_dir, daemon_bin, opts=[]): + daemon_bin = os.path.expanduser(os.path.join(bin_dir, daemon_bin)) + + args = [daemon_bin, '--config-file=' + os.path.join(os.path.expanduser(node_dir), 'monerod.conf')] + opts + logging.info('Starting node {} --data-dir={}'.format(daemon_bin, node_dir)) + + return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + +def startXmrWalletRPC(node_dir, bin_dir, wallet_bin, node_id, opts=[]): + daemon_bin = os.path.expanduser(os.path.join(bin_dir, wallet_bin)) + + data_dir = os.path.expanduser(node_dir) + args = [daemon_bin] + args += ['--daemon-address=localhost:{}'.format(XMR_BASE_RPC_PORT + node_id)] + args += ['--no-dns'] + args += ['--rpc-bind-port={}'.format(XMR_BASE_WALLET_RPC_PORT + node_id)] + args += ['--wallet-dir={}'.format(os.path.join(data_dir, 'wallets'))] + args += ['--log-file={}'.format(os.path.join(data_dir, 'wallet.log'))] + args += ['--rpc-login=test{0}:test_pass{0}'.format(node_id)] + args += ['--shared-ringdb-dir={}'.format(os.path.join(data_dir, 'shared-ringdb'))] + + args += opts + logging.info('Starting daemon {} --wallet-dir={}'.format(daemon_bin, node_dir)) + + wallet_stdout = open(os.path.join(data_dir, 'wallet_stdout.log'), 'w') + wallet_stderr = open(os.path.join(data_dir, 'wallet_stderr.log'), 'w') + return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir) + + +def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey): + basicswap_dir = os.path.join(datadir, 'basicswap_' + str(node_id)) if not os.path.exists(basicswap_dir): os.makedirs(basicswap_dir) - ltcdatadir = os.path.join(datadir, str(LTC_NODE)) - btcdatadir = os.path.join(datadir, str(BTC_NODE)) settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME) settings = { 'zmqhost': 'tcp://127.0.0.1', - 'zmqport': BASE_ZMQ_PORT + nodeId, + 'zmqport': BASE_ZMQ_PORT + node_id, 'htmlhost': 'localhost', - 'htmlport': 12700 + nodeId, + 'htmlport': 12700 + node_id, 'network_key': network_key, 'network_pubkey': network_pubkey, 'chainclients': { 'particl': { 'connection_type': 'rpc', 'manage_daemon': False, - 'rpcport': BASE_RPC_PORT + nodeId, - 'datadir': node_dir, + 'rpcport': BASE_RPC_PORT + node_id, + 'rpcuser': 'test' + str(node_id), + 'rpcpassword': 'test_pass' + str(node_id), + 'datadir': os.path.join(datadir, 'part_' + str(node_id)), 'bindir': cfg.PARTICL_BINDIR, 'blocks_confirmed': 2, # Faster testing }, - 'litecoin': { + 'monero': { 'connection_type': 'rpc', 'manage_daemon': False, - 'rpcport': BASE_RPC_PORT + LTC_NODE, - 'datadir': ltcdatadir, - 'bindir': cfg.LITECOIN_BINDIR, - # 'use_segwit': True, + 'rpcport': XMR_BASE_RPC_PORT + node_id, + 'walletrpcport': XMR_BASE_WALLET_RPC_PORT + node_id, + 'walletrpcuser': 'test' + str(node_id), + 'walletrpcpassword': 'test_pass' + str(node_id), + 'datadir': os.path.join(datadir, 'xmr_' + str(node_id)), + 'bindir': cfg.XMR_BINDIR, }, 'bitcoin': { 'connection_type': 'rpc', 'manage_daemon': False, - 'rpcport': BASE_RPC_PORT + BTC_NODE, - 'datadir': btcdatadir, + 'rpcport': BTC_BASE_RPC_PORT + node_id, + 'rpcuser': 'test' + str(node_id), + 'rpcpassword': 'test_pass' + str(node_id), + 'datadir': os.path.join(datadir, 'btc_' + str(node_id)), 'bindir': cfg.BITCOIN_BINDIR, 'use_segwit': True, } + }, 'check_progress_seconds': 2, 'check_watched_seconds': 4, @@ -173,16 +242,27 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey): 'min_delay_auto_accept': 1, 'max_delay_auto_accept': 5 } + with open(settings_path, 'w') as fp: json.dump(settings, fp, indent=4) def partRpc(cmd, node_id=0): - return callrpc_cli(cfg.PARTICL_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(node_id)), 'regtest', cmd, cfg.PARTICL_CLI) + return callrpc_cli(cfg.PARTICL_BINDIR, os.path.join(TEST_DIR, 'part_' + str(node_id)), 'regtest', cmd, cfg.PARTICL_CLI) -def btcRpc(cmd): - return callrpc_cli(cfg.BITCOIN_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(BTC_NODE)), 'regtest', cmd, cfg.BITCOIN_CLI) +def btcRpc(cmd, node_id=0): + return callrpc_cli(cfg.BITCOIN_BINDIR, os.path.join(TEST_DIR, 'btc_' + str(node_id)), 'regtest', cmd, cfg.BITCOIN_CLI) + + +def make_rpc_func(node_id, base_rpc_port=BASE_RPC_PORT): + node_id = node_id + auth = 'test{0}:test_pass{0}'.format(node_id) + + def rpc_func(method, params=None, wallet=None): + nonlocal node_id, auth + return callrpc(base_rpc_port + node_id, auth, method, params, wallet) + return rpc_func def signal_handler(sig, frame): @@ -191,21 +271,47 @@ def signal_handler(sig, frame): stop_test = True -def run_loop(self): +def waitForXMRNode(rpc_offset, max_tries=7): + for i in range(max_tries + 1): + try: + callrpc_xmr_na(XMR_BASE_RPC_PORT + rpc_offset, 'get_block_count') + return + except Exception as ex: + if i < max_tries: + logging.warning('Can\'t connect to XMR RPC: %s. Retrying in %d second/s.', str(ex), (i + 1)) + time.sleep(i + 1) + raise ValueError('waitForXMRNode failed') + + +def waitForXMRWallet(rpc_offset, auth, max_tries=7): + for i in range(max_tries + 1): + try: + callrpc_xmr(XMR_BASE_WALLET_RPC_PORT + rpc_offset, auth, 'get_languages') + return + except Exception as ex: + if i < max_tries: + logging.warning('Can\'t connect to XMR wallet RPC: %s. Retrying in %d second/s.', str(ex), (i + 1)) + time.sleep(i + 1) + raise ValueError('waitForXMRWallet failed') + + +def callnoderpc(node_id, method, params=[], wallet=None, base_rpc_port=BASE_RPC_PORT): + auth = 'test{0}:test_pass{0}'.format(node_id) + return callrpc(base_rpc_port + node_id, auth, method, params, wallet) + + +def run_loop(cls): + global stop_test while not stop_test: - time.sleep(1) - for c in self.swap_clients: + for c in cls.swap_clients: c.update() - btcRpc('generatetoaddress 1 {}'.format(self.btc_addr)) + if cls.btc_addr is not None: + btcRpc('generatetoaddress 1 {}'.format(cls.btc_addr)) -def checkForks(ro): - if 'bip9_softforks' in ro: - assert(ro['bip9_softforks']['csv']['status'] == 'active') - assert(ro['bip9_softforks']['segwit']['status'] == 'active') - else: - assert(ro['softforks']['csv']['active']) - assert(ro['softforks']['segwit']['active']) + if cls.xmr_addr is not None: + callrpc_xmr_na(XMR_BASE_RPC_PORT + 0, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1}) + time.sleep(1.0) class Test(unittest.TestCase): @@ -214,32 +320,210 @@ class Test(unittest.TestCase): def setUpClass(cls): super(Test, cls).setUpClass() + cls.update_thread = None + cls.http_threads = [] cls.swap_clients = [] + cls.part_daemons = [] + cls.btc_daemons = [] cls.xmr_daemons = [] cls.xmr_wallet_auth = [] cls.part_stakelimit = 0 cls.xmr_addr = None + cls.btc_addr = None - signal.signal(signal.SIGINT, signal_handler) - cls.update_thread = threading.Thread(target=run_loop, args=(cls,)) - cls.update_thread.start() + logger.propagate = False + logger.handlers = [] + logger.setLevel(logging.INFO) # DEBUG shows many messages from requests.post + formatter = logging.Formatter('%(asctime)s %(levelname)s : %(message)s') + stream_stdout = logging.StreamHandler() + stream_stdout.setFormatter(formatter) + logger.addHandler(stream_stdout) + + if os.path.isdir(TEST_DIR): + logging.info('Removing ' + TEST_DIR) + shutil.rmtree(TEST_DIR) + if not os.path.exists(TEST_DIR): + os.makedirs(TEST_DIR) + + cls.stream_fp = logging.FileHandler(os.path.join(TEST_DIR, 'test.log')) + cls.stream_fp.setFormatter(formatter) + logger.addHandler(cls.stream_fp) + + try: + logging.info('Preparing coin nodes.') + for i in range(NUM_NODES): + prepareDataDir(TEST_DIR, i, 'particl.conf', 'part_') + + cls.part_daemons.append(startDaemon(os.path.join(TEST_DIR, 'part_' + str(i)), cfg.PARTICL_BINDIR, cfg.PARTICLD)) + logging.info('Started %s %d', cfg.PARTICLD, cls.part_daemons[-1].pid) + + waitForRPC(make_rpc_func(i)) + + for i in range(NUM_BTC_NODES): + prepareDataDir(TEST_DIR, i, 'bitcoin.conf', 'btc_', base_p2p_port=BTC_BASE_PORT, base_rpc_port=BTC_BASE_RPC_PORT) + + cls.btc_daemons.append(startDaemon(os.path.join(TEST_DIR, 'btc_' + str(i)), cfg.BITCOIN_BINDIR, cfg.BITCOIND)) + logging.info('Started %s %d', cfg.BITCOIND, cls.part_daemons[-1].pid) + + waitForRPC(make_rpc_func(i, base_rpc_port=BTC_BASE_RPC_PORT)) + + for i in range(NUM_XMR_NODES): + prepareXmrDataDir(TEST_DIR, i, 'monerod.conf') + + cls.xmr_daemons.append(startXmrDaemon(os.path.join(TEST_DIR, 'xmr_' + str(i)), cfg.XMR_BINDIR, cfg.XMRD)) + logging.info('Started %s %d', cfg.XMRD, cls.xmr_daemons[-1].pid) + waitForXMRNode(i) + + cls.xmr_daemons.append(startXmrWalletRPC(os.path.join(TEST_DIR, 'xmr_' + str(i)), cfg.XMR_BINDIR, cfg.XMR_WALLET_RPC, i)) + + for i in range(NUM_XMR_NODES): + cls.xmr_wallet_auth.append(('test{0}'.format(i), 'test_pass{0}'.format(i))) + logging.info('Creating XMR wallet %i', i) + + waitForXMRWallet(i, cls.xmr_wallet_auth[i]) + + cls.callxmrnodewallet(cls, i, 'create_wallet', {'filename': 'testwallet', 'language': 'English'}) + cls.callxmrnodewallet(cls, i, 'open_wallet', {'filename': 'testwallet'}) + + logging.info('Preparing swap clients.') + eckey = ECKey() + eckey.generate() + cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, eckey.get_bytes()) + cls.network_pubkey = eckey.get_pubkey().get_bytes().hex() + + for i in range(NUM_NODES): + prepare_swapclient_dir(TEST_DIR, i, cls.network_key, cls.network_pubkey) + basicswap_dir = os.path.join(os.path.join(TEST_DIR, 'basicswap_' + str(i))) + settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME) + with open(settings_path) as fs: + settings = json.load(fs) + fp = open(os.path.join(basicswap_dir, 'basicswap.log'), 'w') + sc = BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i)) + sc.setDaemonPID(Coins.BTC, cls.btc_daemons[i].pid) + sc.setDaemonPID(Coins.PART, cls.part_daemons[i].pid) + sc.start() + cls.swap_clients.append(sc) + + logging.info('Initialising coin networks.') + cls.swap_clients[0].callrpc('extkeyimportmaster', ['abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb']) + cls.swap_clients[1].callrpc('extkeyimportmaster', ['pact mammal barrel matrix local final lecture chunk wasp survey bid various book strong spread fall ozone daring like topple door fatigue limb olympic', '', 'true']) + cls.swap_clients[1].callrpc('getnewextaddress', ['lblExtTest']) + cls.swap_clients[1].callrpc('rescanblockchain') + + for i in range(3): + t = HttpThread(cls.swap_clients[i].fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, cls.swap_clients[i]) + cls.http_threads.append(t) + t.start() + + cls.btc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT) + cls.xmr_addr = cls.callxmrnodewallet(cls, 0, 'get_address')['address'] + + num_blocks = 500 + logging.info('Mining %d bitcoin blocks to %s', num_blocks, cls.btc_addr) + callnoderpc(0, 'generatetoaddress', [num_blocks, cls.btc_addr], base_rpc_port=BTC_BASE_RPC_PORT) + + checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=BTC_BASE_RPC_PORT)) + + if callrpc_xmr_na(XMR_BASE_RPC_PORT + 0, 'get_block_count')['count'] < num_blocks: + logging.info('Mining %d Monero blocks.', num_blocks) + callrpc_xmr_na(XMR_BASE_RPC_PORT + 0, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': num_blocks}) + rv = callrpc_xmr_na(XMR_BASE_RPC_PORT + 0, 'get_block_count') + logging.info('XMR blocks: %d', rv['count']) + + logging.info('Starting update thread.') + signal.signal(signal.SIGINT, signal_handler) + cls.update_thread = threading.Thread(target=run_loop, args=(cls,)) + cls.update_thread.start() + except Exception: + traceback.print_exc() + Test.tearDownClass() + raise ValueError('setUpClass() failed.') @classmethod def tearDownClass(cls): global stop_test logging.info('Finalising') stop_test = True - cls.update_thread.join() + if cls.update_thread is not None: + try: + cls.update_thread.join() + except Exception: + logging.info('Failed to join update_thread') + cls.update_thread = None + + for t in cls.http_threads: + t.stop() + t.join() + for c in cls.swap_clients: + c.fp.close() + + for d in cls.xmr_daemons: + logging.info('Interrupting %d', d.pid) + try: + d.send_signal(signal.SIGINT) + except Exception as e: + logging.info('Interrupting %d, error %s', d.pid, str(e)) + for d in cls.xmr_daemons: + try: + d.wait(timeout=20) + if d.stdout: + d.stdout.close() + if d.stderr: + d.stderr.close() + if d.stdin: + d.stdin.close() + except Exception as e: + logging.info('Closing %d, error %s', d.pid, str(e)) + + for d in cls.part_daemons + cls.btc_daemons: + logging.info('Interrupting %d', d.pid) + try: + d.send_signal(signal.SIGINT) + except Exception as e: + logging.info('Interrupting %d, error %s', d.pid, str(e)) + for d in cls.part_daemons + cls.btc_daemons: + try: + d.wait(timeout=20) + if d.stdout: + d.stdout.close() + if d.stderr: + d.stderr.close() + if d.stdin: + d.stdin.close() + except Exception as e: + logging.info('Closing %d, error %s', d.pid, str(e)) super(Test, cls).tearDownClass() + def callxmrnodewallet(self, node_id, method, params=None): + return callrpc_xmr(XMR_BASE_WALLET_RPC_PORT + node_id, self.xmr_wallet_auth[node_id], method, params) + + def wait_for_offer(self, swap_client, offer_id): + logging.info('wait_for_offer %s', offer_id.hex()) + for i in range(20): + time.sleep(1) + offers = swap_client.listOffers() + for offer in offers: + if offer.offer_id == offer_id: + return + raise ValueError('wait_for_offer timed out.') + def test_01_part_xmr(self): logging.info('---------- Test PART to XMR') - #swap_clients = self.swap_clients + swap_clients = self.swap_clients - #offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 100 * COIN, 0.5 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST) + js_0 = json.loads(urlopen('http://localhost:1800/json/wallets').read()) + assert(make_int(js_0[str(int(Coins.XMR))]['balance'], scale=12) > 0) + assert(make_int(js_0[str(int(Coins.XMR))]['unconfirmed'], scale=12) > 0) + offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 100 * COIN, 10 * XMR_COIN, 100 * COIN, SwapTypes.XMR_SWAP) + + self.wait_for_offer(swap_clients[1], offer_id) + offers = swap_clients[1].listOffers() + assert(len(offers) == 1) + for offer in offers: + print('offer', offer) if __name__ == '__main__':