From 6860279faaee8069978a1be52fb67259590551d0 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Sun, 11 Dec 2022 01:26:42 +0200 Subject: [PATCH] tests: Add prefunded itx and xmr protocol tests --- basicswap/basicswap.py | 16 ++- basicswap/chainparams.py | 2 +- basicswap/interface/btc.py | 18 +-- basicswap/interface/dash.py | 30 ++++- basicswap/interface/firo.py | 25 +++- basicswap/interface/pivx.py | 35 +++++ basicswap/protocols/__init__.py | 7 + basicswap/protocols/atomic_swap_1.py | 6 +- basicswap/protocols/xmr_swap_1.py | 6 +- doc/protocols/sequence_diagrams/notes.txt | 15 +++ tests/basicswap/extended/test_dash.py | 146 +++++++++++++++++++- tests/basicswap/extended/test_firo.py | 132 ++++++++++++++++++ tests/basicswap/extended/test_network.py | 4 +- tests/basicswap/extended/test_nmc.py | 4 +- tests/basicswap/extended/test_pivx.py | 157 +++++++++++++++++++++- 15 files changed, 564 insertions(+), 39 deletions(-) create mode 100644 doc/protocols/sequence_diagrams/notes.txt diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index c1d54bb..d7db78c 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -1194,16 +1194,22 @@ class BasicSwap(BaseApp): ensure(amount_to > ci_to.min_amount(), 'To amount below min value for chain') ensure(amount_to < ci_to.max_amount(), 'To amount above max value for chain') - def validateOfferLockValue(self, coin_from, coin_to, lock_type, lock_value): + def validateOfferLockValue(self, swap_type, coin_from, coin_to, lock_type, lock_value): coin_from_has_csv = self.coin_clients[coin_from]['use_csv'] coin_to_has_csv = self.coin_clients[coin_to]['use_csv'] if lock_type == OfferMessage.SEQUENCE_LOCK_TIME: ensure(lock_value >= self.min_sequence_lock_seconds and lock_value <= self.max_sequence_lock_seconds, 'Invalid lock_value time') - ensure(coin_from_has_csv and coin_to_has_csv, 'Both coins need CSV activated.') + if swap_type == SwapTypes.XMR_SWAP: + ensure(coin_from_has_csv, 'Coin from needs CSV activated.') + else: + ensure(coin_from_has_csv and coin_to_has_csv, 'Both coins need CSV activated.') elif lock_type == OfferMessage.SEQUENCE_LOCK_BLOCKS: ensure(lock_value >= 5 and lock_value <= 1000, 'Invalid lock_value blocks') - ensure(coin_from_has_csv and coin_to_has_csv, 'Both coins need CSV activated.') + if swap_type == SwapTypes.XMR_SWAP: + ensure(coin_from_has_csv, 'Coin from needs CSV activated.') + else: + ensure(coin_from_has_csv and coin_to_has_csv, 'Both coins need CSV activated.') elif lock_type == TxLockTypes.ABS_LOCK_TIME: # TODO: range? ensure(not coin_from_has_csv or not coin_to_has_csv, 'Should use CSV.') @@ -1262,7 +1268,7 @@ class BasicSwap(BaseApp): self.validateSwapType(coin_from_t, coin_to_t, swap_type) self.validateOfferAmounts(coin_from_t, coin_to_t, amount, rate, min_bid_amount) - self.validateOfferLockValue(coin_from_t, coin_to_t, lock_type, lock_value) + self.validateOfferLockValue(swap_type, coin_from_t, coin_to_t, lock_type, lock_value) self.validateOfferValidTime(swap_type, coin_from_t, coin_to_t, valid_for_seconds) offer_addr_to = self.getOfferAddressTo(extra_options) @@ -3916,7 +3922,7 @@ class BasicSwap(BaseApp): self.validateSwapType(coin_from, coin_to, offer_data.swap_type) self.validateOfferAmounts(coin_from, coin_to, offer_data.amount_from, offer_data.rate, offer_data.min_bid_amount) - self.validateOfferLockValue(coin_from, coin_to, offer_data.lock_type, offer_data.lock_value) + self.validateOfferLockValue(offer_data.swap_type, coin_from, coin_to, offer_data.lock_type, offer_data.lock_value) self.validateOfferValidTime(offer_data.swap_type, coin_from, coin_to, offer_data.time_valid) ensure(msg['sent'] + offer_data.time_valid >= now, 'Offer expired') diff --git a/basicswap/chainparams.py b/basicswap/chainparams.py index 4b495d9..eb2c269 100644 --- a/basicswap/chainparams.py +++ b/basicswap/chainparams.py @@ -321,7 +321,7 @@ chainparams = { 'rpcport': 28888, 'pubkey_address': 65, 'script_address': 178, - 'key_prefix': 185, + 'key_prefix': 239, 'hrp': '', 'bip44': 1, 'min_amount': 1000, diff --git a/basicswap/interface/btc.py b/basicswap/interface/btc.py index 2d80ef9..6e293ff 100644 --- a/basicswap/interface/btc.py +++ b/basicswap/interface/btc.py @@ -92,7 +92,7 @@ def findOutput(tx, script_pk: bytes): return None -def find_vout_for_address_from_txobj(tx_obj, addr) -> int: +def find_vout_for_address_from_txobj(tx_obj, addr: str) -> int: """ Locate the vout index of the given transaction sending to the given address. Raises runtime error exception if not found. @@ -1033,7 +1033,15 @@ class BTCInterface(CoinInterface): return None ''' + def getBLockSpendTxFee(self, tx, fee_rate: int) -> int: + witness_bytes = 109 + vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) + pay_fee = int(fee_rate * vsize // 1000) + self._log.info(f'BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {vsize}, {pay_fee}.') + return pay_fee + def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes: + self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex()) wtx = self.rpc_callback('gettransaction', [chain_b_lock_txid.hex(), ]) lock_tx = self.loadTx(bytes.fromhex(wtx['hex'])) @@ -1054,12 +1062,8 @@ class BTCInterface(CoinInterface): scriptSig=self.getScriptScriptSig(script_lock))) tx.vout.append(self.txoType()(cb_swap_value, self.getScriptForPubkeyHash(pkh_to))) - witness_bytes = 109 - vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes) - pay_fee = int(b_fee * vsize // 1000) + pay_fee = self.getBLockSpendTxFee(tx, b_fee) tx.vout[0].nValue = cb_swap_value - pay_fee - self._log.info('spendBLockTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', - chain_b_lock_txid.hex(), b_fee, vsize, pay_fee) b_lock_spend_tx = tx.serialize() b_lock_spend_tx = self.signTxWithKey(b_lock_spend_tx, kbs) @@ -1366,10 +1370,8 @@ class BTCInterface(CoinInterface): except Exception as ex: self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) return None - if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: return {'txid': txid_hex, 'amount': 0, 'height': rv['blockheight']} - return None diff --git a/basicswap/interface/dash.py b/basicswap/interface/dash.py index 48d1df7..8312550 100644 --- a/basicswap/interface/dash.py +++ b/basicswap/interface/dash.py @@ -9,6 +9,10 @@ from .btc import BTCInterface from basicswap.chainparams import Coins from basicswap.util.address import decodeAddress from mnemonic import Mnemonic +from basicswap.contrib.test_framework.script import ( + CScript, + OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG +) class DASHInterface(BTCInterface): @@ -37,8 +41,32 @@ class DASHInterface(BTCInterface): return False def withdrawCoin(self, value, addr_to, subfee): - params = [addr_to, value, '', '', subfee] + params = [addr_to, value, '', '', subfee, False, False, self._conf_target] return self.rpc_callback('sendtoaddress', params) def getSpendableBalance(self): return self.make_int(self.rpc_callback('getwalletinfo')['balance']) + + def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray: + # Return P2PKH + return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]) + + def getBLockSpendTxFee(self, tx, fee_rate: int) -> int: + add_bytes = 107 + size = len(tx.serialize_with_witness()) + add_bytes + pay_fee = int(fee_rate * size // 1000) + self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.') + return pay_fee + + def findTxnByHash(self, txid_hex: str): + # Only works for wallet txns + try: + rv = self.rpc_callback('gettransaction', [txid_hex]) + except Exception as ex: + self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) + return None + if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: + block_height = self.getBlockHeader(rv['blockhash'])['height'] + return {'txid': txid_hex, 'amount': 0, 'height': block_height} + + return None diff --git a/basicswap/interface/firo.py b/basicswap/interface/firo.py index 24468c7..b4feb40 100644 --- a/basicswap/interface/firo.py +++ b/basicswap/interface/firo.py @@ -151,7 +151,6 @@ class FIROInterface(BTCInterface): def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray: # Return P2PKH - return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]) def getScriptDest(self, script: bytearray) -> bytearray: @@ -184,3 +183,27 @@ class FIROInterface(BTCInterface): def getSpendableBalance(self): return self.make_int(self.rpc_callback('getwalletinfo')['balance']) + + def getBLockSpendTxFee(self, tx, fee_rate: int) -> int: + add_bytes = 107 + size = len(tx.serialize_with_witness()) + add_bytes + pay_fee = int(fee_rate * size // 1000) + self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.') + return pay_fee + + def signTxWithKey(self, tx: bytes, key: bytes) -> bytes: + key_wif = self.encodeKey(key) + rv = self.rpc_callback('signrawtransaction', [tx.hex(), [], [key_wif, ]]) + return bytes.fromhex(rv['hex']) + + def findTxnByHash(self, txid_hex: str): + # Only works for wallet txns + try: + rv = self.rpc_callback('gettransaction', [txid_hex]) + except Exception as ex: + self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) + return None + if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: + block_height = self.getBlockHeader(rv['blockhash'])['height'] + return {'txid': txid_hex, 'amount': 0, 'height': block_height} + return None diff --git a/basicswap/interface/pivx.py b/basicswap/interface/pivx.py index 59cdca9..9988f1e 100644 --- a/basicswap/interface/pivx.py +++ b/basicswap/interface/pivx.py @@ -15,6 +15,13 @@ from .contrib.pivx_test_framework.messages import ( ToHex, FromHex, CTransaction) +from basicswap.contrib.test_framework.script import ( + CScript, + OP_DUP, + OP_HASH160, + OP_CHECKSIG, + OP_EQUALVERIFY, +) class PIVXInterface(BTCInterface): @@ -80,3 +87,31 @@ class PIVXInterface(BTCInterface): tx = CTransaction() tx.deserialize(BytesIO(tx_bytes)) return tx + + def getScriptForPubkeyHash(self, pkh: bytes) -> bytearray: + # Return P2PKH + return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]) + + def getBLockSpendTxFee(self, tx, fee_rate: int) -> int: + add_bytes = 107 + size = len(tx.serialize_with_witness()) + add_bytes + pay_fee = int(fee_rate * size // 1000) + self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.') + return pay_fee + + def signTxWithKey(self, tx: bytes, key: bytes) -> bytes: + key_wif = self.encodeKey(key) + rv = self.rpc_callback('signrawtransaction', [tx.hex(), [], [key_wif, ]]) + return bytes.fromhex(rv['hex']) + + def findTxnByHash(self, txid_hex: str): + # Only works for wallet txns + try: + rv = self.rpc_callback('gettransaction', [txid_hex]) + except Exception as ex: + self._log.debug('findTxnByHash getrawtransaction failed: {}'.format(txid_hex)) + return None + if 'confirmations' in rv and rv['confirmations'] >= self.blocks_confirmed: + block_height = self.getBlockHeader(rv['blockhash'])['height'] + return {'txid': txid_hex, 'amount': 0, 'height': block_height} + return None diff --git a/basicswap/protocols/__init__.py b/basicswap/protocols/__init__.py index 1c4769f..4867926 100644 --- a/basicswap/protocols/__init__.py +++ b/basicswap/protocols/__init__.py @@ -7,6 +7,9 @@ from basicswap.script import ( OpCodes, ) +from basicswap.util.script import ( + getP2WSH, +) class ProtocolInterface: @@ -22,3 +25,7 @@ class ProtocolInterface: def getMockScriptScriptPubkey(self, ci) -> bytearray: script = self.getMockScript() return ci.get_p2wsh_script_pubkey(script) if ci._use_segwit else ci.get_p2sh_script_pubkey(script) + + def getMockAddrTo(self, ci): + script = self.getMockScript() + return ci.encode_p2wsh(getP2WSH(script)) if ci._use_segwit else ci.encode_p2sh(script) diff --git a/basicswap/protocols/atomic_swap_1.py b/basicswap/protocols/atomic_swap_1.py index 37f5161..5f16410 100644 --- a/basicswap/protocols/atomic_swap_1.py +++ b/basicswap/protocols/atomic_swap_1.py @@ -10,9 +10,6 @@ from basicswap.db import ( from basicswap.util import ( SerialiseNum, ) -from basicswap.util.script import ( - getP2WSH, -) from basicswap.script import ( OpCodes, ) @@ -77,8 +74,7 @@ class AtomicSwapInterface(ProtocolInterface): swap_type = SwapTypes.SELLER_FIRST def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes: - script = self.getMockScript() - addr_to = ci.encode_p2wsh(getP2WSH(script)) if ci._use_segwit else ci.encode_p2sh(script) + addr_to = self.getMockAddrTo(ci) funded_tx = ci.createRawFundedTransaction(addr_to, amount, sub_fee, lock_unspents=False) return bytes.fromhex(funded_tx) diff --git a/basicswap/protocols/xmr_swap_1.py b/basicswap/protocols/xmr_swap_1.py index 8cc03a6..ffeaf59 100644 --- a/basicswap/protocols/xmr_swap_1.py +++ b/basicswap/protocols/xmr_swap_1.py @@ -9,9 +9,6 @@ from sqlalchemy.orm import scoped_session from basicswap.util import ( ensure, ) -from basicswap.util.script import ( - getP2WSH, -) from basicswap.chainparams import ( Coins, ) @@ -104,8 +101,7 @@ class XmrSwapInterface(ProtocolInterface): return CScript([2, Kal_enc, Kaf_enc, 2, CScriptOp(OP_CHECKMULTISIG)]) def getFundedInitiateTxTemplate(self, ci, amount: int, sub_fee: bool) -> bytes: - script = self.getMockScript() - addr_to = ci.encode_p2wsh(getP2WSH(script)) if ci._use_segwit else ci.encode_p2sh(script) + addr_to = self.getMockAddrTo(ci) funded_tx = ci.createRawFundedTransaction(addr_to, amount, sub_fee, lock_unspents=False) return bytes.fromhex(funded_tx) diff --git a/doc/protocols/sequence_diagrams/notes.txt b/doc/protocols/sequence_diagrams/notes.txt new file mode 100644 index 0000000..5df4d60 --- /dev/null +++ b/doc/protocols/sequence_diagrams/notes.txt @@ -0,0 +1,15 @@ +nvm use 14 +npm install -g mscgenjs-cli + +mscgenjs -T svg -i bidder.alt.xu -o bidder.alt.xu.svg +mscgenjs -T svg -i offerer.alt.xu -o offerer.alt.xu.svg +mscgenjs -T svg -i xmr.bidder.alt.xu -o xmr.bidder.alt.xu.svg +mscgenjs -T svg -i xmr.offerer.alt.xu -o xmr.offerer.alt.xu.svg + + +npm -g install svgo + +svgo --pretty bidder.alt.xu.svg -o bidder.alt.xu.min.svg +svgo --pretty offerer.alt.xu.svg -o offerer.alt.xu.min.svg +svgo --pretty xmr.bidder.alt.xu.svg -o xmr.bidder.alt.xu.min.svg +svgo --pretty xmr.offerer.alt.xu.svg -o xmr.offerer.alt.xu.min.svg diff --git a/tests/basicswap/extended/test_dash.py b/tests/basicswap/extended/test_dash.py index 18c2ed1..c06e405 100644 --- a/tests/basicswap/extended/test_dash.py +++ b/tests/basicswap/extended/test_dash.py @@ -25,9 +25,10 @@ import basicswap.config as cfg from basicswap.basicswap import ( BasicSwap, Coins, + TxStates, SwapTypes, BidStates, - TxStates, + DebugTypes, ) from basicswap.util import ( COIN, @@ -56,6 +57,8 @@ from tests.basicswap.common import ( stopDaemons, wait_for_offer, wait_for_bid, + wait_for_balance, + wait_for_unspent, wait_for_bid_tx_state, wait_for_in_progress, TEST_HTTP_HOST, @@ -192,7 +195,15 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey): }, 'check_progress_seconds': 2, 'check_watched_seconds': 4, - 'check_expired_seconds': 60 + 'check_expired_seconds': 60, + 'check_events_seconds': 1, + 'check_xmr_swaps_seconds': 1, + 'min_delay_event': 1, + 'max_delay_event': 3, + 'min_delay_event_short': 1, + 'max_delay_event_short': 3, + 'min_delay_retry': 2, + 'max_delay_retry': 10 } with open(settings_path, 'w') as fp: json.dump(settings, fp, indent=4) @@ -305,8 +316,8 @@ class Test(unittest.TestCase): rpc('rescanblockchain') else: rpc('extkeyimportmaster', [rpc('mnemonic', ['new'])['master']]) - rpc('walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}]) - rpc('reservebalance', [False]) + rpc('walletsettings', ['stakingoptions', json.dumps({'stakecombinethreshold': 100, 'stakesplitthreshold': 200}).replace('"', '\\"')]) + rpc('reservebalance', ['false']) basicswap_dir = os.path.join(os.path.join(cfg.TEST_DATADIRS, str(i)), 'basicswap') settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME) @@ -564,6 +575,133 @@ class Test(unittest.TestCase): dashRpc('unloadwallet', wallet=new_wallet_name) assert (addr_test == addr) + def test_10_prefunded_itx(self): + logging.info('---------- Test prefunded itx offer') + + swap_clients = self.swap_clients + coin_from = Coins.DASH + coin_to = Coins.BTC + swap_type = SwapTypes.SELLER_FIRST + ci_from = swap_clients[2].ci(coin_from) + ci_to = swap_clients[1].ci(coin_to) + tla_from = coin_from.name + + # Prepare balance + js_w2 = read_json_api(1802, 'wallets') + if float(js_w2[tla_from]['balance']) < 100.0: + post_json = { + 'value': 100, + 'address': js_w2[tla_from]['deposit_address'], + 'subfee': False, + } + json_rv = read_json_api(1800, 'wallets/{}/withdraw'.format(tla_from.lower()), post_json) + assert (len(json_rv['txid']) == 64) + wait_for_balance(delay_event, 'http://127.0.0.1:1802/json/wallets/{}'.format(tla_from.lower()), 'balance', 100.0) + + js_w2 = read_json_api(1802, 'wallets') + assert (float(js_w2[tla_from]['balance']) >= 100.0) + + js_w2 = read_json_api(1802, 'wallets') + post_json = { + 'value': 100.0, + 'address': read_json_api(1802, 'wallets/{}/nextdepositaddr'.format(tla_from.lower())), + 'subfee': True, + } + json_rv = read_json_api(1802, 'wallets/{}/withdraw'.format(tla_from.lower()), post_json) + wait_for_balance(delay_event, 'http://127.0.0.1:1802/json/wallets/{}'.format(tla_from.lower()), 'balance', 10.0) + assert (len(json_rv['txid']) == 64) + + # Create prefunded ITX + pi = swap_clients[2].pi(SwapTypes.XMR_SWAP) + js_w2 = read_json_api(1802, 'wallets') + swap_value = 100.0 + if float(js_w2[tla_from]['balance']) < swap_value: + swap_value = js_w2[tla_from]['balance'] + swap_value = ci_from.make_int(swap_value) + assert (swap_value > ci_from.make_int(95)) + + itx = pi.getFundedInitiateTxTemplate(ci_from, swap_value, True) + itx_decoded = ci_from.describeTx(itx.hex()) + value_after_subfee = ci_from.make_int(itx_decoded['vout'][0]['value']) + assert (value_after_subfee < swap_value) + swap_value = value_after_subfee + wait_for_unspent(delay_event, ci_from, swap_value) + + extra_options = {'prefunded_itx': itx} + rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1) + offer_id = swap_clients[2].postOffer(coin_from, coin_to, swap_value, rate_swap, swap_value, swap_type, extra_options=extra_options) + + wait_for_offer(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) + + wait_for_bid(delay_event, swap_clients[2], bid_id, BidStates.BID_RECEIVED) + swap_clients[2].acceptBid(bid_id) + + wait_for_bid(delay_event, swap_clients[2], bid_id, BidStates.SWAP_COMPLETED, wait_for=120) + wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120) + + # Verify expected inputs were used + bid, offer = swap_clients[2].getBidAndOffer(bid_id) + assert (bid.initiate_tx) + wtx = ci_from.rpc_callback('gettransaction', [bid.initiate_tx.txid.hex(),]) + itx_after = ci_from.describeTx(wtx['hex']) + assert (len(itx_after['vin']) == len(itx_decoded['vin'])) + for i, txin in enumerate(itx_decoded['vin']): + assert (txin['txid'] == itx_after['vin'][i]['txid']) + assert (txin['vout'] == itx_after['vin'][i]['vout']) + + def test_11_xmrswap_to(self): + logging.info('---------- Test xmr swap protocol to') + + swap_clients = self.swap_clients + coin_from = Coins.BTC + coin_to = Coins.DASH + swap_type = SwapTypes.XMR_SWAP + ci_from = swap_clients[0].ci(coin_from) + ci_to = swap_clients[1].ci(coin_to) + + swap_value = ci_from.make_int(random.uniform(0.2, 20.0), r=1) + rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1) + offer_id = swap_clients[0].postOffer(coin_from, coin_to, swap_value, rate_swap, swap_value, swap_type) + + wait_for_offer(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) + + wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED) + swap_clients[0].acceptBid(bid_id) + + wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=120) + wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120) + + def test_12_xmrswap_to_recover_b_lock_tx(self): + coin_from = Coins.BTC + coin_to = Coins.DASH + logging.info('---------- Test {} to {} follower recovers coin b lock tx'.format(coin_from.name, coin_to.name)) + + swap_clients = self.swap_clients + ci_from = swap_clients[0].ci(coin_from) + ci_to = swap_clients[1].ci(coin_to) + + amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1) + rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1) + offer_id = swap_clients[0].postOffer( + coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP, + lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=32) + wait_for_offer(delay_event, swap_clients[1], offer_id) + offer = swap_clients[1].getOffer(offer_id) + + bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from) + wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED) + + bid, xmr_swap = swap_clients[0].getXmrBid(bid_id) + swap_clients[1].setBidDebugInd(bid_id, DebugTypes.CREATE_INVALID_COIN_B_LOCK) + swap_clients[0].acceptXmrBid(bid_id) + + wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180) + wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True) + if __name__ == '__main__': unittest.main() diff --git a/tests/basicswap/extended/test_firo.py b/tests/basicswap/extended/test_firo.py index 9e18481..07723a8 100644 --- a/tests/basicswap/extended/test_firo.py +++ b/tests/basicswap/extended/test_firo.py @@ -16,6 +16,7 @@ from basicswap.basicswap import ( TxStates, SwapTypes, BidStates, + DebugTypes, ) from basicswap.basicswap_util import ( TxLockTypes, @@ -38,6 +39,8 @@ from tests.basicswap.common import ( make_rpc_func, TEST_HTTP_PORT, wait_for_offer, + wait_for_balance, + wait_for_unspent, wait_for_in_progress, wait_for_bid_tx_state, ) @@ -420,6 +423,135 @@ class Test(BaseTest): json_rv = read_json_api(TEST_HTTP_PORT + 0, 'wallets/{}/createutxo'.format(self.test_coin_from.name.lower()), post_json) assert (len(json_rv['txid']) == 64) + def ensure_balance(self, coin_type, node_id, amount): + tla = coin_type.name + js_w = read_json_api(1800 + node_id, 'wallets') + if float(js_w[tla]['balance']) < amount: + post_json = { + 'value': amount, + 'address': js_w[tla]['deposit_address'], + 'subfee': False, + } + json_rv = read_json_api(1800, 'wallets/{}/withdraw'.format(tla.lower()), post_json) + assert (len(json_rv['txid']) == 64) + wait_for_balance(test_delay_event, 'http://127.0.0.1:{}/json/wallets/{}'.format(1800 + node_id, tla.lower()), 'balance', amount) + + def test_10_prefunded_itx(self): + logging.info('---------- Test prefunded itx offer') + + swap_clients = self.swap_clients + coin_from = Coins.FIRO + coin_to = Coins.BTC + swap_type = SwapTypes.SELLER_FIRST + ci_from = swap_clients[2].ci(coin_from) + ci_to = swap_clients[1].ci(coin_to) + tla_from = coin_from.name + + # Prepare balance + self.ensure_balance(coin_from, 2, 10.0) + self.ensure_balance(coin_to, 1, 100.0) + + js_w2 = read_json_api(1802, 'wallets') + post_json = { + 'value': 10.0, + 'address': read_json_api(1802, 'wallets/{}/nextdepositaddr'.format(tla_from.lower())), + 'subfee': True, + } + json_rv = read_json_api(1802, 'wallets/{}/withdraw'.format(tla_from.lower()), post_json) + wait_for_balance(test_delay_event, 'http://127.0.0.1:1802/json/wallets/{}'.format(tla_from.lower()), 'balance', 9.0) + assert (len(json_rv['txid']) == 64) + + # Create prefunded ITX + pi = swap_clients[2].pi(SwapTypes.XMR_SWAP) + js_w2 = read_json_api(1802, 'wallets') + swap_value = 10.0 + if float(js_w2[tla_from]['balance']) < swap_value: + swap_value = js_w2[tla_from]['balance'] + swap_value = ci_from.make_int(swap_value) + assert (swap_value > ci_from.make_int(9)) + + itx = pi.getFundedInitiateTxTemplate(ci_from, swap_value, True) + itx_decoded = ci_from.describeTx(itx.hex()) + value_after_subfee = ci_from.make_int(itx_decoded['vout'][0]['value']) + assert (value_after_subfee < swap_value) + swap_value = value_after_subfee + wait_for_unspent(test_delay_event, ci_from, swap_value) + + extra_options = {'prefunded_itx': itx} + rate_swap = ci_to.make_int(random.uniform(0.2, 10.0), r=1) + offer_id = swap_clients[2].postOffer(coin_from, coin_to, swap_value, rate_swap, swap_value, swap_type, extra_options=extra_options) + + 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) + + wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.BID_RECEIVED) + swap_clients[2].acceptBid(bid_id) + + wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.SWAP_COMPLETED, wait_for=120) + wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120) + + # Verify expected inputs were used + bid, offer = swap_clients[2].getBidAndOffer(bid_id) + assert (bid.initiate_tx) + wtx = ci_from.rpc_callback('gettransaction', [bid.initiate_tx.txid.hex(),]) + itx_after = ci_from.describeTx(wtx['hex']) + assert (len(itx_after['vin']) == len(itx_decoded['vin'])) + for i, txin in enumerate(itx_decoded['vin']): + assert (txin['txid'] == itx_after['vin'][i]['txid']) + assert (txin['vout'] == itx_after['vin'][i]['vout']) + + def test_11_xmrswap_to(self): + logging.info('---------- Test xmr swap protocol to') + + swap_clients = self.swap_clients + coin_from = Coins.BTC + coin_to = Coins.FIRO + swap_type = SwapTypes.XMR_SWAP + ci_from = swap_clients[0].ci(coin_from) + ci_to = swap_clients[1].ci(coin_to) + + swap_value = ci_from.make_int(random.uniform(0.2, 20.0), r=1) + rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1) + offer_id = swap_clients[0].postOffer(coin_from, coin_to, swap_value, rate_swap, swap_value, swap_type) + + 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) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED) + swap_clients[0].acceptBid(bid_id) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=120) + wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120) + + def test_12_xmrswap_to_recover_b_lock_tx(self): + coin_from = Coins.BTC + coin_to = Coins.FIRO + logging.info('---------- Test {} to {} follower recovers coin b lock tx'.format(coin_from.name, coin_to.name)) + + swap_clients = self.swap_clients + ci_from = swap_clients[0].ci(coin_from) + ci_to = swap_clients[1].ci(coin_to) + + amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1) + rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1) + offer_id = swap_clients[0].postOffer( + coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP, + lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=32) + wait_for_offer(test_delay_event, swap_clients[1], offer_id) + offer = swap_clients[1].getOffer(offer_id) + + bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from) + wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED) + + bid, xmr_swap = swap_clients[0].getXmrBid(bid_id) + swap_clients[1].setBidDebugInd(bid_id, DebugTypes.CREATE_INVALID_COIN_B_LOCK) + swap_clients[0].acceptXmrBid(bid_id) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180) + wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True) + def test_101_full_swap(self): logging.info('---------- Test {} to XMR'.format(self.test_coin_from.name)) if not self.test_xmr: diff --git a/tests/basicswap/extended/test_network.py b/tests/basicswap/extended/test_network.py index 9da32d5..c82c3a2 100644 --- a/tests/basicswap/extended/test_network.py +++ b/tests/basicswap/extended/test_network.py @@ -220,8 +220,8 @@ class Test(unittest.TestCase): else: rpc('extkeyimportmaster', [rpc('mnemonic', ['new'])['master']]) # Lower output split threshold for more stakeable outputs - rpc('walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}]) - rpc('reservebalance', [False]) + rpc('walletsettings', ['stakingoptions', json.dumps({'stakecombinethreshold': 100, 'stakesplitthreshold': 200}).replace('"', '\\"')]) + rpc('reservebalance', ['false']) for i in range(NUM_BTC_NODES): data_dir = prepareDataDir(TEST_DIR, i, 'bitcoin.conf', 'btc_', base_p2p_port=BTC_BASE_PORT, base_rpc_port=BTC_BASE_RPC_PORT) diff --git a/tests/basicswap/extended/test_nmc.py b/tests/basicswap/extended/test_nmc.py index fb0efdf..c4dcdb6 100644 --- a/tests/basicswap/extended/test_nmc.py +++ b/tests/basicswap/extended/test_nmc.py @@ -297,8 +297,8 @@ class Test(unittest.TestCase): rpc('rescanblockchain') else: rpc('extkeyimportmaster', [rpc('mnemonic', ['new'])['master']]) - rpc('walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}]) - rpc('reservebalance', [False]) + rpc('walletsettings', ['stakingoptions', json.dumps({'stakecombinethreshold': 100, 'stakesplitthreshold': 200}).replace('"', '\\"')]) + rpc('reservebalance', ['false']) basicswap_dir = os.path.join(os.path.join(cfg.TEST_DATADIRS, str(i)), 'basicswap') settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME) diff --git a/tests/basicswap/extended/test_pivx.py b/tests/basicswap/extended/test_pivx.py index 383281a..cb290a0 100644 --- a/tests/basicswap/extended/test_pivx.py +++ b/tests/basicswap/extended/test_pivx.py @@ -14,6 +14,7 @@ import os import sys import json import time +import random import shutil import signal import logging @@ -27,6 +28,7 @@ from basicswap.basicswap import ( SwapTypes, BidStates, TxStates, + DebugTypes, ) from basicswap.util import ( COIN, @@ -47,16 +49,21 @@ from basicswap.contrib.key import ( from basicswap.http_server import ( HttpThread, ) +from basicswap.interface.btc import ( + find_vout_for_address_from_txobj, +) from tests.basicswap.util import ( read_json_api, ) from tests.basicswap.common import ( checkForks, stopDaemons, - wait_for_offer, wait_for_bid, - wait_for_bid_tx_state, + wait_for_offer, + wait_for_balance, + wait_for_unspent, wait_for_in_progress, + wait_for_bid_tx_state, TEST_HTTP_HOST, TEST_HTTP_PORT, BASE_PORT, @@ -197,7 +204,15 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey): }, 'check_progress_seconds': 2, 'check_watched_seconds': 4, - 'check_expired_seconds': 60 + 'check_expired_seconds': 60, + 'check_events_seconds': 1, + 'check_xmr_swaps_seconds': 1, + 'min_delay_event': 1, + 'max_delay_event': 3, + 'min_delay_event_short': 1, + 'max_delay_event_short': 3, + 'min_delay_retry': 2, + 'max_delay_retry': 10 } with open(settings_path, 'w') as fp: json.dump(settings, fp, indent=4) @@ -317,8 +332,8 @@ class Test(unittest.TestCase): rpc('rescanblockchain') else: rpc('extkeyimportmaster', [rpc('mnemonic', ['new'])['master']]) - rpc('walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}]) - rpc('reservebalance', [False]) + rpc('walletsettings', ['stakingoptions', json.dumps({'stakecombinethreshold': 100, 'stakesplitthreshold': 200}).replace('"', '\\"')]) + rpc('reservebalance', ['false']) basicswap_dir = os.path.join(os.path.join(cfg.TEST_DATADIRS, str(i)), 'basicswap') settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME) @@ -581,6 +596,138 @@ class Test(unittest.TestCase): break assert found + def ensure_balance(self, coin_type, node_id, amount): + tla = coin_type.name + js_w = read_json_api(1800 + node_id, 'wallets') + if float(js_w[tla]['balance']) < amount: + post_json = { + 'value': amount, + 'address': js_w[tla]['deposit_address'], + 'subfee': False, + } + json_rv = read_json_api(1800, 'wallets/{}/withdraw'.format(tla.lower()), post_json) + assert (len(json_rv['txid']) == 64) + wait_for_balance(delay_event, 'http://127.0.0.1:{}/json/wallets/{}'.format(1800 + node_id, tla.lower()), 'balance', amount) + + def test_10_prefunded_itx(self): + logging.info('---------- Test prefunded itx offer') + + swap_clients = self.swap_clients + coin_from = Coins.PIVX + coin_to = Coins.BTC + swap_type = SwapTypes.SELLER_FIRST + ci_from = swap_clients[2].ci(coin_from) + ci_to = swap_clients[1].ci(coin_to) + tla_from = coin_from.name + + # Prepare balance + self.ensure_balance(coin_from, 2, 10.0) + self.ensure_balance(coin_to, 1, 100.0) + + js_w2 = read_json_api(1802, 'wallets') + post_json = { + 'value': 10.0, + 'address': read_json_api(1802, 'wallets/{}/nextdepositaddr'.format(tla_from.lower())), + 'subfee': True, + } + json_rv = read_json_api(1802, 'wallets/{}/withdraw'.format(tla_from.lower()), post_json) + wait_for_balance(delay_event, 'http://127.0.0.1:1802/json/wallets/{}'.format(tla_from.lower()), 'balance', 9.0) + assert (len(json_rv['txid']) == 64) + + # Create prefunded ITX + pi = swap_clients[2].pi(SwapTypes.XMR_SWAP) + js_w2 = read_json_api(1802, 'wallets') + swap_value = 10.0 + if float(js_w2[tla_from]['balance']) < swap_value: + swap_value = js_w2[tla_from]['balance'] + swap_value = ci_from.make_int(swap_value) + assert (swap_value > ci_from.make_int(9)) + + itx = pi.getFundedInitiateTxTemplate(ci_from, swap_value, True) + itx_decoded = ci_from.describeTx(itx.hex()) + + mock_addr = pi.getMockAddrTo(ci_from) + n = find_vout_for_address_from_txobj(itx_decoded, mock_addr) + value_after_subfee = ci_from.make_int(itx_decoded['vout'][n]['value']) + assert (value_after_subfee < swap_value) + swap_value = value_after_subfee + wait_for_unspent(delay_event, ci_from, swap_value) + + extra_options = {'prefunded_itx': itx} + rate_swap = ci_to.make_int(random.uniform(0.2, 10.0), r=1) + offer_id = swap_clients[2].postOffer(coin_from, coin_to, swap_value, rate_swap, swap_value, swap_type, TxLockTypes.ABS_LOCK_TIME, extra_options=extra_options) + + wait_for_offer(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) + + wait_for_bid(delay_event, swap_clients[2], bid_id, BidStates.BID_RECEIVED) + swap_clients[2].acceptBid(bid_id) + + wait_for_bid(delay_event, swap_clients[2], bid_id, BidStates.SWAP_COMPLETED, wait_for=120) + wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120) + + # Verify expected inputs were used + bid, offer = swap_clients[2].getBidAndOffer(bid_id) + assert (bid.initiate_tx) + wtx = ci_from.rpc_callback('gettransaction', [bid.initiate_tx.txid.hex(),]) + itx_after = ci_from.describeTx(wtx['hex']) + assert (len(itx_after['vin']) == len(itx_decoded['vin'])) + for i, txin in enumerate(itx_decoded['vin']): + assert (txin['txid'] == itx_after['vin'][i]['txid']) + assert (txin['vout'] == itx_after['vin'][i]['vout']) + + def test_11_xmrswap_to(self): + logging.info('---------- Test xmr swap protocol to') + + swap_clients = self.swap_clients + coin_from = Coins.BTC + coin_to = Coins.PIVX + swap_type = SwapTypes.XMR_SWAP + ci_from = swap_clients[0].ci(coin_from) + ci_to = swap_clients[1].ci(coin_to) + + swap_value = ci_from.make_int(random.uniform(0.2, 20.0), r=1) + rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1) + offer_id = swap_clients[0].postOffer(coin_from, coin_to, swap_value, rate_swap, swap_value, swap_type) + + wait_for_offer(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) + + wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED) + swap_clients[0].acceptBid(bid_id) + + wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=120) + wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120) + + def test_12_xmrswap_to_recover_b_lock_tx(self): + coin_from = Coins.BTC + coin_to = Coins.PIVX + logging.info('---------- Test {} to {} follower recovers coin b lock tx'.format(coin_from.name, coin_to.name)) + + swap_clients = self.swap_clients + ci_from = swap_clients[0].ci(coin_from) + ci_to = swap_clients[1].ci(coin_to) + + amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1) + rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1) + offer_id = swap_clients[0].postOffer( + coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP, + lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=32) + wait_for_offer(delay_event, swap_clients[1], offer_id) + offer = swap_clients[1].getOffer(offer_id) + + bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from) + wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED) + + bid, xmr_swap = swap_clients[0].getXmrBid(bid_id) + swap_clients[1].setBidDebugInd(bid_id, DebugTypes.CREATE_INVALID_COIN_B_LOCK) + swap_clients[0].acceptXmrBid(bid_id) + + wait_for_bid(delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180) + wait_for_bid(delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True) + if __name__ == '__main__': unittest.main()