tests: Add prefunded itx and xmr protocol tests

This commit is contained in:
tecnovert 2022-12-11 01:26:42 +02:00
parent 80df3b1a34
commit 6860279faa
No known key found for this signature in database
GPG key ID: 8ED6D8750C4E3F93
15 changed files with 564 additions and 39 deletions

View file

@ -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')

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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()

View file

@ -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:

View file

@ -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)

View file

@ -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)

View file

@ -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()