diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 871e658..357c670 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -1521,12 +1521,13 @@ class BasicSwap(BaseApp): ensure(sign_for_addr is not None, 'Could not find address with enough funds for proof') self.log.debug('sign_for_addr %s', sign_for_addr) - if self.coin_clients[coin_type]['use_segwit']: + if self.coin_clients[coin_type]['use_segwit']: # TODO: Use isSegwitAddress when scantxoutset can use combo # 'Address does not refer to key' for non p2pkh addrinfo = self.callcoinrpc(coin_type, 'getaddressinfo', [sign_for_addr]) pkh = addrinfo['scriptPubKey'][4:] sign_for_addr = encodeAddress(bytes((chainparams[coin_type][self.chain]['pubkey_address'],)) + bytes.fromhex(pkh)) self.log.debug('sign_for_addr converted %s', sign_for_addr) + signature = self.callcoinrpc(coin_type, 'signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + extra_commit_bytes.hex()]) return (sign_for_addr, signature) @@ -2549,7 +2550,7 @@ class BasicSwap(BaseApp): return redeem_txn def createRefundTxn(self, coin_type, txn, offer, bid, txn_script, addr_refund_out=None, tx_type=TxTypes.ITX_REFUND): - self.log.debug('createRefundTxn') + self.log.debug('createRefundTxn for coin %s', Coins(coin_type).name) if self.coin_clients[coin_type]['connection_type'] != 'rpc': return None @@ -2662,7 +2663,7 @@ class BasicSwap(BaseApp): bid.setITxState(TxStates.TX_CONFIRMED) if bid.debug_ind == DebugTypes.BUYER_STOP_AFTER_ITX: - self.log.debug('bid %s: Abandoning bid for testing: %d.', bid_id.hex(), bid.debug_ind) + self.log.debug('bid %s: Abandoning bid for testing: %d, %s.', bid_id.hex(), bid.debug_ind, DebugTypes(bid.debug_ind).name) bid.setState(BidStates.BID_ABANDONED) self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), None) return # Bid saved in checkBidState @@ -2787,7 +2788,7 @@ class BasicSwap(BaseApp): sum_unspent = 0 self.log.debug('[rm] scantxoutset start') # scantxoutset is slow - ro = self.callcoinrpc(coin_type, 'scantxoutset', ['start', ['addr({})'.format(address)]]) + ro = self.callcoinrpc(coin_type, 'scantxoutset', ['start', ['addr({})'.format(address)]]) # TODO: Use combo(address) where possible self.log.debug('[rm] scantxoutset end') for o in ro['unspents']: if assert_txid and o['txid'] != assert_txid: @@ -3304,11 +3305,17 @@ class BasicSwap(BaseApp): if bid.was_sent: txn = self.createRedeemTxn(coin_from, bid, for_txn_type='initiate') - txid = self.submitTxn(coin_from, txn) - bid.initiate_tx.spend_txid = bytes.fromhex(txid) - # bid.initiate_txn_redeem = bytes.fromhex(txn) # Worth keeping? - self.log.debug('Submitted initiate redeem txn %s to %s chain for bid %s', txid, chainparams[coin_from]['name'], bid_id.hex()) + if bid.debug_ind == DebugTypes.DONT_SPEND_ITX: + self.log.debug('bid %s: Abandoning bid for testing: %d, %s.', bid_id.hex(), bid.debug_ind, DebugTypes(bid.debug_ind).name) + bid.setState(BidStates.BID_ABANDONED) + self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), None) + else: + txid = self.submitTxn(coin_from, txn) + + bid.initiate_tx.spend_txid = bytes.fromhex(txid) + # bid.initiate_txn_redeem = bytes.fromhex(txn) # Worth keeping? + self.log.debug('Submitted initiate redeem txn %s to %s chain for bid %s', txid, chainparams[coin_from]['name'], bid_id.hex()) # TODO: Wait for depth? new state SWAP_TXI_REDEEM_SENT? diff --git a/basicswap/basicswap_util.py b/basicswap/basicswap_util.py index 8e6b928..027e21b 100644 --- a/basicswap/basicswap_util.py +++ b/basicswap/basicswap_util.py @@ -156,6 +156,7 @@ class DebugTypes(IntEnum): CREATE_INVALID_COIN_B_LOCK = auto() BUYER_STOP_AFTER_ITX = auto() MAKE_INVALID_PTX = auto() + DONT_SPEND_ITX = auto() def strOfferState(state): diff --git a/basicswap/interface_btc.py b/basicswap/interface_btc.py index 5ad14c9..885ee99 100644 --- a/basicswap/interface_btc.py +++ b/basicswap/interface_btc.py @@ -254,9 +254,12 @@ class BTCInterface(CoinInterface): except Exception: return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee' + def isSegwitAddress(self, address): + return address.startswith(self.chainparams_network()['hrp'] + '1') + def decodeAddress(self, address): bech32_prefix = self.chainparams_network()['hrp'] - if address.startswith(bech32_prefix): + if address.startswith(bech32_prefix + '1'): return bytes(segwit_addr.decode(bech32_prefix, address)[1]) return decodeAddress(address)[1:] diff --git a/basicswap/js_server.py b/basicswap/js_server.py index 0b3c63c..88afb60 100644 --- a/basicswap/js_server.py +++ b/basicswap/js_server.py @@ -168,6 +168,7 @@ def js_bids(self, url_split, post_string, is_json): assert(offer), 'Offer not found.' ci_from = swap_client.ci(offer.coin_from) + ci_to = swap_client.ci(offer.coin_to) amount_from = inputAmount(get_data_entry(post_data, 'amount_from'), ci_from) addr_from = None @@ -186,10 +187,10 @@ def js_bids(self, url_split, post_string, is_json): extra_options = { 'valid_for_seconds': valid_for_seconds, } - if have_data_entry(form_data, 'bid_rate'): - extra_options['bid_rate'] = ci_to.make_int(get_data_entry(form_data, 'bid_rate'), r=1) - if have_data_entry(form_data, 'bid_amount'): - amount_from = inputAmount(get_data_entry(form_data, 'bid_amount'), ci_from) + if have_data_entry(post_data, 'bid_rate'): + extra_options['bid_rate'] = ci_to.make_int(get_data_entry(post_data, 'bid_rate'), r=1) + if have_data_entry(post_data, 'bid_amount'): + amount_from = inputAmount(get_data_entry(post_data, 'bid_amount'), ci_from) if offer.swap_type == SwapTypes.XMR_SWAP: bid_id = swap_client.postXmrBid(offer_id, amount_from, addr_send_from=addr_from, extra_options=extra_options) diff --git a/tests/basicswap/test_run.py b/tests/basicswap/test_run.py index 9b3e5be..dfdba71 100644 --- a/tests/basicswap/test_run.py +++ b/tests/basicswap/test_run.py @@ -13,20 +13,13 @@ $ pytest -v -s tests/basicswap/test_run.py::Test::test_04_ltc_btc """ -import os -import sys import json import random -import shutil -import signal import logging import unittest -import threading from urllib.request import urlopen -import basicswap.config as cfg from basicswap.basicswap import ( - BasicSwap, Coins, SwapTypes, BidStates, @@ -38,346 +31,53 @@ from basicswap.basicswap_util import ( ) from basicswap.util import ( COIN, - toWIF, - dumpje, make_int, format_amount, ) -from basicswap.rpc import ( - callrpc_cli, - waitForRPC, -) -from basicswap.contrib.key import ( - ECKey, -) -from basicswap.http_server import ( - HttpThread, -) from tests.basicswap.common import ( - checkForks, - stopDaemons, wait_for_offer, wait_for_bid, + wait_for_balance, wait_for_bid_tx_state, wait_for_in_progress, post_json_req, - TEST_HTTP_HOST, TEST_HTTP_PORT, - BASE_PORT, - BASE_RPC_PORT, - BASE_ZMQ_PORT, - PREFIX_SECRET_KEY_REGTEST, + LTC_BASE_RPC_PORT, + BTC_BASE_RPC_PORT, ) -from bin.basicswap_run import startDaemon - - -NUM_NODES = 3 -LTC_NODE = 3 -BTC_NODE = 4 - -test_delay_event = threading.Event() +from .test_xmr import BaseTest, test_delay_event, callnoderpc logger = logging.getLogger() -logger.level = logging.DEBUG -if not len(logger.handlers): - logger.addHandler(logging.StreamHandler(sys.stdout)) -def prepareOtherDir(datadir, nodeId, conf_file='litecoin.conf'): - 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, conf_file) - - 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('fallbackfee=0.0002\n') - fp.write('wallet=wallet.dat\n') - - fp.write('acceptnonstdtxn=0\n') - return node_dir - - -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') - fp.write('wallet=wallet.dat\n') - - fp.write('acceptnonstdtxn=0\n') - fp.write('minstakeinterval=2\n') - fp.write('smsgsregtestadjust=0\n') - fp.write('stakethreadconddelayms=1000\n') - - for i in range(0, NUM_NODES): - if nodeId == i: - continue - fp.write('addnode=127.0.0.1:%d\n' % (BASE_PORT + i)) - - if nodeId < 2: - fp.write('spentindex=1\n') - fp.write('txindex=1\n') - - basicswap_dir = os.path.join(datadir, str(nodeId), 'basicswap') - 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 = { - 'debug': True, - 'zmqhost': 'tcp://127.0.0.1', - 'zmqport': BASE_ZMQ_PORT + nodeId, - 'htmlhost': '127.0.0.1', - 'htmlport': 12700 + nodeId, - 'network_key': network_key, - 'network_pubkey': network_pubkey, - 'chainclients': { - 'particl': { - 'connection_type': 'rpc', - 'manage_daemon': False, - 'rpcport': BASE_RPC_PORT + nodeId, - 'datadir': node_dir, - 'bindir': cfg.PARTICL_BINDIR, - 'blocks_confirmed': 2, # Faster testing - }, - 'litecoin': { - 'connection_type': 'rpc', - 'manage_daemon': False, - 'rpcport': BASE_RPC_PORT + LTC_NODE, - 'datadir': ltcdatadir, - 'bindir': cfg.LITECOIN_BINDIR, - # 'use_segwit': True, - }, - 'bitcoin': { - 'connection_type': 'rpc', - 'manage_daemon': False, - 'rpcport': BASE_RPC_PORT + BTC_NODE, - 'datadir': btcdatadir, - 'bindir': cfg.BITCOIN_BINDIR, - 'use_segwit': True, - } - }, - 'check_progress_seconds': 2, - 'check_watched_seconds': 4, - 'check_expired_seconds': 60, - 'check_events_seconds': 1, - 'min_delay_event': 1, - 'max_delay_event': 5 - } - with open(settings_path, 'w') as fp: - json.dump(settings, fp, indent=4) - - return node_dir - - -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) - - -def btcRpc(cmd): - return callrpc_cli(cfg.BITCOIN_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(BTC_NODE)), 'regtest', cmd, cfg.BITCOIN_CLI) - - -def ltcRpc(cmd): - return callrpc_cli(cfg.LITECOIN_BINDIR, os.path.join(cfg.TEST_DATADIRS, str(LTC_NODE)), 'regtest', cmd, cfg.LITECOIN_CLI) - - -def signal_handler(sig, frame): - print('signal {} detected.'.format(sig)) - test_delay_event.set() - - -def run_coins_loop(cls): - while not test_delay_event.is_set(): - try: - ltcRpc('generatetoaddress 1 {}'.format(cls.ltc_addr)) - btcRpc('generatetoaddress 1 {}'.format(cls.btc_addr)) - except Exception as e: - logging.warning('run_coins_loop ' + str(e)) - test_delay_event.wait(1.0) - - -def run_loop(cls): - while not test_delay_event.is_set(): - for c in cls.swap_clients: - c.update() - test_delay_event.wait(1) - - -def make_part_cli_rpc_func(node_id): - node_id = node_id - - def rpc_func(method, params=None, wallet=None): - nonlocal node_id - cmd = method - if params: - for p in params: - if isinstance(p, dict) or isinstance(p, list): - cmd += ' "' + dumpje(p) + '"' - elif isinstance(p, int): - cmd += ' ' + str(p) - else: - cmd += ' "' + p + '"' - return partRpc(cmd, node_id) - return rpc_func - - -class Test(unittest.TestCase): +class Test(BaseTest): + __test__ = True @classmethod def setUpClass(cls): + cls.start_ltc_nodes = True + cls.start_xmr_nodes = False super(Test, cls).setUpClass() - eckey = ECKey() - eckey.generate() - cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, eckey.get_bytes()) - cls.network_pubkey = eckey.get_pubkey().get_bytes().hex() + btc_addr1 = callnoderpc(1, 'getnewaddress', ['initial funds', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT) + ltc_addr1 = callnoderpc(1, 'getnewaddress', ['initial funds', 'bech32'], base_rpc_port=LTC_BASE_RPC_PORT) - if os.path.isdir(cfg.TEST_DATADIRS): - logging.info('Removing ' + cfg.TEST_DATADIRS) - shutil.rmtree(cfg.TEST_DATADIRS) + callnoderpc(0, 'sendtoaddress', [btc_addr1, 1000], base_rpc_port=BTC_BASE_RPC_PORT) + callnoderpc(0, 'sendtoaddress', [ltc_addr1, 1000], base_rpc_port=LTC_BASE_RPC_PORT) - for i in range(NUM_NODES): - data_dir = prepareDir(cfg.TEST_DATADIRS, i, cls.network_key, cls.network_pubkey) - callrpc_cli(cfg.PARTICL_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'particl-wallet') # Necessary for 0.21 - - prepareOtherDir(cfg.TEST_DATADIRS, LTC_NODE) - data_dir = prepareOtherDir(cfg.TEST_DATADIRS, BTC_NODE, 'bitcoin.conf') - callrpc_cli(cfg.BITCOIN_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'bitcoin-wallet') # Necessary for 0.21 - - cls.daemons = [] - cls.swap_clients = [] - cls.http_threads = [] - - cls.daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, str(BTC_NODE)), cfg.BITCOIN_BINDIR, cfg.BITCOIND)) - logging.info('Started %s %d', cfg.BITCOIND, cls.daemons[-1].pid) - cls.daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, str(LTC_NODE)), cfg.LITECOIN_BINDIR, cfg.LITECOIND)) - logging.info('Started %s %d', cfg.LITECOIND, cls.daemons[-1].pid) - - for i in range(NUM_NODES): - cls.daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, str(i)), cfg.PARTICL_BINDIR, cfg.PARTICLD)) - logging.info('Started %s %d', cfg.PARTICLD, cls.daemons[-1].pid) - - for i in range(NUM_NODES): - # Load mnemonics after all nodes have started to avoid staking getting stuck in TryToSync - rpc = make_part_cli_rpc_func(i) - waitForRPC(rpc) - if i == 0: - rpc('extkeyimportmaster', ['abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb']) - elif i == 1: - rpc('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']) - rpc('getnewextaddress', ['lblExtTest']) - rpc('rescanblockchain') - else: - rpc('extkeyimportmaster', [rpc('mnemonic', ['new'])['master']]) - # Lower output split threshold for more stakeable outputs - rpc('walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}]) - - basicswap_dir = os.path.join(os.path.join(cfg.TEST_DATADIRS, str(i)), 'basicswap') - 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.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) - - 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() - - waitForRPC(ltcRpc) - num_blocks = 500 - logging.info('Mining %d litecoin blocks', num_blocks) - cls.ltc_addr = ltcRpc('getnewaddress mining_addr legacy') - ltcRpc('generatetoaddress {} {}'.format(num_blocks, cls.ltc_addr)) - - ro = ltcRpc('getblockchaininfo') - checkForks(ro) - - waitForRPC(btcRpc) - cls.btc_addr = btcRpc('getnewaddress mining_addr bech32') - logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr) - btcRpc('generatetoaddress {} {}'.format(num_blocks, cls.btc_addr)) - - ro = btcRpc('getblockchaininfo') - checkForks(ro) - - ro = ltcRpc('getwalletinfo') - print('ltcRpc', ro) - - signal.signal(signal.SIGINT, signal_handler) - cls.update_thread = threading.Thread(target=run_loop, args=(cls,)) - cls.update_thread.start() - - cls.coins_update_thread = threading.Thread(target=run_coins_loop, args=(cls,)) - cls.coins_update_thread.start() - - # Wait for height, or sequencelock is thrown off by genesis blocktime - num_blocks = 3 - logging.info('Waiting for Particl chain height %d', num_blocks) - for i in range(60): - particl_blocks = cls.swap_clients[0].callrpc('getblockchaininfo')['blocks'] - print('particl_blocks', particl_blocks) - if particl_blocks >= num_blocks: - break - test_delay_event.wait(1) - assert(particl_blocks >= num_blocks) + wait_for_balance(test_delay_event, 'http://127.0.0.1:1801/json/wallets/btc', 'balance', 1000.0) + wait_for_balance(test_delay_event, 'http://127.0.0.1:1801/json/wallets/ltc', 'balance', 1000.0) @classmethod def tearDownClass(cls): - logging.info('Finalising') - test_delay_event.set() - cls.update_thread.join() - cls.coins_update_thread.join() - for t in cls.http_threads: - t.stop() - t.join() - for c in cls.swap_clients: - c.finalise() - c.fp.close() - - stopDaemons(cls.daemons) - + logging.info('Finalising test') super(Test, cls).tearDownClass() + def getBalance(self, js_wallets, coin_type): + ci = self.swap_clients[0].ci(coin_type) + return ci.make_int(float(js_wallets[str(int(coin_type))]['balance']) + float(js_wallets[str(int(coin_type))]['unconfirmed'])) + def test_01_verifyrawtransaction(self): txn = '0200000001eb6e5c4ebba4efa32f40c7314cad456a64008e91ee30b2dd0235ab9bb67fbdbb01000000ee47304402200956933242dde94f6cf8f195a470f8d02aef21ec5c9b66c5d3871594bdb74c9d02201d7e1b440de8f4da672d689f9e37e98815fb63dbc1706353290887eb6e8f7235012103dc1b24feb32841bc2f4375da91fa97834e5983668c2a39a6b7eadb60e7033f9d205a803b28fe2f86c17db91fa99d7ed2598f79b5677ffe869de2e478c0d1c02cc7514c606382012088a8201fe90717abb84b481c2a59112414ae56ec8acc72273642ca26cc7a5812fdc8f68876a914225fbfa4cb725b75e511810ac4d6f74069bdded26703520140b27576a914207eb66b2fd6ed9924d6217efc7fa7b38dfabe666888acffffffff01e0167118020000001976a9140044e188928710cecba8311f1cf412135b98145c88ac00000000' prevout = { @@ -386,12 +86,12 @@ class Test(unittest.TestCase): 'scriptPubKey': 'a9143d37191e8b864222d14952a14c85504677a0581d87', 'redeemScript': '6382012088a8201fe90717abb84b481c2a59112414ae56ec8acc72273642ca26cc7a5812fdc8f68876a914225fbfa4cb725b75e511810ac4d6f74069bdded26703520140b27576a914207eb66b2fd6ed9924d6217efc7fa7b38dfabe666888ac', 'amount': 1.0} - ro = partRpc('verifyrawtransaction {} "{}"'.format(txn, dumpje([prevout, ]))) + ro = callnoderpc(0, 'verifyrawtransaction', [txn, [prevout, ]]) assert(ro['inputs_valid'] is False) assert(ro['validscripts'] == 1) prevout['amount'] = 100.0 - ro = partRpc('verifyrawtransaction {} "{}"'.format(txn, dumpje([prevout, ]))) + ro = callnoderpc(0, 'verifyrawtransaction', [txn, [prevout, ]]) assert(ro['inputs_valid'] is True) assert(ro['validscripts'] == 1) @@ -402,12 +102,12 @@ class Test(unittest.TestCase): 'scriptPubKey': 'a914129aee070317bbbd57062288849e85cf57d15c2687', 'redeemScript': '6382012088a8201fe90717abb84b481c2a59112414ae56ec8acc72273642ca26cc7a5812fdc8f68876a914207eb66b2fd6ed9924d6217efc7fa7b38dfabe666703a90040b27576a914225fbfa4cb725b75e511810ac4d6f74069bdded26888ac', 'amount': 1.0} - ro = partRpc('verifyrawtransaction {} "{}"'.format(txn, dumpje([prevout, ]))) + ro = callnoderpc(0, 'verifyrawtransaction', [txn, [prevout, ]]) assert(ro['inputs_valid'] is False) assert(ro['validscripts'] == 0) # Amount covered by signature prevout['amount'] = 90.0 - ro = partRpc('verifyrawtransaction {} "{}"'.format(txn, dumpje([prevout, ]))) + ro = callnoderpc(0, 'verifyrawtransaction', [txn, [prevout, ]]) assert(ro['inputs_valid'] is True) assert(ro['validscripts'] == 1) @@ -653,7 +353,7 @@ class Test(unittest.TestCase): def test_12_withdrawal(self): logging.info('---------- Test LTC withdrawals') - ltc_addr = ltcRpc('getnewaddress "Withdrawal test" legacy') + ltc_addr = callnoderpc(0, 'getnewaddress', ['Withdrawal test', 'legacy'], base_rpc_port=LTC_BASE_RPC_PORT) wallets0 = json.loads(urlopen('http://127.0.0.1:{}/json/wallets'.format(TEST_HTTP_PORT + 0)).read()) assert(float(wallets0['3']['balance']) > 100) @@ -665,6 +365,54 @@ class Test(unittest.TestCase): json_rv = json.loads(post_json_req('http://127.0.0.1:{}/json/wallets/ltc/withdraw'.format(TEST_HTTP_PORT + 0), post_json)) assert(len(json_rv['txid']) == 64) + def test_13_itx_refund(self): + logging.info('---------- Test ITX refunded') + # Initiator claims PTX and refunds ITX after lock expires + # Participant loses PTX value without gaining ITX value + swap_clients = self.swap_clients + + js_w0_before = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read()) + js_w1_before = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read()) + + swap_value = make_int(random.uniform(2.0, 20.0), scale=8, r=1) + logging.info('swap_value {}'.format(format_amount(swap_value, 8))) + offer_id = swap_clients[0].postOffer(Coins.LTC, Coins.BTC, swap_value, 0.5 * COIN, swap_value, SwapTypes.SELLER_FIRST, + SEQUENCE_LOCK_BLOCKS, 10) + + wait_for_offer(test_delay_event, swap_clients[1], offer_id) + offer = swap_clients[1].getOffer(offer_id) + bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + swap_clients[1].setBidDebugInd(bid_id, DebugTypes.DONT_SPEND_ITX) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id) + swap_clients[0].acceptBid(bid_id) + + wait_for_bid_tx_state(test_delay_event, swap_clients[0], bid_id, TxStates.TX_REFUNDED, TxStates.TX_REDEEMED, wait_for=60) + + js_w0_after = json.loads(urlopen('http://127.0.0.1:1800/json/wallets').read()) + js_w1_after = json.loads(urlopen('http://127.0.0.1:1801/json/wallets').read()) + + ltc_swap_value = swap_value + btc_swap_value = swap_value // 2 + node0_btc_before = self.getBalance(js_w0_before, Coins.BTC) + node0_btc_after = self.getBalance(js_w0_after, Coins.BTC) + node0_ltc_before = self.getBalance(js_w0_before, Coins.LTC) + node0_ltc_after = self.getBalance(js_w0_after, Coins.LTC) + + node1_btc_before = self.getBalance(js_w1_before, Coins.BTC) + node1_btc_after = self.getBalance(js_w1_after, Coins.BTC) + node1_ltc_before = self.getBalance(js_w1_before, Coins.LTC) + node1_ltc_after = self.getBalance(js_w1_after, Coins.LTC) + + high_fee_value_btc = int(0.001 * COIN) + high_fee_value_ltc = int(0.01 * COIN) # TODO Set fees directly, see listtransactions + + assert(node0_btc_after > node0_btc_before + btc_swap_value - high_fee_value_btc) + assert(node0_ltc_after > node0_ltc_before - high_fee_value_ltc) + + assert(node1_btc_after < node1_btc_before - btc_swap_value) + assert(node1_ltc_before == node1_ltc_after) + def pass_99_delay(self): logging.info('Delay') for i in range(60 * 10): diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index 784f770..4960e71 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -142,7 +142,7 @@ def startXmrWalletRPC(node_dir, bin_dir, wallet_bin, node_id, opts=[]): 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, with_ltc=False): +def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_ltc=False, with_xmr=False): basicswap_dir = os.path.join(datadir, 'basicswap_' + str(node_id)) if not os.path.exists(basicswap_dir): os.makedirs(basicswap_dir) @@ -167,17 +167,6 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_l 'bindir': cfg.PARTICL_BINDIR, 'blocks_confirmed': 2, # Faster testing }, - 'monero': { - 'connection_type': 'rpc', - 'manage_daemon': False, - '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), - 'walletfile': 'testwallet', - 'datadir': os.path.join(datadir, 'xmr_' + str(node_id)), - 'bindir': cfg.XMR_BINDIR, - }, 'bitcoin': { 'connection_type': 'rpc', 'manage_daemon': False, @@ -201,6 +190,19 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_l 'debug_ui': True, } + if with_xmr: + settings['chainclients']['monero'] = { + 'connection_type': 'rpc', + 'manage_daemon': False, + '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), + 'walletfile': 'testwallet', + 'datadir': os.path.join(datadir, 'xmr_' + str(node_id)), + 'bindir': cfg.XMR_BINDIR, + } + if with_ltc: settings['chainclients']['litecoin'] = { 'connection_type': 'rpc', @@ -291,6 +293,9 @@ class BaseTest(unittest.TestCase): def setUpClass(cls): if not hasattr(cls, 'start_ltc_nodes'): cls.start_ltc_nodes = False + if not hasattr(cls, 'start_xmr_nodes'): + cls.start_xmr_nodes = True + random.seed(time.time()) cls.update_thread = None @@ -371,23 +376,24 @@ class BaseTest(unittest.TestCase): waitForRPC(make_rpc_func(i, base_rpc_port=LTC_BASE_RPC_PORT)) - for i in range(NUM_XMR_NODES): - prepareXmrDataDir(TEST_DIR, i, 'monerod.conf') + if cls.start_xmr_nodes: + 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(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)) + 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) + 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]) + 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'}) + 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() @@ -396,7 +402,7 @@ class BaseTest(unittest.TestCase): 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, cls.start_ltc_nodes) + prepare_swapclient_dir(TEST_DIR, i, cls.network_key, cls.network_pubkey, cls.start_ltc_nodes, cls.start_xmr_nodes) 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: @@ -410,35 +416,53 @@ class BaseTest(unittest.TestCase): sc.setDaemonPID(Coins.LTC, cls.ltc_daemons[i].pid) sc.start() - # Set XMR main wallet address - xmr_ci = sc.ci(Coins.XMR) - sc.setStringKV('main_wallet_addr_' + xmr_ci.coin_name().lower(), xmr_ci.getMainWalletAddress()) + if cls.start_xmr_nodes: + # Set XMR main wallet address + xmr_ci = sc.ci(Coins.XMR) + sc.setStringKV('main_wallet_addr_' + xmr_ci.coin_name().lower(), xmr_ci.getMainWalletAddress()) cls.swap_clients.append(sc) 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, 1, 'get_address')['address'] + # Set future block rewards to nowhere (a random address) + eckey = ECKey() + eckey.generate() + void_block_rewards_pubkey = eckey.get_pubkey().get_bytes() - num_blocks = 500 # Mine enough to activate segwit + cls.btc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT) + num_blocks = 400 # Mine enough to activate segwit + 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) + + num_blocks = 100 + cls.btc_addr = cls.swap_clients[0].ci(Coins.BTC).pubkey_to_segwit_address(void_block_rewards_pubkey) 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 cls.start_ltc_nodes: + num_blocks = 400 cls.ltc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=LTC_BASE_RPC_PORT) logging.info('Mining %d Litecoin blocks to %s', num_blocks, cls.ltc_addr) callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT) + + num_blocks = 100 + cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey) + logging.info('Mining %d Litecoin blocks to %s', num_blocks, cls.ltc_addr) + callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT) + checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=LTC_BASE_RPC_PORT)) num_blocks = 100 - if callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'] < num_blocks: - logging.info('Mining %d Monero blocks to %s.', num_blocks, cls.xmr_addr) - callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': num_blocks}) - logging.info('XMR blocks: %d', callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count']) + if cls.start_xmr_nodes: + cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address'] + if callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'] < num_blocks: + logging.info('Mining %d Monero blocks to %s.', num_blocks, cls.xmr_addr) + callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': num_blocks}) + logging.info('XMR blocks: %d', callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count']) logging.info('Adding anon outputs') outputs = [] @@ -456,6 +480,7 @@ class BaseTest(unittest.TestCase): pause_event.set() cls.coins_update_thread = threading.Thread(target=run_coins_loop, args=(cls,)) cls.coins_update_thread.start() + except Exception: traceback.print_exc() Test.tearDownClass()