Browse Source

Support xmr-protocol swaps to BTC and PART

pull/22/head
tecnovert 2 years ago
parent
commit
0e1cb6d03d
No known key found for this signature in database GPG Key ID: 8ED6D8750C4E3F93
  1. 18
      basicswap/basicswap.py
  2. 2
      basicswap/basicswap_util.py
  3. 11
      basicswap/chainparams.py
  4. 192
      basicswap/interface/btc.py
  5. 10
      basicswap/interface/firo.py
  6. 2
      basicswap/interface/part.py
  7. 63
      basicswap/interface/xmr.py
  8. 255
      tests/basicswap/test_btc_xmr.py
  9. 1
      tests/basicswap/test_xmr.py

18
basicswap/basicswap.py

@ -3227,11 +3227,11 @@ class BasicSwap(BaseApp):
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SEEN, '', session)
if bid.xmr_b_lock_tx is None:
self.log.debug('Found {} lock tx in chain'.format(ci_to.coin_name()))
b_lock_tx_id = bytes.fromhex(found_tx['txid'])
xmr_swap.b_lock_tx_id = bytes.fromhex(found_tx['txid'])
bid.xmr_b_lock_tx = SwapTx(
bid_id=bid_id,
tx_type=TxTypes.XMR_SWAP_B_LOCK,
txid=b_lock_tx_id,
txid=xmr_swap.b_lock_tx_id,
chain_height=found_tx['height'],
)
bid_changed = True
@ -3930,7 +3930,7 @@ class BasicSwap(BaseApp):
raise ValueError('TODO')
elif offer_data.swap_type == SwapTypes.XMR_SWAP:
ensure(coin_from not in non_script_type_coins, 'Invalid coin from type')
ensure(coin_to in non_script_type_coins, 'Invalid coin to type')
ensure(ci_from.has_segwit(), 'Coin from must support segwit')
ensure(len(offer_data.proof_address) == 0, 'Unexpected data')
ensure(len(offer_data.proof_signature) == 0, 'Unexpected data')
ensure(len(offer_data.pkhash_seller) == 0, 'Unexpected data')
@ -4728,6 +4728,8 @@ class BasicSwap(BaseApp):
try:
b_lock_tx_id = ci_to.publishBLockTx(xmr_swap.pkbv, xmr_swap.pkbs, bid.amount_to, xmr_offer.b_fee_rate, unlock_time=unlock_time)
except Exception as ex:
if self.debug:
self.log.error(traceback.format_exc())
error_msg = 'publishBLockTx failed for bid {} with error {}'.format(bid_id.hex(), str(ex))
num_retries = self.countBidEvents(bid, EventLogTypes.FAILED_TX_B_LOCK_PUBLISH, session)
if num_retries > 0:
@ -4751,6 +4753,7 @@ class BasicSwap(BaseApp):
tx_type=TxTypes.XMR_SWAP_B_LOCK,
txid=b_lock_tx_id,
)
xmr_swap.b_lock_tx_id = b_lock_tx_id
bid.xmr_b_lock_tx.setState(TxStates.TX_NONE)
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_PUBLISHED, '', session)
@ -4867,8 +4870,11 @@ class BasicSwap(BaseApp):
if coin_to == Coins.XMR:
address_to = self.getCachedMainWalletAddress(ci_to)
else:
elif coin_to == Coins.PART_BLIND:
address_to = self.getCachedStealthAddressForCoin(coin_to)
else:
address_to = self.getReceiveAddressFromPool(coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_SPEND)
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, bid.chain_b_height_start)
self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, '', session)
@ -4924,8 +4930,10 @@ class BasicSwap(BaseApp):
try:
if offer.coin_to == Coins.XMR:
address_to = self.getCachedMainWalletAddress(ci_to)
else:
elif coin_to == Coins.PART_BLIND:
address_to = self.getCachedStealthAddressForCoin(coin_to)
else:
address_to = self.getReceiveAddressFromPool(coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_REFUND)
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, bid.chain_b_height_start)
self.log.debug('Submitted lock B refund txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED, '', session)

2
basicswap/basicswap_util.py

@ -122,6 +122,8 @@ class TxTypes(IntEnum):
XMR_SWAP_A_LOCK_REFUND_SPEND = auto()
XMR_SWAP_A_LOCK_REFUND_SWIPE = auto()
XMR_SWAP_B_LOCK = auto()
XMR_SWAP_B_LOCK_SPEND = auto()
XMR_SWAP_B_LOCK_REFUND = auto()
ITX_PRE_FUNDED = auto()

11
basicswap/chainparams.py

@ -296,7 +296,7 @@ chainparams = {
'blocks_target': 60 * 10,
'decimal_places': 8,
'has_csv': True,
'has_segwit': True,
'has_segwit': False,
'mainnet': {
'rpcport': 8888,
'pubkey_address': 82,
@ -353,20 +353,20 @@ class CoinInterface:
self._unknown_wallet_seed = True
self._restore_height = None
def make_int(self, amount_in, r=0):
def make_int(self, amount_in: int, r: int = 0) -> int:
return make_int(amount_in, self.exp(), r=r)
def format_amount(self, amount_in, conv_int=False, r=0):
amount_int = make_int(amount_in, self.exp(), r=r) if conv_int else amount_in
return format_amount(amount_int, self.exp())
def coin_name(self):
def coin_name(self) -> str:
coin_chainparams = chainparams[self.coin_type()]
if coin_chainparams.get('use_ticker_as_name', False):
return coin_chainparams['ticker']
return coin_chainparams['name'].capitalize()
def ticker(self):
def ticker(self) -> str:
ticker = chainparams[self.coin_type()]['ticker']
if self._network == 'testnet':
ticker = 't' + ticker
@ -405,6 +405,9 @@ class CoinInterface:
def chainparams_network(self):
return chainparams[self.coin_type()][self._network]
def has_segwit(self) -> bool:
return chainparams[self.coin_type()].get('has_segwit', True)
def is_transient_error(self, ex):
if isinstance(ex, TemporaryError):
return True

192
basicswap/interface/btc.py

@ -6,7 +6,6 @@
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import json
import time
import base64
import hashlib
import logging
@ -16,7 +15,6 @@ from io import BytesIO
from basicswap.contrib.test_framework import segwit_addr
from basicswap.util import (
dumpj,
ensure,
make_int,
b2h, i2b, b2i, i2h)
@ -55,7 +53,8 @@ from basicswap.contrib.test_framework.messages import (
CTxIn,
CTxInWitness,
CTxOut,
FromHex)
FromHex,
uint256_from_str)
from basicswap.contrib.test_framework.script import (
CScript, CScriptOp,
@ -86,14 +85,14 @@ def ensure_op(v, err_string='Bad opcode'):
ensure(v, err_string)
def findOutput(tx, script_pk):
def findOutput(tx, script_pk: bytes):
for i in range(len(tx.vout)):
if tx.vout[i].scriptPubKey == script_pk:
return i
return None
def find_vout_for_address_from_txobj(tx_obj, addr):
def find_vout_for_address_from_txobj(tx_obj, addr) -> int:
"""
Locate the vout index of the given transaction sending to the
given address. Raises runtime error exception if not found.
@ -139,7 +138,7 @@ class BTCInterface(CoinInterface):
return 2
@staticmethod
def getTxOutputValue(tx):
def getTxOutputValue(tx) -> int:
rv = 0
for output in tx.vout:
rv += output.nValue
@ -158,7 +157,7 @@ class BTCInterface(CoinInterface):
return CTxOut
@staticmethod
def getExpectedSequence(lockType, lockVal):
def getExpectedSequence(lockType: int, lockVal: int) -> int:
assert (lockVal >= 1), 'Bad lockVal'
if lockType == TxLockTypes.SEQUENCE_LOCK_BLOCKS:
return lockVal
@ -172,12 +171,16 @@ class BTCInterface(CoinInterface):
raise ValueError('Unknown lock type')
@staticmethod
def decodeSequence(lock_value):
def decodeSequence(lock_value: int) -> int:
# Return the raw value
if lock_value & SEQUENCE_LOCKTIME_TYPE_FLAG:
return (lock_value & SEQUENCE_LOCKTIME_MASK) << SEQUENCE_LOCKTIME_GRANULARITY
return lock_value & SEQUENCE_LOCKTIME_MASK
@staticmethod
def depth_spendable() -> int:
return 0
def __init__(self, coin_settings, network, swap_client=None):
super().__init__(network)
self._rpc_host = coin_settings.get('rpchost', '127.0.0.1')
@ -192,7 +195,8 @@ class BTCInterface(CoinInterface):
self._log = self._sc.log if self._sc and self._sc.log else logging
self._expect_seedid_hex = None
def using_segwit(self):
def using_segwit(self) -> bool:
# Using btc native segwit
return self._use_segwit
def get_connection_type(self):
@ -217,15 +221,12 @@ class BTCInterface(CoinInterface):
def close_rpc(self, rpc_conn):
rpc_conn.close()
def setConfTarget(self, new_conf_target):
def setConfTarget(self, new_conf_target: int) -> None:
ensure(new_conf_target >= 1 and new_conf_target < 33, 'Invalid conf_target value')
self._conf_target = new_conf_target
def testDaemonRPC(self, with_wallet=True):
if with_wallet:
self.rpc_callback('getwalletinfo', [])
else:
self.rpc_callback('getblockchaininfo', [])
def testDaemonRPC(self, with_wallet=True) -> None:
self.rpc_callback('getwalletinfo' if with_wallet else 'getblockchaininfo')
def getDaemonVersion(self):
return self.rpc_callback('getnetworkinfo')['version']
@ -233,7 +234,7 @@ class BTCInterface(CoinInterface):
def getBlockchainInfo(self):
return self.rpc_callback('getblockchaininfo')
def getChainHeight(self):
def getChainHeight(self) -> int:
return self.rpc_callback('getblockcount')
def getMempoolTx(self, txid):
@ -259,7 +260,7 @@ class BTCInterface(CoinInterface):
last_block_header = prev_block_header
raise ValueError(f'Block header not found at time: {time}')
def initialiseWallet(self, key_bytes):
def initialiseWallet(self, key_bytes: bytes):
key_wif = self.encodeKey(key_bytes)
self.rpc_callback('sethdseed', [True, key_wif])
@ -270,10 +271,10 @@ class BTCInterface(CoinInterface):
rv['locked'] = rv.get('unlocked_until', 1) <= 0
return rv
def walletRestoreHeight(self):
def walletRestoreHeight(self) -> int:
return self._restore_height
def getWalletRestoreHeight(self):
def getWalletRestoreHeight(self) -> int:
start_time = self.rpc_callback('getwalletinfo')['keypoololdest']
blockchaininfo = self.rpc_callback('getblockchaininfo')
@ -295,6 +296,7 @@ class BTCInterface(CoinInterface):
block_hash = block_header['previousblockhash']
finally:
self.close_rpc(rpc_conn)
raise ValueError('{} wallet restore height not found.'.format(self.coin_name()))
def getWalletSeedID(self) -> str:
return self.rpc_callback('getwalletinfo')['hdseedid']
@ -309,9 +311,11 @@ class BTCInterface(CoinInterface):
args.append('bech32')
return self.rpc_callback('getnewaddress', args)
def isAddressMine(self, address: str) -> bool:
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
addr_info = self.rpc_callback('getaddressinfo', [address])
return addr_info['ismine']
if not or_watch_only:
return addr_info['ismine']
return addr_info['ismine'] or addr_info['iswatchonly']
def checkAddressMine(self, address: str) -> None:
addr_info = self.rpc_callback('getaddressinfo', [address])
@ -331,22 +335,22 @@ class BTCInterface(CoinInterface):
except Exception:
return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee'
def isSegwitAddress(self, address):
def isSegwitAddress(self, address: str) -> bool:
return address.startswith(self.chainparams_network()['hrp'] + '1')
def decodeAddress(self, address):
def decodeAddress(self, address: str) -> bytes:
bech32_prefix = self.chainparams_network()['hrp']
if len(bech32_prefix) > 0 and address.startswith(bech32_prefix + '1'):
return bytes(segwit_addr.decode(bech32_prefix, address)[1])
return decodeAddress(address)[1:]
def pubkey_to_segwit_address(self, pk):
def pubkey_to_segwit_address(self, pk: bytes) -> str:
bech32_prefix = self.chainparams_network()['hrp']
version = 0
pkh = hash160(pk)
return segwit_addr.encode(bech32_prefix, version, pkh)
def pkh_to_address(self, pkh):
def pkh_to_address(self, pkh: bytes) -> str:
# pkh is hash160(pk)
assert (len(pkh) == 20)
prefix = self.chainparams_network()['pubkey_address']
@ -354,26 +358,26 @@ class BTCInterface(CoinInterface):
checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest()
return b58encode(data + checksum[0:4])
def sh_to_address(self, sh):
def sh_to_address(self, sh: bytes) -> str:
assert (len(sh) == 20)
prefix = self.chainparams_network()['script_address']
data = bytes((prefix,)) + sh
checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest()
return b58encode(data + checksum[0:4])
def encode_p2wsh(self, script):
def encode_p2wsh(self, script: bytes) -> str:
bech32_prefix = self.chainparams_network()['hrp']
version = 0
program = script[2:] # strip version and length
return segwit_addr.encode(bech32_prefix, version, program)
def encodeScriptDest(self, script):
def encodeScriptDest(self, script: bytes) -> str:
return self.encode_p2wsh(script)
def encode_p2sh(self, script):
def encode_p2sh(self, script: bytes) -> str:
return pubkeyToAddress(self.chainparams_network()['script_address'], script)
def pubkey_to_address(self, pk):
def pubkey_to_address(self, pk: bytes) -> str:
assert (len(pk) == 33)
return self.pkh_to_address(hash160(pk))
@ -383,31 +387,31 @@ class BTCInterface(CoinInterface):
def getPubkey(self, privkey):
return PublicKey.from_secret(privkey).format()
def getAddressHashFromKey(self, key):
def getAddressHashFromKey(self, key) -> bytes:
pk = self.getPubkey(key)
return hash160(pk)
def getSeedHash(self, seed):
return self.getAddressHashFromKey(seed)[::-1]
def verifyKey(self, k):
def verifyKey(self, k: bytes) -> bool:
i = b2i(k)
return (i < ep.o and i > 0)
def verifyPubkey(self, pubkey_bytes):
def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
return verify_secp256k1_point(pubkey_bytes)
def encodeKey(self, key_bytes):
def encodeKey(self, key_bytes: bytes) -> str:
wif_prefix = self.chainparams_network()['key_prefix']
return toWIF(wif_prefix, key_bytes)
def encodePubkey(self, pk):
def encodePubkey(self, pk: bytes) -> bytes:
return pointToCPK(pk)
def encodeSegwitAddress(self, key_hash):
def encodeSegwitAddress(self, key_hash: bytes) -> str:
return segwit_addr.encode(self.chainparams_network()['hrp'], 0, key_hash)
def decodeSegwitAddress(self, addr):
def decodeSegwitAddress(self, addr: str) -> bytes:
return bytes(segwit_addr.decode(self.chainparams_network()['hrp'], addr)[1])
def decodePubkey(self, pke):
@ -423,10 +427,10 @@ class BTCInterface(CoinInterface):
def sumPubkeys(self, Ka, Kb):
return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format()
def getScriptForPubkeyHash(self, pkh):
def getScriptForPubkeyHash(self, pkh: bytes) -> CScript:
return CScript([OP_0, pkh])
def extractScriptLockScriptValues(self, script_bytes):
def extractScriptLockScriptValues(self, script_bytes: bytes):
script_len = len(script_bytes)
ensure(script_len == 71, 'Bad script length')
o = 0
@ -444,7 +448,7 @@ class BTCInterface(CoinInterface):
return pk1, pk2
def createSCLockTx(self, value: int, script: bytearray, vkbv=None) -> bytes:
def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes:
tx = CTransaction()
tx.nVersion = self.txVersion()
tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
@ -898,23 +902,28 @@ class BTCInterface(CoinInterface):
inputs.append({'txid': i2h(pi.prevout.hash), 'vout': pi.prevout.n})
self.rpc_callback('lockunspent', [True, inputs])
def signTxWithWallet(self, tx):
def signTxWithWallet(self, tx: bytes) -> bytes:
rv = self.rpc_callback('signrawtransactionwithwallet', [tx.hex()])
return bytes.fromhex(rv['hex'])
def publishTx(self, tx):
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
key_wif = self.encodeKey(key)
rv = self.rpc_callback('signrawtransactionwithkey', [tx.hex(), [key_wif, ]])
return bytes.fromhex(rv['hex'])
def publishTx(self, tx: bytes):
return self.rpc_callback('sendrawtransaction', [tx.hex()])
def encodeTx(self, tx):
def encodeTx(self, tx) -> bytes:
return tx.serialize()
def loadTx(self, tx_bytes) -> CTransaction:
def loadTx(self, tx_bytes: bytes) -> CTransaction:
# Load tx from bytes to internal representation
tx = CTransaction()
tx.deserialize(BytesIO(tx_bytes))
return tx
def getTxid(self, tx):
def getTxid(self, tx) -> bytes:
if isinstance(tx, str):
tx = bytes.fromhex(tx)
if isinstance(tx, bytes):
@ -928,16 +937,16 @@ class BTCInterface(CoinInterface):
script_pk = self.getScriptDest(script)
return findOutput(tx, script_pk)
def getPubkeyHash(self, K):
return hash160(self.encodePubkey(K))
def getPubkeyHash(self, K: bytes) -> bytes:
return hash160(K)
def getScriptDest(self, script):
return CScript([OP_0, hashlib.sha256(script).digest()])
def getScriptScriptSig(self, script):
def getScriptScriptSig(self, script: bytes) -> bytes:
return bytes()
def getPkDest(self, K):
def getPkDest(self, K: bytes) -> bytearray:
return self.getScriptForPubkeyHash(self.getPubkeyHash(K))
def scanTxOutset(self, dest):
@ -980,26 +989,26 @@ class BTCInterface(CoinInterface):
def createBLockTx(self, Kbs, output_amount):
tx = CTransaction()
tx.nVersion = self.txVersion()
p2wpkh = self.getPkDest(Kbs)
tx.vout.append(self.txoType()(output_amount, p2wpkh))
p2wpkh_script_pk = self.getPkDest(Kbs)
tx.vout.append(self.txoType()(output_amount, p2wpkh_script_pk))
return tx.serialize()
def encodeSharedAddress(self, Kbv, Kbs):
return self.pubkey_to_segwit_address(Kbs)
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate, delay_for=10, unlock_time=0):
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate, delay_for: int = 10, unlock_time: int = 0) -> bytes:
b_lock_tx = self.createBLockTx(Kbs, output_amount)
b_lock_tx = self.fundTx(b_lock_tx, feerate)
b_lock_tx_id = self.getTxid(b_lock_tx)
b_lock_tx = self.signTxWithWallet(b_lock_tx)
return self.publishTx(b_lock_tx)
return bytes.fromhex(self.publishTx(b_lock_tx))
def recoverEncKey(self, esig, sig, K):
return ecdsaotves_rec_enc_key(K, esig, sig[:-1]) # Strip sighash type
def getTxVSize(self, tx, add_bytes=0, add_witness_bytes=0):
def getTxVSize(self, tx, add_bytes: int = 0, add_witness_bytes: int = 0) -> int:
wsf = self.witnessScaleFactor()
len_full = len(tx.serialize_with_witness()) + add_bytes + add_witness_bytes
len_nwit = len(tx.serialize_without_witness()) + add_bytes
@ -1007,10 +1016,13 @@ class BTCInterface(CoinInterface):
return (weight + wsf - 1) // wsf
def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height, bid_sender):
dest_address = self.pubkey_to_segwit_address(Kbs) if self.using_segwit() else self.pubkey_to_address(Kbs)
return self.getLockTxHeight(None, dest_address, cb_swap_value, restore_height)
'''
raw_dest = self.getPkDest(Kbs)
rv = self.scanTxOutset(raw_dest)
print('scanTxOutset', dumpj(rv))
for utxo in rv['unspents']:
if 'height' in utxo and utxo['height'] > 0 and rv['height'] - utxo['height'] > cb_block_confirmed:
@ -1019,31 +1031,45 @@ class BTCInterface(CoinInterface):
else:
return {'txid': utxo['txid'], 'vout': utxo['vout'], 'amount': utxo['amount'], 'height': utxo['height']}
return None
'''
def waitForLockTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed):
raw_dest = self.getPkDest(Kbs)
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:
wtx = self.rpc_callback('gettransaction', [chain_b_lock_txid.hex(), ])
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
for i in range(20):
time.sleep(1)
rv = self.scanTxOutset(raw_dest)
print('scanTxOutset', dumpj(rv))
Kbs = self.getPubkey(kbs)
script_pk = self.getPkDest(Kbs)
locked_n = findOutput(lock_tx, script_pk)
ensure(locked_n is not None, 'Output not found in tx')
pkh_to = self.decodeAddress(address_to)
for utxo in rv['unspents']:
if 'height' in utxo and utxo['height'] > 0 and rv['height'] - utxo['height'] > cb_block_confirmed:
tx = CTransaction()
tx.nVersion = self.txVersion()
if self.make_int(utxo['amount']) != cb_swap_value:
self._log.warning('Found output to lock tx pubkey of incorrect value: %s', str(utxo['amount']))
else:
return True
return False
script_lock = self.getScriptForPubkeyHash(Kbs)
chain_b_lock_txid_int = uint256_from_str(chain_b_lock_txid[::-1])
def spendBLockTx(self, chain_b_lock_txid, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height):
raise ValueError('TODO')
tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n),
nSequence=0,
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)
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)
def importWatchOnlyAddress(self, address, label):
b_lock_spend_tx = tx.serialize()
b_lock_spend_tx = self.signTxWithKey(b_lock_spend_tx, kbs)
return bytes.fromhex(self.publishTx(b_lock_spend_tx))
def importWatchOnlyAddress(self, address: str, label: str):
self.rpc_callback('importaddress', [address, label, False])
def isWatchOnlyAddress(self, address):
def isWatchOnlyAddress(self, address: str):
addr_info = self.rpc_callback('getaddressinfo', [address])
return addr_info['iswatchonly']
@ -1051,10 +1077,10 @@ class BTCInterface(CoinInterface):
lock_tx_dest = self.getScriptDest(lock_script)
return self.encodeScriptDest(lock_tx_dest)
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index=False):
def getLockTxHeight(self, txid, dest_address, bid_amount, rescan_from, find_index: bool = False):
# Add watchonly address and rescan if required
if not self.isWatchOnlyAddress(dest_address):
if not self.isAddressMine(dest_address, or_watch_only=True):
self.importWatchOnlyAddress(dest_address, 'bid')
self._log.info('Imported watch-only addr: {}'.format(dest_address))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))
@ -1263,7 +1289,6 @@ class BTCInterface(CoinInterface):
sign_for_addr = None
for addr, value in unspent_addr.items():
print('[rm]', value, amount_for)
if value >= amount_for:
sign_for_addr = addr
break
@ -1272,7 +1297,7 @@ class BTCInterface(CoinInterface):
self._log.debug('sign_for_addr %s', sign_for_addr)
if self._use_segwit: # TODO: Use isSegwitAddress when scantxoutset can use combo
if self.using_segwit(): # TODO: Use isSegwitAddress when scantxoutset can use combo
# 'Address does not refer to key' for non p2pkh
pkh = self.decodeAddress(sign_for_addr)
sign_for_addr = self.pkh_to_address(pkh)
@ -1286,7 +1311,7 @@ class BTCInterface(CoinInterface):
passed = self.verifyMessage(address, address + '_swap_proof_' + extra_commit_bytes.hex(), signature)
ensure(passed is True, 'Proof of funds signature invalid')
if self._use_segwit:
if self.using_segwit():
address = self.encodeSegwitAddress(decodeAddress(address)[1:])
return self.getUTXOBalance(address)
@ -1334,6 +1359,19 @@ class BTCInterface(CoinInterface):
def get_p2wsh_script_pubkey(self, script: bytearray) -> bytearray:
return CScript([OP_0, hashlib.sha256(script).digest()])
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:
return {'txid': txid_hex, 'amount': 0, 'height': rv['blockheight']}
return None
def testBTCInterface():
print('TODO: testBTCInterface')

10
basicswap/interface/firo.py

@ -55,15 +55,17 @@ class FIROInterface(BTCInterface):
addr_info = self.rpc_callback('validateaddress', [address])
return addr_info['iswatchonly']
def isAddressMine(self, address):
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
addr_info = self.rpc_callback('validateaddress', [address])
return addr_info['ismine']
if not or_watch_only:
return addr_info['ismine']
return addr_info['ismine'] or addr_info['iswatchonly']
def getSCLockScriptAddress(self, lock_script):
lock_tx_dest = self.getScriptDest(lock_script)
address = self.encodeScriptDest(lock_tx_dest)
if not self.isWatchOnlyAddress(address):
if not self.isAddressMine(address, or_watch_only=True):
# Expects P2WSH nested in BIP16_P2SH
ro = self.rpc_callback('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
addr_info = self.rpc_callback('validateaddress', [address])
@ -74,7 +76,7 @@ class FIROInterface(BTCInterface):
# Add watchonly address and rescan if required
lock_tx_dest = self.getScriptDest(lock_script)
dest_address = self.encodeScriptDest(lock_tx_dest)
if not self.isWatchOnlyAddress(dest_address):
if not self.isAddressMine(dest_address, or_watch_only=True):
self.rpc_callback('importaddress', [lock_tx_dest.hex(), 'bid lock', False, True])
self._log.info('Imported watch-only addr: {}'.format(dest_address))
self._log.info('Rescanning {} chain from height: {}'.format(self.coin_name(), rescan_from))

2
basicswap/interface/part.py

@ -638,7 +638,7 @@ class PARTInterfaceAnon(PARTInterface):
def coin_name(self):
return super().coin_name() + ' Anon'
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate, delay_for=10, unlock_time=0):
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate, delay_for: int = 10, unlock_time: int = 0) -> bytes:
sx_addr = self.formatStealthAddress(Kbv, Kbs)
self._log.debug('sx_addr: {}'.format(sx_addr))

63
basicswap/interface/xmr.py

@ -260,7 +260,7 @@ class XMRInterface(CoinInterface):
def encodeSharedAddress(self, Kbv, Kbs):
return xmr_util.encode_address(Kbv, Kbs)
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate, delay_for=10, unlock_time=0):
def publishBLockTx(self, Kbv, Kbs, output_amount, feerate, delay_for: int = 10, unlock_time: int = 0) -> bytes:
with self._mx_wallet:
self.openWallet(self._wallet_filename)
@ -339,67 +339,6 @@ class XMRInterface(CoinInterface):
rv = -1
return rv
def waitForLockTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height):
with self._mx_wallet:
Kbv_enc = self.encodePubkey(self.pubkey(kbv))
address_b58 = xmr_util.encode_address(Kbv_enc, self.encodePubkey(Kbs))
try:
self.rpc_wallet_cb('close_wallet')
except Exception as e:
self._log.warning('close_wallet failed %s', str(e))
params = {
'filename': address_b58,
'address': address_b58,
'viewkey': b2h(kbv[::-1]),
'restore_height': restore_height,
}
self.createWallet(params)
self.openWallet(address_b58)
# For a while after opening the wallet rpc cmds return empty data
num_tries = 40
for i in range(num_tries + 1):
try:
current_height = self.rpc_cb2('get_height')['height']
print('current_height', current_height)
except Exception as e:
self._log.warning('rpc_cb failed %s', str(e))
current_height = None # If the transfer is available it will be deep enough
# TODO: Make accepting current_height == None a user selectable option
# Or look for all transfers and check height
params = {'transfer_type': 'available'}
rv = self.rpc_wallet_cb('incoming_transfers', params)
print('rv', rv)
if 'transfers' in rv:
for transfer in rv['transfers']:
if transfer['amount'] == cb_swap_value \
and (current_height is None or current_height - transfer['block_height'] > cb_block_confirmed):
return True
# TODO: Is it necessary to check the address?
'''
rv = self.rpc_wallet_cb('get_balance')
print('get_balance', rv)
if 'per_subaddress' in rv:
for sub_addr in rv['per_subaddress']:
if sub_addr['address'] == address_b58:
'''
if i >= num_tries:
raise ValueError('Balance not confirming on node')
self._sc.delay_event.wait(1.0)
if self._sc.delay_event.is_set():
raise ValueError('Stopped')
return False
def findTxnByHash(self, txid):
with self._mx_wallet:
self.openWallet(self._wallet_filename)

255
tests/basicswap/test_btc_xmr.py

@ -28,6 +28,8 @@ from tests.basicswap.util import (
from tests.basicswap.common import (
wait_for_bid,
wait_for_offer,
wait_for_balance,
wait_for_unspent,
wait_for_none_active,
BTC_BASE_RPC_PORT,
)
@ -52,8 +54,8 @@ logger = logging.getLogger()
class BasicSwapTest(BaseTest):
base_rpc_port = None
def getBalance(self, js_wallets):
return float(js_wallets[self.test_coin_from.name]['balance']) + float(js_wallets[self.test_coin_from.name]['unconfirmed'])
def getBalance(self, js_wallets, coin):
return float(js_wallets[coin.name]['balance']) + float(js_wallets[coin.name]['unconfirmed'])
def callnoderpc(self, method, params=[], wallet=None, node_id=0):
return callnoderpc(node_id, method, params, wallet, self.base_rpc_port)
@ -252,22 +254,25 @@ class BasicSwapTest(BaseTest):
self.callnoderpc('unloadwallet', [new_wallet_name])
assert (addr == 'bcrt1qps7hnjd866e9ynxadgseprkc2l56m00dvwargr')
def test_01_full_swap(self):
logging.info('---------- Test {} to XMR'.format(self.test_coin_from.name))
def do_test_01_full_swap(self, coin_from, coin_to):
logging.info('---------- Test {} to {}'.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)
js_0 = read_json_api(1800, 'wallets')
node0_from_before = self.getBalance(js_0)
node0_from_before = self.getBalance(js_0, coin_from)
js_1 = read_json_api(1801, 'wallets')
node1_from_before = self.getBalance(js_1)
node1_from_before = self.getBalance(js_1, coin_from)
js_0_xmr = read_json_api(1800, 'wallets/xmr')
js_1_xmr = read_json_api(1801, 'wallets/xmr')
js_0_to = read_json_api(1800, 'wallets/{}'.format(coin_to.name.lower()))
js_1_to = read_json_api(1801, 'wallets/{}'.format(coin_to.name.lower()))
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP)
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)
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
offers = swap_clients[0].listOffers(filters={'offer_id': offer_id})
offer = offers[0]
@ -281,37 +286,55 @@ class BasicSwapTest(BaseTest):
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=180)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True)
amount_from = float(format_amount(amt_swap, 8))
amount_from = float(ci_from.format_amount(amt_swap))
js_1 = read_json_api(1801, 'wallets')
node1_from_after = self.getBalance(js_1)
assert (node1_from_after > node1_from_before + (amount_from - 0.05))
node1_from_after = self.getBalance(js_1, coin_from)
if coin_from is not Coins.PART: # TODO: staking
assert (node1_from_after > node1_from_before + (amount_from - 0.05))
js_0 = read_json_api(1800, 'wallets')
node0_from_after = self.getBalance(js_0)
node0_from_after = self.getBalance(js_0, coin_from)
# TODO: Discard block rewards
# assert (node0_from_after < node0_from_before - amount_from)
js_0_xmr_after = read_json_api(1800, 'wallets/xmr')
js_1_xmr_after = read_json_api(1801, 'wallets/xmr')
js_0_to_after = read_json_api(1800, 'wallets/{}'.format(coin_to.name.lower()))
js_1_to_after = read_json_api(1801, 'wallets/{}'.format(coin_to.name.lower()))
scale_from = 8
amount_to = int((amt_swap * rate_swap) // (10 ** scale_from))
amount_to_float = float(format_amount(amount_to, 12))
node1_xmr_after = float(js_1_xmr_after['unconfirmed']) + float(js_1_xmr_after['balance'])
node1_xmr_before = float(js_1_xmr['unconfirmed']) + float(js_1_xmr['balance'])
assert (node1_xmr_after > node1_xmr_before + (amount_to_float - 0.02))
amount_to_float = float(ci_to.format_amount(amount_to))
node1_to_after = float(js_1_to_after['unconfirmed']) + float(js_1_to_after['balance'])
node1_to_before = float(js_1_to['unconfirmed']) + float(js_1_to['balance'])
if False: # TODO: set stakeaddress and xmr rewards to non wallet addresses
assert (node1_to_after < node1_to_before - amount_to_float)
def test_01_full_swap(self):
if not self.has_segwit:
return
self.do_test_01_full_swap(self.test_coin_from, Coins.XMR)
def test_01_full_swap_to_part(self):
if not self.has_segwit:
return
self.do_test_01_full_swap(self.test_coin_from, Coins.PART)
def test_01_full_swap_from_part(self):
self.do_test_01_full_swap(Coins.PART, self.test_coin_from)
def do_test_02_leader_recover_a_lock_tx(self, coin_from, coin_to):
logging.info('---------- Test {} to {} leader recovers coin a lock tx'.format(coin_from.name, coin_to.name))
def test_02_leader_recover_a_lock_tx(self):
logging.info('---------- Test {} to XMR leader recovers coin a lock tx'.format(self.test_coin_from.name))
swap_clients = self.swap_clients
ci_from = swap_clients[0].ci(coin_from)
ci_to = swap_clients[0].ci(coin_to)
js_w0_before = read_json_api(1800, 'wallets')
node0_from_before = self.getBalance(js_w0_before)
node0_from_before = self.getBalance(js_w0_before, coin_from)
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
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(
self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
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)
@ -331,28 +354,43 @@ class BasicSwapTest(BaseTest):
wait_for_bid(test_delay_event, swap_clients[1], bid_id, [BidStates.BID_STALLED_FOR_TEST, BidStates.XMR_SWAP_FAILED], sent=True)
js_w0_after = read_json_api(1800, 'wallets')
node0_from_after = self.getBalance(js_w0_after)
node0_from_after = self.getBalance(js_w0_after, coin_from)
# TODO: Discard block rewards
# assert (node0_from_before - node0_from_after < 0.02)
def test_03_follower_recover_a_lock_tx(self):
logging.info('---------- Test {} to XMR follower recovers coin a lock tx'.format(self.test_coin_from.name))
def test_02_leader_recover_a_lock_tx(self):
if not self.has_segwit:
return
self.do_test_02_leader_recover_a_lock_tx(self.test_coin_from, Coins.XMR)
def test_02_leader_recover_a_lock_tx_to_part(self):
if not self.has_segwit:
return
self.do_test_02_leader_recover_a_lock_tx(self.test_coin_from, Coins.PART)
def test_02_leader_recover_a_lock_tx_from_part(self):
self.do_test_02_leader_recover_a_lock_tx(Coins.PART, self.test_coin_from)
def do_test_03_follower_recover_a_lock_tx(self, coin_from, coin_to):
logging.info('---------- Test {} to {} follower recovers coin a 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[0].ci(coin_to)
js_w0_before = read_json_api(1800, 'wallets')
js_w1_before = read_json_api(1801, 'wallets')
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
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(
self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
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)
@ -368,8 +406,8 @@ class BasicSwapTest(BaseTest):
js_w1_after = read_json_api(1801, 'wallets')
node1_from_before = self.getBalance(js_w1_before)
node1_from_after = self.getBalance(js_w1_after)
node1_from_before = self.getBalance(js_w1_before, coin_from)
node1_from_after = self.getBalance(js_w1_after, coin_from)
amount_from = float(format_amount(amt_swap, 8))
# TODO: Discard block rewards
# assert (node1_from_after - node1_from_before > (amount_from - 0.02))
@ -379,24 +417,38 @@ class BasicSwapTest(BaseTest):
wait_for_none_active(test_delay_event, 1800)
wait_for_none_active(test_delay_event, 1801)
def test_04_follower_recover_b_lock_tx(self):
logging.info('---------- Test {} to XMR follower recovers coin b lock tx'.format(self.test_coin_from.name))
def test_03_follower_recover_a_lock_tx(self):
if not self.has_segwit:
return
self.do_test_03_follower_recover_a_lock_tx(self.test_coin_from, Coins.XMR)
def test_03_follower_recover_a_lock_tx_to_part(self):
if not self.has_segwit:
return
self.do_test_03_follower_recover_a_lock_tx(self.test_coin_from, Coins.PART)
def test_03_follower_recover_a_lock_tx_from_part(self):
self.do_test_03_follower_recover_a_lock_tx(Coins.PART, self.test_coin_from)
def do_test_04_follower_recover_b_lock_tx(self, coin_from, coin_to):
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[0].ci(coin_to)
js_w0_before = read_json_api(1800, 'wallets')
js_w1_before = read_json_api(1801, 'wallets')
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
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(
self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
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)
@ -411,28 +463,127 @@ class BasicSwapTest(BaseTest):
js_w0_after = read_json_api(1800, 'wallets')
js_w1_after = read_json_api(1801, 'wallets')
node0_from_before = self.getBalance(js_w0_before)
node0_from_after = self.getBalance(js_w0_after)
node0_from_before = self.getBalance(js_w0_before, coin_from)
node0_from_after = self.getBalance(js_w0_after, coin_from)
logging.info('End coin_from balance {}, diff {}'.format(node0_from_after, node0_from_after - node0_from_before))
# TODO: Discard block rewards
# assert (node0_from_before - node0_from_after < 0.02)
node1_xmr_before = self.getXmrBalance(js_w1_before)
node1_xmr_after = self.getXmrBalance(js_w1_after)
assert (node1_xmr_before - node1_xmr_after < 0.02)
node1_coin_to_before = self.getBalance(js_w1_before, coin_to)
node1_coin_to_after = self.getBalance(js_w1_after, coin_to)
logging.info('End coin_to balance {}, diff {}'.format(node1_coin_to_after, node1_coin_to_after - node1_coin_to_before))
assert (node1_coin_to_before - node1_coin_to_after < 0.02)
def test_04_follower_recover_b_lock_tx(self):
if not self.has_segwit:
return
self.do_test_04_follower_recover_b_lock_tx(self.test_coin_from, Coins.XMR)
def test_04_follower_recover_b_lock_tx_to_part(self):
if not self.has_segwit:
return
self.do_test_04_follower_recover_b_lock_tx(self.test_coin_from, Coins.PART)
def test_04_follower_recover_b_lock_tx_from_part(self):
self.do_test_04_follower_recover_b_lock_tx(Coins.PART, self.test_coin_from)
def do_test_05_self_bid(self, coin_from, coin_to):
logging.info('---------- Test {} to {} same client'.format(coin_from.name, coin_to.name))
def test_05_self_bid(self):
logging.info('---------- Test {} to XMR same client'.format(self.test_coin_from.name))
swap_clients = self.swap_clients
ci_to = swap_clients[0].ci(coin_to)
amt_swap = make_int(random.uniform(0.1, 2.0), scale=8, r=1)
rate_swap = make_int(random.uniform(0.2, 20.0), scale=12, r=1)
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
offer_id = swap_clients[1].postOffer(self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP, auto_accept_bids=True)
offer_id = swap_clients[1].postOffer(coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP, auto_accept_bids=True)
bid_id = swap_clients[1].postXmrBid(offer_id, amt_swap)
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=180)
def test_05_self_bid(self):
if not self.has_segwit:
return
self.do_test_05_self_bid(self.test_coin_from, Coins.XMR)
def test_05_self_bid_to_part(self):
if not self.has_segwit:
return
self.do_test_05_self_bid(self.test_coin_from, Coins.PART)
def test_05_self_bid_from_part(self):
self.do_test_05_self_bid(Coins.PART, self.test_coin_from)
def test_06_preselect_inputs(self):
tla_from = self.test_coin_from.name
logging.info('---------- Test {} Preselected inputs'.format(tla_from))
swap_clients = self.swap_clients
# 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(test_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': float(js_w2[tla_from]['balance']),
'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', 10.0)
assert (len(json_rv['txid']) == 64)
# Create prefunded ITX
ci = swap_clients[2].ci(self.test_coin_from)
ci_to = swap_clients[2].ci(Coins.XMR)
pi = swap_clients[2].pi(SwapTypes.XMR_SWAP)
js_w2 = read_json_api(1802, 'wallets')
swap_value = ci.make_int(js_w2[tla_from]['balance'])
assert (swap_value > ci.make_int(95))
itx = pi.getFundedInitiateTxTemplate(ci, swap_value, True)
itx_decoded = ci.describeTx(itx.hex())
value_after_subfee = ci.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, swap_value)
extra_options = {'prefunded_itx': itx}
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0))
offer_id = swap_clients[2].postOffer(self.test_coin_from, Coins.XMR, swap_value, rate_swap, swap_value, SwapTypes.XMR_SWAP, 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, _, _, _, _ = swap_clients[2].getXmrBidAndOffer(bid_id)
assert (bid.xmr_a_lock_tx)
wtx = ci.rpc_callback('gettransaction', [bid.xmr_a_lock_tx.txid.hex(),])
itx_after = ci.describeTx(wtx['hex'])
assert (len(itx_after['vin']) == len(itx_decoded['vin']))
for i, txin in enumerate(itx_decoded['vin']):
txin_after = itx_after['vin'][i]
assert (txin['txid'] == txin_after['txid'])
assert (txin['vout'] == txin_after['vout'])
class TestBTC(BasicSwapTest):
__test__ = True

1
tests/basicswap/test_xmr.py

@ -318,6 +318,7 @@ class BaseTest(unittest.TestCase):
start_ltc_nodes = False
start_xmr_nodes = True
has_segwit = True
xmr_addr = None
btc_addr = None

Loading…
Cancel
Save