From ca264db0d0705938add0ea80dbffd21d1b7c73a5 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Mon, 7 Nov 2022 22:31:10 +0200 Subject: [PATCH 1/7] Add non-segwit Firo support. Rework tests to combine atomic and xmr test cases. Modify btc interface to support P2WSH_nested_in_BIP16_P2SH Add coin feature tests to test_btc_xmr.py --- basicswap/__init__.py | 2 +- basicswap/base.py | 7 - basicswap/basicswap.py | 116 ++--- basicswap/basicswap_util.py | 2 +- basicswap/chainparams.py | 40 ++ basicswap/config.py | 5 + basicswap/interface/btc.py | 185 +++++-- basicswap/interface/firo.py | 172 +++++++ basicswap/interface/part.py | 44 +- basicswap/ui/page_wallet.py | 2 +- bin/basicswap_prepare.py | 148 ++++-- pgp/keys/firo_reuben.pgp | 13 + ...o_binaryfate.asc => monero_binaryfate.pgp} | 0 tests/basicswap/common.py | 4 + tests/basicswap/extended/test_dash.py | 8 +- tests/basicswap/extended/test_firo.py | 465 ++++++++++++++++++ tests/basicswap/extended/test_pivx.py | 8 +- .../basicswap/extended/test_xmr_persistent.py | 5 +- tests/basicswap/test_btc_xmr.py | 224 ++++++++- tests/basicswap/test_other.py | 2 +- tests/basicswap/test_xmr.py | 359 +++++++------- 21 files changed, 1400 insertions(+), 411 deletions(-) create mode 100644 basicswap/interface/firo.py create mode 100644 pgp/keys/firo_reuben.pgp rename pgp/keys/{monero_binaryfate.asc => monero_binaryfate.pgp} (100%) create mode 100644 tests/basicswap/extended/test_firo.py diff --git a/basicswap/__init__.py b/basicswap/__init__.py index daff959..f616645 100644 --- a/basicswap/__init__.py +++ b/basicswap/__init__.py @@ -1,3 +1,3 @@ name = "basicswap" -__version__ = "0.11.44" +__version__ = "0.11.45" diff --git a/basicswap/base.py b/basicswap/base.py index eee3dee..60df37f 100644 --- a/basicswap/base.py +++ b/basicswap/base.py @@ -15,7 +15,6 @@ import traceback import subprocess import basicswap.config as cfg -import basicswap.contrib.segwit_addr as segwit_addr from .rpc import ( callrpc, @@ -112,12 +111,6 @@ class BaseApp: return c raise ValueError('Unknown coin: {}'.format(coin_name)) - def encodeSegwit(self, coin_type, raw): - return segwit_addr.encode(chainparams[coin_type][self.chain]['hrp'], 0, raw) - - def decodeSegwit(self, coin_type, addr): - return bytes(segwit_addr.decode(chainparams[coin_type][self.chain]['hrp'], addr)[1]) - def callrpc(self, method, params=[], wallet=None): cc = self.coin_clients[Coins.PART] return callrpc(cc['rpcport'], cc['rpcauth'], method, params, wallet, cc['rpchost']) diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 99ce727..ac11c27 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -34,6 +34,7 @@ from .interface.nmc import NMCInterface from .interface.xmr import XMRInterface from .interface.pivx import PIVXInterface from .interface.dash import DASHInterface +from .interface.firo import FIROInterface from .interface.passthrough_btc import PassthroughBTCInterface from . import __version__ @@ -58,7 +59,6 @@ from .util.address import ( getKeyID, decodeWif, decodeAddress, - encodeAddress, pubkeyToAddress, ) from .chainparams import ( @@ -531,6 +531,8 @@ class BasicSwap(BaseApp): return PIVXInterface(self.coin_clients[coin], self.chain, self) elif coin == Coins.DASH: return DASHInterface(self.coin_clients[coin], self.chain, self) + elif coin == Coins.FIRO: + return FIROInterface(self.coin_clients[coin], self.chain, self) else: raise ValueError('Unknown coin type') @@ -549,7 +551,7 @@ class BasicSwap(BaseApp): authcookiepath = os.path.join(self.getChainDatadirPath(coin), '.cookie') pidfilename = cc['name'] - if cc['name'] in ('bitcoin', 'litecoin', 'namecoin', 'dash'): + if cc['name'] in ('bitcoin', 'litecoin', 'namecoin', 'dash', 'firo'): pidfilename += 'd' pidfilepath = os.path.join(self.getChainDatadirPath(coin), pidfilename + '.pid') @@ -975,10 +977,8 @@ class BasicSwap(BaseApp): raise ValueError('Invalid swap type for PART_ANON') if (coin_from == Coins.PART_BLIND or coin_to == Coins.PART_BLIND) and swap_type != SwapTypes.XMR_SWAP: raise ValueError('Invalid swap type for PART_BLIND') - if coin_from == Coins.PIVX and swap_type == SwapTypes.XMR_SWAP: - raise ValueError('TODO: PIVX -> XMR') - if coin_from == Coins.DASH and swap_type == SwapTypes.XMR_SWAP: - raise ValueError('TODO: DASH -> XMR') + if coin_from in (Coins.PIVX, Coins.DASH, Coins.FIRO, Coins.NMC) and swap_type == SwapTypes.XMR_SWAP: + raise ValueError('TODO: {} -> XMR'.format(coin_from.name)) def notify(self, event_type, event_data, session=None): @@ -1053,7 +1053,6 @@ class BasicSwap(BaseApp): 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): - coin_from_has_csv = self.coin_clients[coin_from]['use_csv'] coin_to_has_csv = self.coin_clients[coin_to]['use_csv'] @@ -1609,18 +1608,6 @@ class BasicSwap(BaseApp): self.mxDB.release() return self._contract_count - def getUnspentsByAddr(self, coin_type): - ci = self.ci(coin_type) - - unspent_addr = dict() - unspent = self.callcoinrpc(coin_type, 'listunspent') - for u in unspent: - if u['spendable'] is not True: - continue - unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + ci.make_int(u['amount'], r=1) - - return unspent_addr - def getProofOfFunds(self, coin_type, amount_for, extra_commit_bytes): ci = self.ci(coin_type) self.log.debug('getProofOfFunds %s %s', ci.coin_name(), ci.format_amount(amount_for)) @@ -1628,28 +1615,7 @@ class BasicSwap(BaseApp): if self.coin_clients[coin_type]['connection_type'] != 'rpc': return (None, None) - # TODO: Lock unspent and use same output/s to fund bid - unspent_addr = self.getUnspentsByAddr(coin_type) - - sign_for_addr = None - for addr, value in unspent_addr.items(): - if value >= amount_for: - sign_for_addr = addr - break - - ensure(sign_for_addr is not None, 'Could not find address with enough funds for proof') - - self.log.debug('sign_for_addr %s', sign_for_addr) - if self.coin_clients[coin_type]['use_segwit']: # TODO: Use isSegwitAddress when scantxoutset can use combo - # 'Address does not refer to key' for non p2pkh - addrinfo = self.callcoinrpc(coin_type, 'getaddressinfo', [sign_for_addr]) - pkh = addrinfo['scriptPubKey'][4:] - sign_for_addr = encodeAddress(bytes((chainparams[coin_type][self.chain]['pubkey_address'],)) + bytes.fromhex(pkh)) - self.log.debug('sign_for_addr converted %s', sign_for_addr) - - signature = self.callcoinrpc(coin_type, 'signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + extra_commit_bytes.hex()]) - - return (sign_for_addr, signature) + return ci.getProofOfFunds(amount_for, extra_commit_bytes) def saveBidInSession(self, bid_id, bid, session, xmr_swap=None, save_in_progress=None): session.add(bid) @@ -2293,16 +2259,16 @@ class BasicSwap(BaseApp): xmr_swap.kbsl_dleag = xmr_swap.pkbsl # MSG2F - xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script = ci_from.createScriptLockTx( + xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script = ci_from.createSCLockTx( bid.amount, xmr_swap.pkal, xmr_swap.pkaf, xmr_swap.vkbv ) - xmr_swap.a_lock_tx = ci_from.fundScriptLockTx(xmr_swap.a_lock_tx, xmr_offer.a_fee_rate, xmr_swap.vkbv) + xmr_swap.a_lock_tx = ci_from.fundSCLockTx(xmr_swap.a_lock_tx, xmr_offer.a_fee_rate, xmr_swap.vkbv) xmr_swap.a_lock_tx_id = ci_from.getTxid(xmr_swap.a_lock_tx) a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script) - xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value = ci_from.createScriptLockRefundTx( + xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value = ci_from.createSCLockRefundTx( xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script, xmr_swap.pkal, xmr_swap.pkaf, xmr_offer.lock_time_1, xmr_offer.lock_time_2, @@ -2316,7 +2282,7 @@ class BasicSwap(BaseApp): ensure(v, 'Invalid coin A lock refund tx leader sig') pkh_refund_to = ci_from.decodeAddress(self.getReceiveAddressForCoin(coin_from)) - xmr_swap.a_lock_refund_spend_tx = ci_from.createScriptLockRefundSpendTx( + xmr_swap.a_lock_refund_spend_tx = ci_from.createSCLockRefundSpendTx( xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script, pkh_refund_to, xmr_offer.a_fee_rate, xmr_swap.vkbv @@ -2326,7 +2292,7 @@ class BasicSwap(BaseApp): # Double check txns before sending self.log.debug('Bid: {} - Double checking chain A lock txns are valid before sending bid accept.'.format(bid_id.hex())) check_lock_tx_inputs = False # TODO: check_lock_tx_inputs without txindex - _, xmr_swap.a_lock_tx_vout = ci_from.verifyLockTx( + _, xmr_swap.a_lock_tx_vout = ci_from.verifySCLockTx( xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script, bid.amount, @@ -2336,7 +2302,7 @@ class BasicSwap(BaseApp): check_lock_tx_inputs, xmr_swap.vkbv) - _, _, lock_refund_vout = ci_from.verifyLockRefundTx( + _, _, lock_refund_vout = ci_from.verifySCLockRefundTx( xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_tx, xmr_swap.a_lock_refund_tx_script, @@ -2351,7 +2317,7 @@ class BasicSwap(BaseApp): xmr_offer.a_fee_rate, xmr_swap.vkbv) - ci_from.verifyLockRefundSpendTx( + ci_from.verifySCLockRefundSpendTx( xmr_swap.a_lock_refund_spend_tx, xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script, xmr_swap.pkal, @@ -2602,8 +2568,8 @@ class BasicSwap(BaseApp): assert (addr_redeem_out is not None) if self.coin_clients[coin_type]['use_segwit']: - # Change to btc hrp - addr_redeem_out = self.encodeSegwit(Coins.PART, self.decodeSegwit(coin_type, addr_redeem_out)) + # Change to part hrp + addr_redeem_out = self.ci(Coins.PART).encodeSegwitAddress(ci.decodeSegwitAddress(addr_redeem_out)) else: addr_redeem_out = replaceAddrPrefix(addr_redeem_out, Coins.PART, self.chain) self.log.debug('addr_redeem_out %s', addr_redeem_out) @@ -2704,8 +2670,8 @@ class BasicSwap(BaseApp): addr_refund_out = self.getReceiveAddressFromPool(coin_type, bid.bid_id, tx_type) ensure(addr_refund_out is not None, 'addr_refund_out is null') if self.coin_clients[coin_type]['use_segwit']: - # Change to btc hrp - addr_refund_out = self.encodeSegwit(Coins.PART, self.decodeSegwit(coin_type, addr_refund_out)) + # Change to part hrp + addr_refund_out = self.ci(Coins.PART).encodeSegwitAddress(ci.decodeSegwitAddress(addr_refund_out)) else: addr_refund_out = replaceAddrPrefix(addr_refund_out, Coins.PART, self.chain) self.log.debug('addr_refund_out %s', addr_refund_out) @@ -2861,7 +2827,6 @@ class BasicSwap(BaseApp): # TODO: random offset into explorers, try blocks for exp in explorers: - # TODO: ExplorerBitAps use only gettransaction if assert_txid is set rv = exp.lookupUnspentByAddress(address) @@ -3033,11 +2998,11 @@ class BasicSwap(BaseApp): # TODO: Timeout waiting for transactions bid_changed = False - a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script) - # Changed from ci_from.getOutput(bid.xmr_a_lock_tx.txid, a_lock_tx_dest, bid.amount, xmr_swap) - - p2wsh_addr = ci_from.encode_p2wsh(a_lock_tx_dest) - lock_tx_chain_info = ci_from.getLockTxHeight(bid.xmr_a_lock_tx.txid, p2wsh_addr, bid.amount, bid.chain_a_height_start) + if offer.coin_from == Coins.FIRO: + lock_tx_chain_info = ci_from.getLockTxHeightFiro(bid.xmr_a_lock_tx.txid, xmr_swap.a_lock_tx_script, bid.amount, bid.chain_a_height_start) + else: + a_lock_tx_addr = ci_from.getSCLockScriptAddress(xmr_swap.a_lock_tx_script) + lock_tx_chain_info = ci_from.getLockTxHeight(bid.xmr_a_lock_tx.txid, a_lock_tx_addr, bid.amount, bid.chain_a_height_start) if lock_tx_chain_info is None: return rv @@ -3149,9 +3114,11 @@ class BasicSwap(BaseApp): if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns: refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] if refund_tx.block_time is None: - a_lock_refund_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_refund_tx_script) - p2wsh_addr = ci_from.encode_p2wsh(a_lock_refund_tx_dest) - lock_refund_tx_chain_info = ci_from.getLockTxHeight(refund_tx.txid, p2wsh_addr, 0, bid.chain_a_height_start) + if offer.coin_from == Coins.FIRO: + lock_refund_tx_chain_info = ci_from.getLockTxHeightFiro(refund_tx.txid, xmr_swap.a_lock_refund_tx_script, 0, bid.chain_a_height_start) + else: + refund_tx_addr = ci_from.getSCLockScriptAddress(xmr_swap.a_lock_refund_tx_script) + lock_refund_tx_chain_info = ci_from.getLockTxHeight(refund_tx.txid, refund_tx_addr, 0, bid.chain_a_height_start) if lock_refund_tx_chain_info is not None and lock_refund_tx_chain_info.get('height', 0) > 0: block_header = ci_from.getBlockHeaderFromHeight(lock_refund_tx_chain_info['height']) @@ -4015,18 +3982,7 @@ class BasicSwap(BaseApp): if swap_type == SwapTypes.SELLER_FIRST: ensure(len(bid_data.pkhash_buyer) == 20, 'Bad pkhash_buyer length') - # Verify proof of funds - bid_proof_address = replaceAddrPrefix(bid_data.proof_address, Coins.PART, self.chain) - mm = chainparams[coin_to]['message_magic'] - passed = self.ci(Coins.PART).verifyMessage(bid_proof_address, bid_data.proof_address + '_swap_proof_' + offer_id.hex(), bid_data.proof_signature, mm) - ensure(passed is True, 'Proof of funds signature invalid') - - if self.coin_clients[coin_to]['use_segwit']: - addr_search = self.encodeSegwit(coin_to, decodeAddress(bid_data.proof_address)[1:]) - else: - addr_search = bid_data.proof_address - - sum_unspent = self.getAddressBalance(coin_to, addr_search) + sum_unspent = ci_to.verifyProofOfFunds(bid_data.proof_address, bid_data.proof_signature, offer_id) self.log.debug('Proof of funds %s %s', bid_data.proof_address, self.ci(coin_to).format_amount(sum_unspent)) ensure(sum_unspent >= amount_to, 'Proof of funds failed') @@ -4382,7 +4338,7 @@ class BasicSwap(BaseApp): # TODO: check_lock_tx_inputs without txindex check_a_lock_tx_inputs = False - xmr_swap.a_lock_tx_id, xmr_swap.a_lock_tx_vout = ci_from.verifyLockTx( + xmr_swap.a_lock_tx_id, xmr_swap.a_lock_tx_vout = ci_from.verifySCLockTx( xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script, bid.amount, xmr_swap.pkal, xmr_swap.pkaf, @@ -4390,14 +4346,14 @@ class BasicSwap(BaseApp): check_a_lock_tx_inputs, xmr_swap.vkbv) a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script) - xmr_swap.a_lock_refund_tx_id, xmr_swap.a_swap_refund_value, lock_refund_vout = ci_from.verifyLockRefundTx( + xmr_swap.a_lock_refund_tx_id, xmr_swap.a_swap_refund_value, lock_refund_vout = ci_from.verifySCLockRefundTx( xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_tx, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_lock_tx_id, xmr_swap.a_lock_tx_vout, xmr_offer.lock_time_1, xmr_swap.a_lock_tx_script, xmr_swap.pkal, xmr_swap.pkaf, xmr_offer.lock_time_2, bid.amount, xmr_offer.a_fee_rate, xmr_swap.vkbv) - ci_from.verifyLockRefundSpendTx( + ci_from.verifySCLockRefundSpendTx( xmr_swap.a_lock_refund_spend_tx, xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script, xmr_swap.pkal, @@ -4517,7 +4473,7 @@ class BasicSwap(BaseApp): xmr_swap.kal_sig = ci_from.signCompact(kal, 'proof key owned for swap') # Create Script lock spend tx - xmr_swap.a_lock_spend_tx = ci_from.createScriptLockSpendTx( + xmr_swap.a_lock_spend_tx = ci_from.createSCLockSpendTx( xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script, xmr_swap.dest_af, xmr_offer.a_fee_rate, xmr_swap.vkbv) @@ -4923,12 +4879,12 @@ class BasicSwap(BaseApp): xmr_swap.a_lock_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_spend_tx) xmr_swap.kal_sig = msg_data.kal_sig - ci_from.verifyLockSpendTx( + ci_from.verifySCLockSpendTx( xmr_swap.a_lock_spend_tx, xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script, xmr_swap.dest_af, xmr_offer.a_fee_rate, xmr_swap.vkbv) - ci_from.verifyCompact(xmr_swap.pkal, 'proof key owned for swap', xmr_swap.kal_sig) + ci_from.verifyCompactSig(xmr_swap.pkal, 'proof key owned for swap', xmr_swap.kal_sig) bid.setState(BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX) bid.setState(BidStates.XMR_SWAP_MSG_SCRIPT_LOCK_SPEND_TX) @@ -5865,7 +5821,7 @@ class BasicSwap(BaseApp): self.log.debug('Creating %s lock refund swipe tx', ci.coin_name()) pkh_dest = ci.decodeAddress(self.getReceiveAddressForCoin(ci.coin_type())) - spend_tx = ci.createScriptLockRefundSpendToFTx( + spend_tx = ci.createSCLockRefundSpendToFTx( xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script, pkh_dest, xmr_offer.a_fee_rate, xmr_swap.vkbv) diff --git a/basicswap/basicswap_util.py b/basicswap/basicswap_util.py index 71d44be..bc25a45 100644 --- a/basicswap/basicswap_util.py +++ b/basicswap/basicswap_util.py @@ -9,8 +9,8 @@ import struct import hashlib from enum import IntEnum, auto from .util.address import ( - decodeAddress, encodeAddress, + decodeAddress, ) from .chainparams import ( chainparams, diff --git a/basicswap/chainparams.py b/basicswap/chainparams.py index 7a61910..f4e6959 100644 --- a/basicswap/chainparams.py +++ b/basicswap/chainparams.py @@ -30,6 +30,7 @@ class Coins(IntEnum): # NDAU = 10 PIVX = 11 DASH = 12 + FIRO = 13 chainparams = { @@ -287,6 +288,45 @@ chainparams = { 'min_amount': 1000, 'max_amount': 100000 * COIN, } + }, + Coins.FIRO: { + 'name': 'firo', + 'ticker': 'FIRO', + 'message_magic': 'Zcoin Signed Message:\n', + 'blocks_target': 60 * 10, + 'decimal_places': 8, + 'has_csv': True, + 'has_segwit': True, + 'mainnet': { + 'rpcport': 8888, + 'pubkey_address': 82, + 'script_address': 7, + 'key_prefix': 210, + 'hrp': '', + 'bip44': 136, + 'min_amount': 1000, + 'max_amount': 100000 * COIN, + }, + 'testnet': { + 'rpcport': 18888, + 'pubkey_address': 65, + 'script_address': 178, + 'key_prefix': 185, + 'hrp': '', + 'bip44': 1, + 'min_amount': 1000, + 'max_amount': 100000 * COIN, + }, + 'regtest': { + 'rpcport': 28888, + 'pubkey_address': 65, + 'script_address': 178, + 'key_prefix': 185, + 'hrp': '', + 'bip44': 1, + 'min_amount': 1000, + 'max_amount': 100000 * COIN, + } } } ticker_map = {} diff --git a/basicswap/config.py b/basicswap/config.py index 61010e5..33483f9 100644 --- a/basicswap/config.py +++ b/basicswap/config.py @@ -46,3 +46,8 @@ DASH_BINDIR = os.path.expanduser(os.getenv('DASH_BINDIR', os.path.join(DEFAULT_T DASHD = os.getenv('DASHD', 'dashd' + bin_suffix) DASH_CLI = os.getenv('DASH_CLI', 'dash-cli' + bin_suffix) DASH_TX = os.getenv('DASH_TX', 'dash-tx' + bin_suffix) + +FIRO_BINDIR = os.path.expanduser(os.getenv('FIRO_BINDIR', os.path.join(DEFAULT_TEST_BINDIR, 'firo'))) +FIROD = os.getenv('FIROD', 'firod' + bin_suffix) +FIRO_CLI = os.getenv('FIRO_CLI', 'firo-cli' + bin_suffix) +FIRO_TX = os.getenv('FIRO_TX', 'firo-tx' + bin_suffix) diff --git a/basicswap/interface/btc.py b/basicswap/interface/btc.py index d60b15d..aeff269 100644 --- a/basicswap/interface/btc.py +++ b/basicswap/interface/btc.py @@ -320,7 +320,7 @@ class BTCInterface(CoinInterface): def decodeAddress(self, address): bech32_prefix = self.chainparams_network()['hrp'] - if address.startswith(bech32_prefix + '1'): + if len(bech32_prefix) > 0 and address.startswith(bech32_prefix + '1'): return bytes(segwit_addr.decode(bech32_prefix, address)[1]) return decodeAddress(address)[1:] @@ -338,12 +338,22 @@ class BTCInterface(CoinInterface): checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest() return b58encode(data + checksum[0:4]) + def sh_to_address(self, sh): + 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): 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): + return self.encode_p2wsh(script) + def encode_p2sh(self, script): return pubkeyToAddress(self.chainparams_network()['script_address'], script) @@ -375,6 +385,12 @@ class BTCInterface(CoinInterface): def encodePubkey(self, pk): return pointToCPK(pk) + def encodeSegwitAddress(self, key_hash): + return segwit_addr.encode(self.chainparams_network()['hrp'], 0, key_hash) + + def decodeSegwitAddress(self, addr): + return bytes(segwit_addr.decode(self.chainparams_network()['hrp'], addr)[1]) + def decodePubkey(self, pke): return CPKToPoint(pke) @@ -415,7 +431,7 @@ class BTCInterface(CoinInterface): return CScript([2, Kal_enc, Kaf_enc, 2, CScriptOp(OP_CHECKMULTISIG)]) - def createScriptLockTx(self, value, Kal, Kaf, vkbv=None): + def createSCLockTx(self, value, Kal, Kaf, vkbv=None): script = self.genScriptLockTxScript(Kal, Kaf) tx = CTransaction() tx.nVersion = self.txVersion() @@ -423,7 +439,7 @@ class BTCInterface(CoinInterface): return tx.serialize(), script - def fundScriptLockTx(self, tx_bytes, feerate, vkbv=None): + def fundSCLockTx(self, tx_bytes, feerate, vkbv=None): return self.fundTx(tx_bytes, feerate) def extractScriptLockRefundScriptValues(self, script_bytes): @@ -470,11 +486,11 @@ class BTCInterface(CoinInterface): Kaf_enc, CScriptOp(OP_CHECKSIG), CScriptOp(OP_ENDIF)]) - def createScriptLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None): + def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None): tx_lock = CTransaction() tx_lock = FromHex(tx_lock, tx_lock_bytes.hex()) - output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()]) + output_script = self.getScriptDest(script_lock) locked_n = findOutput(tx_lock, output_script) ensure(locked_n is not None, 'Output not found in tx') locked_coin = tx_lock.vout[locked_n].nValue @@ -485,8 +501,10 @@ class BTCInterface(CoinInterface): refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val) tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n), nSequence=lock1_value)) - tx.vout.append(self.txoType()(locked_coin, CScript([OP_0, hashlib.sha256(refund_script).digest()]))) + tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n), + nSequence=lock1_value, + scriptSig=self.getScriptScriptSig(script_lock))) + tx.vout.append(self.txoType()(locked_coin, self.getScriptDest(refund_script))) dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock) witness_bytes = self.getWitnessStackSerialisedLength(dummy_witness_stack) @@ -495,19 +513,19 @@ class BTCInterface(CoinInterface): tx.vout[0].nValue = locked_coin - pay_fee tx.rehash() - self._log.info('createScriptLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', + self._log.info('createSCLockRefundTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) return tx.serialize(), refund_script, tx.vout[0].nValue - def createScriptLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv=None): + def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv=None): # Returns the coinA locked coin to the leader # The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey # If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower tx_lock_refund = self.loadTx(tx_lock_refund_bytes) - output_script = CScript([OP_0, hashlib.sha256(script_lock_refund).digest()]) + output_script = self.getScriptDest(script_lock_refund) locked_n = findOutput(tx_lock_refund, output_script) ensure(locked_n is not None, 'Output not found in tx') locked_coin = tx_lock_refund.vout[locked_n].nValue @@ -517,7 +535,9 @@ class BTCInterface(CoinInterface): tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), nSequence=0)) + tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), + nSequence=0, + scriptSig=self.getScriptScriptSig(script_lock_refund))) tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_refund_to))) @@ -528,18 +548,18 @@ class BTCInterface(CoinInterface): tx.vout[0].nValue = locked_coin - pay_fee tx.rehash() - self._log.info('createScriptLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', + self._log.info('createSCLockRefundSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) return tx.serialize() - def createScriptLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None): + def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None): # lock refund swipe tx # Sends the coinA locked coin to the follower tx_lock_refund = self.loadTx(tx_lock_refund_bytes) - output_script = CScript([OP_0, hashlib.sha256(script_lock_refund).digest()]) + output_script = self.getScriptDest(script_lock_refund) locked_n = findOutput(tx_lock_refund, output_script) ensure(locked_n is not None, 'Output not found in tx') locked_coin = tx_lock_refund.vout[locked_n].nValue @@ -551,7 +571,9 @@ class BTCInterface(CoinInterface): tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), nSequence=lock2_value)) + tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), + nSequence=lock2_value, + scriptSig=self.getScriptScriptSig(script_lock_refund))) tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest))) @@ -562,14 +584,14 @@ class BTCInterface(CoinInterface): tx.vout[0].nValue = locked_coin - pay_fee tx.rehash() - self._log.info('createScriptLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', + self._log.info('createSCLockRefundSpendToFTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) return tx.serialize() - def createScriptLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None): + def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None): tx_lock = self.loadTx(tx_lock_bytes) - output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()]) + output_script = self.getScriptDest(script_lock) locked_n = findOutput(tx_lock, output_script) ensure(locked_n is not None, 'Output not found in tx') locked_coin = tx_lock.vout[locked_n].nValue @@ -579,7 +601,8 @@ class BTCInterface(CoinInterface): tx = CTransaction() tx.nVersion = self.txVersion() - tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n))) + tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n), + scriptSig=self.getScriptScriptSig(script_lock))) tx.vout.append(self.txoType()(locked_coin, self.getScriptForPubkeyHash(pkh_dest))) @@ -590,16 +613,16 @@ class BTCInterface(CoinInterface): tx.vout[0].nValue = locked_coin - pay_fee tx.rehash() - self._log.info('createScriptLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', + self._log.info('createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', i2h(tx.sha256), tx_fee_rate, vsize, pay_fee) return tx.serialize() - def verifyLockTx(self, tx_bytes, script_out, - swap_value, - Kal, Kaf, - feerate, - check_lock_tx_inputs, vkbv=None): + def verifySCLockTx(self, tx_bytes, script_out, + swap_value, + Kal, Kaf, + feerate, + check_lock_tx_inputs, vkbv=None): # Verify: # @@ -614,7 +637,7 @@ class BTCInterface(CoinInterface): ensure(tx.nVersion == self.txVersion(), 'Bad version') ensure(tx.nLockTime == 0, 'Bad nLockTime') # TODO match txns created by cores - script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()]) + script_pk = self.getScriptDest(script_out) locked_n = findOutput(tx, script_pk) ensure(locked_n is not None, 'Output not found in tx') locked_coin = tx.vout[locked_n].nValue @@ -663,9 +686,9 @@ class BTCInterface(CoinInterface): return txid, locked_n - def verifyLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out, - prevout_id, prevout_n, prevout_seq, prevout_script, - Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv=None): + def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out, + prevout_id, prevout_n, prevout_seq, prevout_script, + Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv=None): # Verify: # Must have only one input with correct prevout and sequence # Must have only one output to the p2wsh of the lock refund script @@ -680,12 +703,12 @@ class BTCInterface(CoinInterface): ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') ensure(tx.vin[0].nSequence == prevout_seq, 'Bad input nSequence') - ensure(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty') + ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script), 'Input scriptsig mismatch') ensure(tx.vin[0].prevout.hash == b2i(prevout_id) and tx.vin[0].prevout.n == prevout_n, 'Input prevout mismatch') ensure(len(tx.vout) == 1, 'tx doesn\'t have one output') - script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()]) + script_pk = self.getScriptDest(script_out) locked_n = findOutput(tx, script_pk) ensure(locked_n is not None, 'Output not found in tx') locked_coin = tx.vout[locked_n].nValue @@ -712,10 +735,10 @@ class BTCInterface(CoinInterface): return txid, locked_coin, locked_n - def verifyLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes, - lock_refund_tx_id, prevout_script, - Kal, - prevout_n, prevout_value, feerate, vkbv=None): + def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes, + lock_refund_tx_id, prevout_script, + Kal, + prevout_n, prevout_value, feerate, vkbv=None): # Verify: # Must have only one input with correct prevout (n is always 0) and sequence # Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr @@ -728,7 +751,7 @@ class BTCInterface(CoinInterface): ensure(len(tx.vin) == 1, 'tx doesn\'t have one input') ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence') - ensure(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty') + ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(prevout_script), 'Input scriptsig mismatch') ensure(tx.vin[0].prevout.hash == b2i(lock_refund_tx_id) and tx.vin[0].prevout.n == 0, 'Input prevout mismatch') ensure(len(tx.vout) == 1, 'tx doesn\'t have one output') @@ -756,9 +779,9 @@ class BTCInterface(CoinInterface): return True - def verifyLockSpendTx(self, tx_bytes, - lock_tx_bytes, lock_tx_script, - a_pkhash_f, feerate, vkbv=None): + def verifySCLockSpendTx(self, tx_bytes, + lock_tx_bytes, lock_tx_script, + a_pkhash_f, feerate, vkbv=None): # Verify: # Must have only one input with correct prevout (n is always 0) and sequence # Must have only one output with destination and amount @@ -774,13 +797,13 @@ class BTCInterface(CoinInterface): lock_tx = self.loadTx(lock_tx_bytes) lock_tx_id = self.getTxid(lock_tx) - output_script = CScript([OP_0, hashlib.sha256(lock_tx_script).digest()]) + output_script = self.getScriptDest(lock_tx_script) locked_n = findOutput(lock_tx, output_script) ensure(locked_n is not None, 'Output not found in tx') locked_coin = lock_tx.vout[locked_n].nValue ensure(tx.vin[0].nSequence == 0, 'Bad input nSequence') - ensure(len(tx.vin[0].scriptSig) == 0, 'Input scriptsig not empty') + ensure(tx.vin[0].scriptSig == self.getScriptScriptSig(lock_tx_script), 'Input scriptsig mismatch') ensure(tx.vin[0].prevout.hash == b2i(lock_tx_id) and tx.vin[0].prevout.n == locked_n, 'Input prevout mismatch') ensure(len(tx.vout) == 1, 'tx doesn\'t have one output') @@ -891,7 +914,7 @@ class BTCInterface(CoinInterface): def getTxOutputPos(self, tx, script): if isinstance(tx, bytes): tx = self.loadTx(tx) - script_pk = CScript([OP_0, hashlib.sha256(script).digest()]) + script_pk = self.getScriptDest(script) return findOutput(tx, script_pk) def getPubkeyHash(self, K): @@ -900,6 +923,9 @@ class BTCInterface(CoinInterface): def getScriptDest(self, script): return CScript([OP_0, hashlib.sha256(script).digest()]) + def getScriptScriptSig(self, script): + return bytes() + def getPkDest(self, K): return self.getScriptForPubkeyHash(self.getPubkeyHash(K)) @@ -1003,11 +1029,22 @@ class BTCInterface(CoinInterface): def spendBLockTx(self, chain_b_lock_txid, address_to, kbv, kbs, cb_swap_value, b_fee, restore_height): raise ValueError('TODO') + def importWatchOnlyAddress(self, address, label): + self.rpc_callback('importaddress', [address, label, False]) + + def isWatchOnlyAddress(self, address): + addr_info = self.rpc_callback('getaddressinfo', [address]) + return addr_info['iswatchonly'] + + def getSCLockScriptAddress(self, lock_script): + 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): # Add watchonly address and rescan if required - addr_info = self.rpc_callback('getaddressinfo', [dest_address]) - if not addr_info['iswatchonly']: - ro = self.rpc_callback('importaddress', [dest_address, 'bid', False]) + + if not self.isWatchOnlyAddress(dest_address): + 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)) self.rpc_callback('rescanblockchain', [rescan_from]) @@ -1082,7 +1119,7 @@ class BTCInterface(CoinInterface): privkey = PrivateKey(k) return privkey.sign_recoverable(message_hash, hasher=None)[:64] - def verifyCompact(self, K, message, sig): + def verifyCompactSig(self, K, message, sig): message_hash = hashlib.sha256(bytes(message, 'utf-8')).digest() pubkey = PublicKey(K) rv = pubkey.verify_compact(sig, message_hash, hasher=None) @@ -1090,7 +1127,7 @@ class BTCInterface(CoinInterface): def verifyMessage(self, address, message, signature, message_magic=None) -> bool: if message_magic is None: - message_magic = self.chainparams_network()['message_magic'] + message_magic = self.chainparams()['message_magic'] message_bytes = SerialiseNumCompact(len(message_magic)) + bytes(message_magic, 'utf-8') + SerialiseNumCompact(len(message)) + bytes(message, 'utf-8') message_hash = hashlib.sha256(hashlib.sha256(message_bytes).digest()).digest() @@ -1185,9 +1222,61 @@ class BTCInterface(CoinInterface): def getBlockWithTxns(self, block_hash): return self.rpc_callback('getblock', [block_hash, 2]) + def getUnspentsByAddr(self): + unspent_addr = dict() + unspent = self.rpc_callback('listunspent') + for u in unspent: + if u['spendable'] is not True: + continue + unspent_addr[u['address']] = unspent_addr.get(u['address'], 0) + self.make_int(u['amount'], r=1) + return unspent_addr + + def getUTXOBalance(self, address): + num_blocks = self.rpc_callback('getblockcount') + + sum_unspent = 0 + self._log.debug('[rm] scantxoutset start') # scantxoutset is slow + ro = self.rpc_callback('scantxoutset', ['start', ['addr({})'.format(address)]]) # TODO: Use combo(address) where possible + self._log.debug('[rm] scantxoutset end') + for o in ro['unspents']: + sum_unspent += self.make_int(o['amount']) + return sum_unspent + + def getProofOfFunds(self, amount_for, extra_commit_bytes): + # TODO: Lock unspent and use same output/s to fund bid + unspent_addr = self.getUnspentsByAddr() + + sign_for_addr = None + for addr, value in unspent_addr.items(): + if value >= amount_for: + sign_for_addr = addr + break + + ensure(sign_for_addr is not None, 'Could not find address with enough funds for proof') + + self._log.debug('sign_for_addr %s', sign_for_addr) + if self._use_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) + self._log.debug('sign_for_addr converted %s', sign_for_addr) + + signature = self.rpc_callback('signmessage', [sign_for_addr, sign_for_addr + '_swap_proof_' + extra_commit_bytes.hex()]) + + return (sign_for_addr, signature) + + def verifyProofOfFunds(self, address, signature, extra_commit_bytes): + 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: + address = self.encodeSegwitAddress(decodeAddress(address)[1:]) + + return self.getUTXOBalance(address) + def testBTCInterface(): - print('testBTCInterface') + print('TODO: testBTCInterface') if __name__ == "__main__": diff --git a/basicswap/interface/firo.py b/basicswap/interface/firo.py new file mode 100644 index 0000000..a6e46d8 --- /dev/null +++ b/basicswap/interface/firo.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2022 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +import hashlib +from .btc import BTCInterface, find_vout_for_address_from_txobj +from basicswap.chainparams import Coins + +from basicswap.util.address import ( + decodeAddress, +) +from basicswap.contrib.test_framework.script import ( + CScript, + OP_0, + OP_DUP, + OP_EQUAL, + OP_HASH160, + OP_CHECKSIG, + OP_EQUALVERIFY, + hash160, +) +from basicswap.contrib.test_framework.messages import ( + CTransaction, +) + + +class FIROInterface(BTCInterface): + @staticmethod + def coin_type(): + return Coins.FIRO + + def initialiseWallet(self, key): + # load with -hdseed= parameter + pass + + def getNewAddress(self, use_segwit, label='swap_receive'): + return self.rpc_callback('getnewaddress', [label]) + # addr_plain = self.rpc_callback('getnewaddress', [label]) + # return self.rpc_callback('addwitnessaddress', [addr_plain]) + + def decodeAddress(self, address): + return decodeAddress(address)[1:] + + def encodeSegwitAddress(self, script): + raise ValueError('TODO') + + def decodeSegwitAddress(self, addr): + raise ValueError('TODO') + + def isWatchOnlyAddress(self, address): + addr_info = self.rpc_callback('validateaddress', [address]) + return 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): + # 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]) + + return address + + def getLockTxHeightFiro(self, txid, lock_script, bid_amount, rescan_from, find_index=False): + # 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): + 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)) + self.rpc_callback('rescanblockchain', [rescan_from]) + + return_txid = True if txid is None else False + if txid is None: + txns = self.rpc_callback('listunspent', [0, 9999999, [dest_address, ]]) + + for tx in txns: + if self.make_int(tx['amount']) == bid_amount: + txid = bytes.fromhex(tx['txid']) + break + + if txid is None: + return None + + try: + tx = self.rpc_callback('gettransaction', [txid.hex()]) + + block_height = 0 + if 'blockhash' in tx: + block_header = self.rpc_callback('getblockheader', [tx['blockhash']]) + block_height = block_header['height'] + + rv = { + 'depth': 0 if 'confirmations' not in tx else tx['confirmations'], + 'height': block_height} + + except Exception as e: + self._log.debug('getLockTxHeight gettransaction failed: %s, %s', txid.hex(), str(e)) + return None + + if find_index: + tx_obj = self.rpc_callback('decoderawtransaction', [tx['hex']]) + rv['index'] = find_vout_for_address_from_txobj(tx_obj, dest_address) + + if return_txid: + rv['txid'] = txid.hex() + + return rv + + def createSCLockTx(self, value, Kal, Kaf, vkbv=None): + script = self.genScriptLockTxScript(Kal, Kaf) + tx = CTransaction() + tx.nVersion = self.txVersion() + tx.vout.append(self.txoType()(value, self.getScriptDest(script))) + + return tx.serialize(), script + + def fundSCLockTx(self, tx_bytes, feerate, vkbv=None): + return self.fundTx(tx_bytes, feerate) + + def signTxWithWallet(self, tx): + rv = self.rpc_callback('signrawtransaction', [tx.hex()]) + return bytes.fromhex(rv['hex']) + + def createRawSignedTransaction(self, addr_to, amount): + txn = self.rpc_callback('createrawtransaction', [[], {addr_to: self.format_amount(amount)}]) + + fee_rate, fee_src = self.get_fee_rate(self._conf_target) + self._log.debug(f'Fee rate: {fee_rate}, source: {fee_src}, block target: {self._conf_target}') + + options = { + 'lockUnspents': True, + 'feeRate': fee_rate, + } + txn_funded = self.rpc_callback('fundrawtransaction', [txn, options])['hex'] + txn_signed = self.rpc_callback('signrawtransaction', [txn_funded])['hex'] + return txn_signed + + def getScriptForPubkeyHash(self, pkh): + # Return P2WPKH nested in BIP16 P2SH + + return CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]) + + def getScriptDest(self, script): + # P2WSH nested in BIP16_P2SH + + script_hash = hashlib.sha256(script).digest() + assert len(script_hash) == 32 + script_hash_hash = hash160(script_hash) + assert len(script_hash_hash) == 20 + + return CScript([OP_HASH160, script_hash_hash, OP_EQUAL]) + + def encodeScriptDest(self, script): + # Extract hash from script + script_hash = script[2:-1] + return self.sh_to_address(script_hash) + + def getScriptScriptSig(self, script): + return CScript([OP_0, hashlib.sha256(script).digest()]) + + def withdrawCoin(self, value, addr_to, subfee): + params = [addr_to, value, '', '', subfee] + return self.rpc_callback('sendtoaddress', params) + + def getWalletSeedID(self): + return self.rpc_callback('getwalletinfo')['hdmasterkeyid'] diff --git a/basicswap/interface/part.py b/basicswap/interface/part.py index 06b28dc..cddf673 100644 --- a/basicswap/interface/part.py +++ b/basicswap/interface/part.py @@ -166,7 +166,7 @@ class PARTInterfaceBlind(PARTInterface): ensure(v['result'] is True, 'verifycommitment failed') return output_n, blinded_info - def createScriptLockTx(self, value, Kal, Kaf, vkbv): + def createSCLockTx(self, value, Kal, Kaf, vkbv): script = self.genScriptLockTxScript(Kal, Kaf) # Nonce is derived from vkbv, ephemeral_key isn't used @@ -183,7 +183,7 @@ class PARTInterfaceBlind(PARTInterface): tx_bytes = bytes.fromhex(rv['hex']) return tx_bytes, script - def fundScriptLockTx(self, tx_bytes, feerate, vkbv): + def fundSCLockTx(self, tx_bytes, feerate, vkbv): feerate_str = self.format_amount(feerate) # TODO: unlock unspents if bid cancelled @@ -205,7 +205,7 @@ class PARTInterfaceBlind(PARTInterface): rv = self.rpc_callback('fundrawtransactionfrom', ['blind', tx_hex, {}, outputs_info, options]) return bytes.fromhex(rv['hex']) - def createScriptLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv): + def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv): lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()]) assert (self.getTxid(tx_lock_bytes).hex() == lock_tx_obj['txid']) # Nonce is derived from vkbv, ephemeral_key isn't used @@ -252,7 +252,7 @@ class PARTInterfaceBlind(PARTInterface): return bytes.fromhex(lock_refund_tx_hex), refund_script, refunded_value - def createScriptLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv): + def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv): # Returns the coinA locked coin to the leader # The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey # If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower @@ -297,11 +297,11 @@ class PARTInterfaceBlind(PARTInterface): return bytes.fromhex(lock_refund_spend_tx_hex) - def verifyLockTx(self, tx_bytes, script_out, - swap_value, - Kal, Kaf, - feerate, - check_lock_tx_inputs, vkbv): + def verifySCLockTx(self, tx_bytes, script_out, + swap_value, + Kal, Kaf, + feerate, + check_lock_tx_inputs, vkbv): lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()]) lock_txid_hex = lock_tx_obj['txid'] self._log.info('Verifying lock tx: {}.'.format(lock_txid_hex)) @@ -341,9 +341,9 @@ class PARTInterfaceBlind(PARTInterface): return bytes.fromhex(lock_txid_hex), lock_output_n - def verifyLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out, - prevout_id, prevout_n, prevout_seq, prevout_script, - Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv): + def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out, + prevout_id, prevout_n, prevout_seq, prevout_script, + Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv): lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()]) lock_refund_txid_hex = lock_refund_tx_obj['txid'] self._log.info('Verifying lock refund tx: {}.'.format(lock_refund_txid_hex)) @@ -399,10 +399,10 @@ class PARTInterfaceBlind(PARTInterface): return bytes.fromhex(lock_refund_txid_hex), lock_refund_txo_value, lock_refund_output_n - def verifyLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes, - lock_refund_tx_id, prevout_script, - Kal, - prevout_n, prevout_value, feerate, vkbv): + def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes, + lock_refund_tx_id, prevout_script, + Kal, + prevout_n, prevout_value, feerate, vkbv): lock_refund_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()]) lock_refund_spend_txid_hex = lock_refund_spend_tx_obj['txid'] self._log.info('Verifying lock refund spend tx: {}.'.format(lock_refund_spend_txid_hex)) @@ -460,7 +460,7 @@ class PARTInterfaceBlind(PARTInterface): ensure(output_n is not None, 'Output not found in tx') return output_n - def createScriptLockSpendTx(self, tx_lock_bytes, script_lock, pk_dest, tx_fee_rate, vkbv): + def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pk_dest, tx_fee_rate, vkbv): lock_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_bytes.hex()]) lock_txid_hex = lock_tx_obj['txid'] @@ -501,14 +501,14 @@ class PARTInterfaceBlind(PARTInterface): vsize = lock_spend_tx_obj['vsize'] pay_fee = make_int(lock_spend_tx_obj['vout'][0]['ct_fee']) actual_tx_fee_rate = pay_fee * 1000 // vsize - self._log.info('createScriptLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', + self._log.info('createSCLockSpendTx %s:\n fee_rate, vsize, fee: %ld, %ld, %ld.', lock_spend_tx_obj['txid'], actual_tx_fee_rate, vsize, pay_fee) return bytes.fromhex(lock_spend_tx_hex) - def verifyLockSpendTx(self, tx_bytes, - lock_tx_bytes, lock_tx_script, - a_pk_f, feerate, vkbv): + def verifySCLockSpendTx(self, tx_bytes, + lock_tx_bytes, lock_tx_script, + a_pk_f, feerate, vkbv): lock_spend_tx_obj = self.rpc_callback('decoderawtransaction', [tx_bytes.hex()]) lock_spend_txid_hex = lock_spend_tx_obj['txid'] self._log.info('Verifying lock spend tx: {}.'.format(lock_spend_txid_hex)) @@ -578,7 +578,7 @@ class PARTInterfaceBlind(PARTInterface): return True - def createScriptLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv): + def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv): # lock refund swipe tx # Sends the coinA locked coin to the follower lock_refund_tx_obj = self.rpc_callback('decoderawtransaction', [tx_lock_refund_bytes.hex()]) diff --git a/basicswap/ui/page_wallet.py b/basicswap/ui/page_wallet.py index 1f08f88..47d2489 100644 --- a/basicswap/ui/page_wallet.py +++ b/basicswap/ui/page_wallet.py @@ -296,7 +296,7 @@ def page_wallet(self, url_split, post_string): if show_utxo_groups: utxo_groups = '' - unspent_by_addr = swap_client.getUnspentsByAddr(k) + unspent_by_addr = ci.getUnspentsByAddr() sorted_unspent_by_addr = sorted(unspent_by_addr.items(), key=lambda x: x[1], reverse=True) for kv in sorted_unspent_by_addr: diff --git a/bin/basicswap_prepare.py b/bin/basicswap_prepare.py index 3f2a444..3551e8b 100755 --- a/bin/basicswap_prepare.py +++ b/bin/basicswap_prepare.py @@ -53,6 +53,9 @@ PIVX_VERSION_TAG = os.getenv('PIVX_VERSION_TAG', '_scantxoutset') DASH_VERSION = os.getenv('DASH_VERSION', '18.1.0') DASH_VERSION_TAG = os.getenv('DASH_VERSION_TAG', '') +FIRO_VERSION = os.getenv('FIRO_VERSION', '0.14.11.1') +FIRO_VERSION_TAG = os.getenv('FIRO_VERSION_TAG', '') + known_coins = { 'particl': (PARTICL_VERSION, PARTICL_VERSION_TAG, ('tecnovert',)), @@ -62,6 +65,7 @@ known_coins = { 'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)), 'pivx': (PIVX_VERSION, PIVX_VERSION_TAG, ('tecnovert',)), 'dash': (DASH_VERSION, DASH_VERSION_TAG, ('pasta',)), + 'firo': (FIRO_VERSION, FIRO_VERSION_TAG, ('reuben',)), } expected_key_ids = { @@ -73,6 +77,7 @@ expected_key_ids = { 'davidburkett38': ('3620E9D387E55666',), 'fuzzbawls': ('3BDCDA2D87A881D9',), 'pasta': ('52527BEDABE87984',), + 'reuben': ('1290A1D0FA7EE109',), } if platform.system() == 'Darwin': @@ -137,6 +142,12 @@ DASH_ONION_PORT = int(os.getenv('DASH_ONION_PORT', 9999)) # nDefaultPort DASH_RPC_USER = os.getenv('DASH_RPC_USER', '') DASH_RPC_PWD = os.getenv('DASH_RPC_PWD', '') +FIRO_RPC_HOST = os.getenv('FIRO_RPC_HOST', '127.0.0.1') +FIRO_RPC_PORT = int(os.getenv('FIRO_RPC_PORT', 8888)) +FIRO_ONION_PORT = int(os.getenv('FIRO_ONION_PORT', 8168)) # nDefaultPort +FIRO_RPC_USER = os.getenv('FIRO_RPC_USER', '') +FIRO_RPC_PWD = os.getenv('FIRO_RPC_PWD', '') + TOR_PROXY_HOST = os.getenv('TOR_PROXY_HOST', '127.0.0.1') TOR_PROXY_PORT = int(os.getenv('TOR_PROXY_PORT', 9050)) TOR_CONTROL_PORT = int(os.getenv('TOR_CONTROL_PORT', 9051)) @@ -211,6 +222,19 @@ def downloadBytes(url): popConnectionParameters() +def importPubkeyFromUrls(gpg, pubkeyurls): + for url in pubkeyurls: + try: + logger.info('Importing public key from url: ' + url) + rv = gpg.import_keys(downloadBytes(url)) + break + except Exception as e: + logging.warning('Import from url failed: %s', str(e)) + + for key in rv.fingerprints: + gpg.trust_keys(key, 'TRUST_FULLY') + + def testTorConnection(): test_url = 'https://check.torproject.org/' logger.info('Testing TOR connection at: ' + test_url) @@ -278,8 +302,14 @@ def extractCore(coin, version_data, settings, bin_dir, release_path, extra_opts= logger.info('extractCore %s v%s%s', coin, version, version_tag) extract_core_overwrite = extra_opts.get('extract_core_overwrite', True) - if coin == 'monero': - bins = ['monerod', 'monero-wallet-rpc'] + if coin in ('monero', 'firo'): + if coin == 'monero': + bins = ['monerod', 'monero-wallet-rpc'] + elif coin == 'firo': + bins = [coin + 'd', coin + '-cli', coin + '-tx'] + else: + raise ValueError('Unknown coin') + num_exist = 0 for b in bins: out_path = os.path.join(bin_dir, b) @@ -412,12 +442,23 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}): release_url = 'https://github.com/dashpay/dash/releases/download/v{}/{}'.format(version + version_tag, release_filename) assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, major_version) assert_url = 'https://raw.githubusercontent.com/dashpay/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name, assert_filename) + elif coin == 'firo': + raise ValueError('TODO: scantxoutset release') + if BIN_ARCH == 'x86_64-linux-gnu': + arch_name = 'linux64' + file_ext = 'tar.gz' + elif BIN_ARCH == 'osx64': + arch_name = 'macos' + file_ext = 'dmg' + raise ValueError('TODO: Firo - Extract .dmg') + else: + raise ValueError('Firo: Unknown architecture') + release_filename = '{}-{}-{}{}.{}'.format('firo', version + version_tag, arch_name, filename_extra, file_ext) + release_url = 'https://github.com/firoorg/firo/releases/download/v{}/{}'.format(version + version_tag, release_filename) + assert_url = 'https://github.com/firoorg/firo/releases/download/v%s/SHA256SUMS' % (version + version_tag) else: raise ValueError('Unknown coin') - assert_sig_filename = assert_filename + '.sig' - assert_sig_url = assert_url + ('.asc' if major_version >= 22 else '.sig') - release_path = os.path.join(bin_dir, release_filename) if not os.path.exists(release_path): downloadFile(release_url, release_path) @@ -428,10 +469,12 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}): if not os.path.exists(assert_path): downloadFile(assert_url, assert_path) - assert_sig_filename = '{}-{}-{}-build-{}.assert.sig'.format(coin, os_name, version, signing_key_name) - assert_sig_path = os.path.join(bin_dir, assert_sig_filename) - if not os.path.exists(assert_sig_path): - downloadFile(assert_sig_url, assert_sig_path) + if coin not in ('firo', ): + assert_sig_url = assert_url + ('.asc' if major_version >= 22 else '.sig') + assert_sig_filename = '{}-{}-{}-build-{}.assert.sig'.format(coin, os_name, version, signing_key_name) + assert_sig_path = os.path.join(bin_dir, assert_sig_filename) + if not os.path.exists(assert_sig_path): + downloadFile(assert_sig_url, assert_sig_path) hasher = hashlib.sha256() with open(release_path, 'rb') as fp: @@ -462,17 +505,28 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}): for key in rv.fingerprints: gpg.trust_keys(rv.fingerprints[0], 'TRUST_FULLY') + if coin == 'pivx': + pubkey_filename = '{}_{}.pgp'.format('particl', signing_key_name) + else: + pubkey_filename = '{}_{}.pgp'.format(coin, signing_key_name) + pubkeyurls = [ + 'https://raw.githubusercontent.com/tecnovert/basicswap/master/pgp/keys/' + pubkey_filename, + 'https://gitlab.com/particl/basicswap/-/raw/master/pgp/keys/' + pubkey_filename, + ] + if coin == 'dash': + pubkeyurls.append('https://raw.githubusercontent.com/dashpay/dash/master/contrib/gitian-keys/pasta.pgp') if coin == 'monero': + pubkeyurls.append('https://raw.githubusercontent.com/monero-project/monero/master/utils/gpg_keys/binaryfate.asc') + if coin == 'firo': + pubkeyurls.append('https://firo.org/reuben.asc') + + if coin in ('monero', 'firo'): with open(assert_path, 'rb') as fp: verified = gpg.verify_file(fp) if not isValidSignature(verified) and verified.username is None: logger.warning('Signature made by unknown key.') - - pubkeyurl = 'https://raw.githubusercontent.com/monero-project/monero/master/utils/gpg_keys/binaryfate.asc' - logger.info('Importing public key from url: ' + pubkeyurl) - rv = gpg.import_keys(downloadBytes(pubkeyurl)) - gpg.trust_keys(rv.fingerprints[0], 'TRUST_FULLY') + importPubkeyFromUrls(gpg, pubkeyurls) with open(assert_path, 'rb') as fp: verified = gpg.verify_file(fp) else: @@ -481,28 +535,7 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}): if not isValidSignature(verified) and verified.username is None: logger.warning('Signature made by unknown key.') - - if coin == 'pivx': - filename = '{}_{}.pgp'.format('particl', signing_key_name) - else: - filename = '{}_{}.pgp'.format(coin, signing_key_name) - pubkeyurls = [ - 'https://raw.githubusercontent.com/tecnovert/basicswap/master/pgp/keys/' + filename, - 'https://gitlab.com/particl/basicswap/-/raw/master/pgp/keys/' + filename, - ] - if coin == 'dash': - pubkeyurls.append('https://raw.githubusercontent.com/dashpay/dash/master/contrib/gitian-keys/pasta.pgp') - for url in pubkeyurls: - try: - logger.info('Importing public key from url: ' + url) - rv = gpg.import_keys(downloadBytes(url)) - break - except Exception as e: - logging.warning('Import from url failed: %s', str(e)) - - for key in rv.fingerprints: - gpg.trust_keys(key, 'TRUST_FULLY') - + importPubkeyFromUrls(gpg, pubkeyurls) with open(assert_sig_path, 'rb') as fp: verified = gpg.verify_file(fp, assert_path) @@ -600,12 +633,13 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}): with open(core_conf_path, 'w') as fp: if chain != 'mainnet': fp.write(chain + '=1\n') - if chain == 'testnet': - fp.write('[test]\n\n') - if chain == 'regtest': - fp.write('[regtest]\n\n') - else: - logger.warning('Unknown chain %s', chain) + if coin != 'firo': + if chain == 'testnet': + fp.write('[test]\n\n') + elif chain == 'regtest': + fp.write('[regtest]\n\n') + else: + logger.warning('Unknown chain %s', chain) if COINS_RPCBIND_IP != '127.0.0.1': fp.write('rpcallowip=127.0.0.1\n') @@ -656,6 +690,13 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}): fp.write('fallbackfee=0.0002\n') if DASH_RPC_USER != '': fp.write('rpcauth={}:{}${}\n'.format(DASH_RPC_USER, salt, password_to_hmac(salt, DASH_RPC_PWD))) + elif coin == 'firo': + fp.write('prune=4000\n') + fp.write('fallbackfee=0.0002\n') + fp.write('txindex=0\n') + fp.write('usehd=1\n') + if FIRO_RPC_USER != '': + fp.write('rpcauth={}:{}${}\n'.format(FIRO_RPC_USER, salt, password_to_hmac(salt, FIRO_RPC_PWD))) else: logger.warning('Unknown coin %s', coin) @@ -886,6 +927,9 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '') coin_args = ['-nofindpeers', '-nostaking'] if c == Coins.PART else [] + if c == Coins.FIRO: + coin_args += ['-hdseed={}'.format(swap_client.getWalletKey(Coins.FIRO, 1).hex())] + daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args)) swap_client.setDaemonPID(c, daemons[-1].pid) swap_client.setCoinRunParams(c) @@ -1173,7 +1217,7 @@ def main(): }, 'dash': { 'connection_type': 'rpc' if 'dash' in with_coins else 'none', - 'manage_daemon': True if ('dash' in with_coins and PIVX_RPC_HOST == '127.0.0.1') else False, + 'manage_daemon': True if ('dash' in with_coins and DASH_RPC_HOST == '127.0.0.1') else False, 'rpchost': DASH_RPC_HOST, 'rpcport': DASH_RPC_PORT + port_offset, 'onionport': DASH_ONION_PORT + port_offset, @@ -1185,6 +1229,21 @@ def main(): 'conf_target': 2, 'core_version_group': 18, 'chain_lookups': 'local', + }, + 'firo': { + 'connection_type': 'rpc' if 'firo' in with_coins else 'none', + 'manage_daemon': True if ('firo' in with_coins and FIRO_RPC_HOST == '127.0.0.1') else False, + 'rpchost': FIRO_RPC_HOST, + 'rpcport': FIRO_RPC_PORT + port_offset, + 'onionport': FIRO_ONION_PORT + port_offset, + 'datadir': os.getenv('FIRO_DATA_DIR', os.path.join(data_dir, 'firo')), + 'bindir': os.path.join(bin_dir, 'firo'), + 'use_segwit': False, + 'use_csv': True, + 'blocks_confirmed': 1, + 'conf_target': 2, + 'core_version_group': 18, + 'chain_lookups': 'local', } } @@ -1203,6 +1262,9 @@ def main(): if DASH_RPC_USER != '': chainclients['dash']['rpcuser'] = DASH_RPC_USER chainclients['dash']['rpcpassword'] = DASH_RPC_PWD + if FIRO_RPC_USER != '': + chainclients['firo']['rpcuser'] = FIRO_RPC_USER + chainclients['firo']['rpcpassword'] = FIRO_RPC_PWD chainclients['monero']['walletsdir'] = os.getenv('XMR_WALLETS_DIR', chainclients['monero']['datadir']) diff --git a/pgp/keys/firo_reuben.pgp b/pgp/keys/firo_reuben.pgp new file mode 100644 index 0000000..709dcb6 --- /dev/null +++ b/pgp/keys/firo_reuben.pgp @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEX7UxaxYJKwYBBAHaRw8BAQdAjb4i983N4ysLmcn6RyeTwctpB2EppSc7qJ6l +yb0pezm0IXJldWJlbkBmaXJvLm9yZyA8cmV1YmVuQGZpcm8ub3JnPoiPBBAWCgAg +BQJftTFrBgsJBwgDAgQVCAoCBBYCAQACGQECGwMCHgEAIQkQEpCh0Pp+4QkWIQQB +hkVNY+g9he+R3k4SkKHQ+n7hCaMKAP9pYkzGWBNRZyvLnUVob9mV+1rOQfNM0T8p +Pmj9rIl+fgEAw8ae8Suhotv9DawP90ehFNUNUwxKz4b2zJgzz5Y7ewO4OARftTFr +EgorBgEEAZdVAQUBAQdAi86WcrR9ArfA48pJ6hFiPilSfYLd7vZJ4UgeN/I6kB4D +AQgHiHgEGBYIAAkFAl+1MWsCGwwAIQkQEpCh0Pp+4QkWIQQBhkVNY+g9he+R3k4S +kKHQ+n7hCdfMAP49okBRnhH7n4VLmfdygZUDJyfMSPG4CBC+wL2igQMqBAEAjnAf +VjbDqrZ5bf6eBbSEblyyQBtKvlARiNUu1oNsogw= +=PWmb +-----END PGP PUBLIC KEY BLOCK----- diff --git a/pgp/keys/monero_binaryfate.asc b/pgp/keys/monero_binaryfate.pgp similarity index 100% rename from pgp/keys/monero_binaryfate.asc rename to pgp/keys/monero_binaryfate.pgp diff --git a/tests/basicswap/common.py b/tests/basicswap/common.py index 22cf30a..4c8cb77 100644 --- a/tests/basicswap/common.py +++ b/tests/basicswap/common.py @@ -308,6 +308,10 @@ def delay_for(delay_event, delay_for=60): delay_event.wait(delay_for) +def make_boolean(s): + return s.lower() in ['1', 'true'] + + def make_rpc_func(node_id, base_rpc_port=BASE_RPC_PORT): node_id = node_id auth = 'test{0}:test_pass{0}'.format(node_id) diff --git a/tests/basicswap/extended/test_dash.py b/tests/basicswap/extended/test_dash.py index 5a659d4..2b12a4d 100644 --- a/tests/basicswap/extended/test_dash.py +++ b/tests/basicswap/extended/test_dash.py @@ -528,13 +528,13 @@ class Test(unittest.TestCase): def test_08_withdrawal(self): logging.info('---------- Test DASH withdrawals') - pivx_addr = dashRpc('getnewaddress \"Withdrawal test\"') - wallets0 = read_json_api(TEST_HTTP_PORT + 0, 'wallets') - assert (float(wallets0['DASH']['balance']) > 100) + addr = dashRpc('getnewaddress \"Withdrawal test\"') + wallets = read_json_api(TEST_HTTP_PORT + 0, 'wallets') + assert (float(wallets['DASH']['balance']) > 100) post_json = { 'value': 100, - 'address': pivx_addr, + 'address': addr, 'subfee': False, } json_rv = json.loads(post_json_req('http://127.0.0.1:{}/json/wallets/dash/withdraw'.format(TEST_HTTP_PORT + 0), post_json)) diff --git a/tests/basicswap/extended/test_firo.py b/tests/basicswap/extended/test_firo.py new file mode 100644 index 0000000..638beb8 --- /dev/null +++ b/tests/basicswap/extended/test_firo.py @@ -0,0 +1,465 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright (c) 2022 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +import os +import json +import random +import logging +import unittest + +import basicswap.config as cfg +from basicswap.basicswap import ( + Coins, + TxStates, + SwapTypes, + BidStates, +) +from basicswap.basicswap_util import ( + TxLockTypes, +) +from basicswap.util import ( + COIN, + make_int, + format_amount, +) +from basicswap.rpc import ( + callrpc_cli, + waitForRPC, +) +from tests.basicswap.common import ( + stopDaemons, + wait_for_bid, + make_rpc_func, + read_json_api, + post_json_req, + TEST_HTTP_PORT, + wait_for_offer, + wait_for_in_progress, + wait_for_bid_tx_state, +) +from basicswap.contrib.test_framework.messages import ( + FromHex, + CTransaction, +) +from bin.basicswap_run import startDaemon +from basicswap.contrib.rpcauth import generate_salt, password_to_hmac +from tests.basicswap.test_xmr import BaseTest, test_delay_event, callnoderpc + +logger = logging.getLogger() + +FIRO_BASE_PORT = 34832 +FIRO_BASE_RPC_PORT = 35832 +FIRO_BASE_ZMQ_PORT = 36832 + + +def firoCli(cmd, node_id=0): + return callrpc_cli(cfg.FIRO_BINDIR, os.path.join(cfg.TEST_DATADIRS, 'firo_' + str(node_id)), 'regtest', cmd, cfg.FIRO_CLI) + + +def prepareDataDir(datadir, node_id, conf_file, dir_prefix, base_p2p_port, base_rpc_port, num_nodes=3): + node_dir = os.path.join(datadir, dir_prefix + str(node_id)) + if not os.path.exists(node_dir): + os.makedirs(node_dir) + cfg_file_path = os.path.join(node_dir, conf_file) + if os.path.exists(cfg_file_path): + return + with open(cfg_file_path, 'w+') as fp: + fp.write('regtest=1\n') + fp.write('port=' + str(base_p2p_port + node_id) + '\n') + fp.write('rpcport=' + str(base_rpc_port + node_id) + '\n') + + salt = generate_salt(16) + fp.write('rpcauth={}:{}${}\n'.format('test' + str(node_id), salt, password_to_hmac(salt, 'test_pass' + str(node_id)))) + + fp.write('daemon=0\n') + fp.write('dandelion=0\n') + fp.write('printtoconsole=0\n') + fp.write('server=1\n') + fp.write('discover=0\n') + fp.write('listenonion=0\n') + fp.write('bind=127.0.0.1\n') + fp.write('findpeers=0\n') + fp.write('debug=1\n') + fp.write('debugexclude=libevent\n') + + fp.write('fallbackfee=0.01\n') + fp.write('acceptnonstdtxn=0\n') + + # qa/rpc-tests/segwit.py + fp.write('prematurewitness=1\n') + fp.write('walletprematurewitness=1\n') + fp.write('blockversion=4\n') + fp.write('promiscuousmempoolflags=517\n') + + for i in range(0, num_nodes): + if node_id == i: + continue + fp.write('addnode=127.0.0.1:{}\n'.format(base_p2p_port + i)) + + return node_dir + + +class Test(BaseTest): + __test__ = True + firo_daemons = [] + firo_addr = None + test_coin_from = Coins.FIRO + + test_atomic = True + test_xmr = False + + @classmethod + def setUpClass(cls): + cls.start_ltc_nodes = False + cls.start_xmr_nodes = False + super(Test, cls).setUpClass() + + @classmethod + def prepareExtraDataDir(cls, i): + if not cls.restore_instance: + data_dir = prepareDataDir(cfg.TEST_DATADIRS, i, 'firo.conf', 'firo_', base_p2p_port=FIRO_BASE_PORT, base_rpc_port=FIRO_BASE_RPC_PORT) + if os.path.exists(os.path.join(cfg.FIRO_BINDIR, 'firo-wallet')): + callrpc_cli(cfg.FIRO_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'firo-wallet') + + cls.firo_daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, 'firo_' + str(i)), cfg.FIRO_BINDIR, cfg.FIROD)) + logging.info('Started %s %d', cfg.FIROD, cls.part_daemons[-1].pid) + + waitForRPC(make_rpc_func(i, base_rpc_port=FIRO_BASE_RPC_PORT)) + + @classmethod + def addPIDInfo(cls, sc, i): + sc.setDaemonPID(Coins.FIRO, cls.firo_daemons[i].pid) + + @classmethod + def prepareExtraCoins(cls): + if cls.restore_instance: + void_block_rewards_pubkey = cls.getRandomPubkey() + cls.firo_addr = cls.swap_clients[0].ci(Coins.FIRO).pubkey_to_address(void_block_rewards_pubkey) + else: + num_blocks = 400 + cls.firo_addr = callnoderpc(0, 'getnewaddress', ['mining_addr'], base_rpc_port=FIRO_BASE_RPC_PORT) + # cls.firo_addr = callnoderpc(0, 'addwitnessaddress', [cls.firo_addr], base_rpc_port=FIRO_BASE_RPC_PORT) + logging.info('Mining %d Firo blocks to %s', num_blocks, cls.firo_addr) + callnoderpc(0, 'generatetoaddress', [num_blocks, cls.firo_addr], base_rpc_port=FIRO_BASE_RPC_PORT) + + firo_addr1 = callnoderpc(1, 'getnewaddress', ['initial addr'], base_rpc_port=FIRO_BASE_RPC_PORT) + # firo_addr1 = callnoderpc(1, 'addwitnessaddress', [firo_addr1], base_rpc_port=FIRO_BASE_RPC_PORT) + for i in range(5): + callnoderpc(0, 'sendtoaddress', [firo_addr1, 1000], base_rpc_port=FIRO_BASE_RPC_PORT) + + # Set future block rewards to nowhere (a random address), so wallet amounts stay constant + void_block_rewards_pubkey = cls.getRandomPubkey() + cls.firo_addr = cls.swap_clients[0].ci(Coins.FIRO).pubkey_to_address(void_block_rewards_pubkey) + num_blocks = 100 + logging.info('Mining %d Firo blocks to %s', num_blocks, cls.firo_addr) + callnoderpc(0, 'generatetoaddress', [num_blocks, cls.firo_addr], base_rpc_port=FIRO_BASE_RPC_PORT) + + @classmethod + def tearDownClass(cls): + logging.info('Finalising FIRO Test') + super(Test, cls).tearDownClass() + + stopDaemons(cls.firo_daemons) + + @classmethod + def addCoinSettings(cls, settings, datadir, node_id): + settings['chainclients']['firo'] = { + 'connection_type': 'rpc', + 'manage_daemon': False, + 'rpcport': FIRO_BASE_RPC_PORT + node_id, + 'rpcuser': 'test' + str(node_id), + 'rpcpassword': 'test_pass' + str(node_id), + 'datadir': os.path.join(datadir, 'firo_' + str(node_id)), + 'bindir': cfg.FIRO_BINDIR, + 'use_csv': True, + 'use_segwit': False, + } + + @classmethod + def coins_loop(cls): + super(Test, cls).coins_loop() + callnoderpc(0, 'generatetoaddress', [1, cls.firo_addr], base_rpc_port=FIRO_BASE_RPC_PORT) + + 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 getXmrBalance(self, js_wallets): + return float(js_wallets[Coins.XMR.name]['unconfirmed']) + float(js_wallets[Coins.XMR.name]['balance']) + + def callnoderpc(self, method, params=[], wallet=None, node_id=0): + return callnoderpc(node_id, method, params, wallet, base_rpc_port=FIRO_BASE_RPC_PORT) + + def test_01_firo(self): + logging.info('---------- Test {} segwit'.format(self.test_coin_from.name)) + + ''' + Segwit is not currently enabled: + https://github.com/firoorg/firo/blob/master/src/validation.cpp#L4425 + + Txns spending segwit utxos don't get mined. + ''' + + swap_clients = self.swap_clients + + addr_plain = firoCli('getnewaddress \"segwit test\"') + addr_witness = firoCli(f'addwitnessaddress {addr_plain}') + addr_witness_info = firoCli(f'validateaddress {addr_witness}') + txid = firoCli(f'sendtoaddress {addr_witness} 1.0') + assert len(txid) == 64 + + self.callnoderpc('generatetoaddress', [1, self.firo_addr]) + ''' + TODO: Add back when segwit is active + ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_witness)]]) + assert (len(ro['unspents']) == 1) + assert (ro['unspents'][0]['txid'] == txid) + ''' + + tx_wallet = firoCli(f'gettransaction {txid}') + tx_hex = tx_wallet['hex'] + tx = firoCli(f'decoderawtransaction {tx_hex}') + + prevout_n = -1 + for txo in tx['vout']: + if addr_witness in txo['scriptPubKey']['addresses']: + prevout_n = txo['n'] + break + assert prevout_n > -1 + + tx_funded = firoCli(f'createrawtransaction [{{\\"txid\\":\\"{txid}\\",\\"vout\\":{prevout_n}}}] {{\\"{addr_plain}\\":0.99}}') + tx_signed = firoCli(f'signrawtransaction {tx_funded}')['hex'] + + # Add scriptsig for txids to match + decoded_tx = CTransaction() + decoded_tx = FromHex(decoded_tx, tx_funded) + decoded_tx.vin[0].scriptSig = bytes.fromhex('16' + addr_witness_info['hex']) + txid_with_scriptsig = decoded_tx.rehash() + + tx_funded_decoded = firoCli(f'decoderawtransaction {tx_funded}') + tx_signed_decoded = firoCli(f'decoderawtransaction {tx_signed}') + assert tx_funded_decoded['txid'] != tx_signed_decoded['txid'] + assert txid_with_scriptsig == tx_signed_decoded['txid'] + + def test_02_part_coin(self): + logging.info('---------- Test PART to {}'.format(self.test_coin_from.name)) + if not self.test_atomic: + logging.warning('Skipping test') + return + swap_clients = self.swap_clients + + offer_id = swap_clients[0].postOffer(Coins.PART, self.test_coin_from, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST) + + wait_for_offer(test_delay_event, swap_clients[1], offer_id) + offers = swap_clients[1].listOffers() + assert (len(offers) == 1) + for offer in offers: + if offer.offer_id == offer_id: + bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id) + swap_clients[0].acceptBid(bid_id) + + wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True) + wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60) + wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60) + + js_0 = read_json_api(1800) + js_1 = read_json_api(1801) + assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0) + assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0) + + def test_03_coin_part(self): + logging.info('---------- Test {} to PART'.format(self.test_coin_from.name)) + swap_clients = self.swap_clients + + offer_id = swap_clients[1].postOffer(self.test_coin_from, Coins.PART, 10 * COIN, 9.0 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST) + + wait_for_offer(test_delay_event, swap_clients[0], offer_id) + offers = swap_clients[0].listOffers() + for offer in offers: + if offer.offer_id == offer_id: + bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) + + wait_for_bid(test_delay_event, swap_clients[1], bid_id) + swap_clients[1].acceptBid(bid_id) + + wait_for_in_progress(test_delay_event, swap_clients[0], bid_id, sent=True) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60) + wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=60) + + js_0 = read_json_api(1800) + js_1 = read_json_api(1801) + assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0) + assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0) + + def test_04_coin_btc(self): + logging.info('---------- Test {} to BTC'.format(self.test_coin_from.name)) + swap_clients = self.swap_clients + + offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST) + + wait_for_offer(test_delay_event, swap_clients[1], offer_id) + offers = swap_clients[1].listOffers() + for offer in offers: + if offer.offer_id == offer_id: + bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id) + swap_clients[0].acceptBid(bid_id) + + wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60) + wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60) + + js_0bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex())) + + js_0 = read_json_api(1800) + js_1 = read_json_api(1801) + + assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0) + assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0) + + def test_05_refund(self): + # Seller submits initiate txn, buyer doesn't respond + logging.info('---------- Test refund, {} to BTC'.format(self.test_coin_from.name)) + swap_clients = self.swap_clients + + offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, + TxLockTypes.SEQUENCE_LOCK_BLOCKS, 10) + + wait_for_offer(test_delay_event, swap_clients[1], offer_id) + offers = swap_clients[1].listOffers() + for offer in offers: + if offer.offer_id == offer_id: + bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id) + swap_clients[1].abandonBid(bid_id) + swap_clients[0].acceptBid(bid_id) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60) + wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=60) + + js_0 = read_json_api(1800) + js_1 = read_json_api(1801) + assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0) + assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0) + + def test_06_self_bid(self): + logging.info('---------- Test same client, BTC to {}'.format(self.test_coin_from.name)) + swap_clients = self.swap_clients + + js_0_before = read_json_api(1800) + + offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.BTC, 10 * COIN, 10 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST) + + wait_for_offer(test_delay_event, swap_clients[0], offer_id) + offers = swap_clients[0].listOffers() + for offer in offers: + if offer.offer_id == offer_id: + bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id) + swap_clients[0].acceptBid(bid_id) + + wait_for_bid_tx_state(test_delay_event, swap_clients[0], bid_id, TxStates.TX_REDEEMED, TxStates.TX_REDEEMED, wait_for=60) + wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60) + + js_0 = read_json_api(1800) + assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0) + assert (js_0['num_recv_bids'] == js_0_before['num_recv_bids'] + 1 and js_0['num_sent_bids'] == js_0_before['num_sent_bids'] + 1) + + def test_07_error(self): + logging.info('---------- Test error, BTC to {}, set fee above bid value'.format(self.test_coin_from.name)) + swap_clients = self.swap_clients + + js_0_before = read_json_api(1800) + + offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.BTC, 0.001 * COIN, 1.0 * COIN, 0.001 * COIN, SwapTypes.SELLER_FIRST) + + wait_for_offer(test_delay_event, swap_clients[0], offer_id) + offers = swap_clients[0].listOffers() + for offer in offers: + if offer.offer_id == offer_id: + bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) + + wait_for_bid(test_delay_event, swap_clients[0], bid_id) + swap_clients[0].acceptBid(bid_id) + swap_clients[0].getChainClientSettings(Coins.BTC)['override_feerate'] = 10.0 + swap_clients[0].getChainClientSettings(Coins.FIRO)['override_feerate'] = 10.0 + wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_ERROR, wait_for=60) + + def test_08_withdrawal(self): + logging.info('---------- Test {} withdrawals'.format(self.test_coin_from.name)) + + addr = self.callnoderpc('getnewaddress', ['Withdrawal test', ]) + wallets = read_json_api(TEST_HTTP_PORT + 0, 'wallets') + assert (float(wallets[self.test_coin_from.name]['balance']) > 100) + + post_json = { + 'value': 100, + 'address': addr, + 'subfee': False, + } + json_rv = json.loads(post_json_req('http://127.0.0.1:{}/json/wallets/{}/withdraw'.format(TEST_HTTP_PORT + 0, self.test_coin_from.name.lower()), post_json)) + assert (len(json_rv['txid']) == 64) + + def test_101_full_swap(self): + logging.info('---------- Test {} to XMR'.format(self.test_coin_from.name)) + if not self.test_xmr: + logging.warning('Skipping test') + return + swap_clients = self.swap_clients + + js_0 = read_json_api(1800, 'wallets') + node0_from_before = self.getBalance(js_0) + + js_1 = read_json_api(1801, 'wallets') + node1_from_before = self.getBalance(js_1) + + js_0_xmr = read_json_api(1800, 'wallets/xmr') + js_1_xmr = read_json_api(1801, 'wallets/xmr') + + 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) + wait_for_offer(test_delay_event, swap_clients[1], offer_id) + offers = swap_clients[0].listOffers(filters={'offer_id': offer_id}) + offer = offers[0] + + 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) + swap_clients[0].acceptXmrBid(bid_id) + + 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)) + 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)) + + js_0 = read_json_api(1800, 'wallets') + node0_from_after = self.getBalance(js_0) + # 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') + + 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)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/basicswap/extended/test_pivx.py b/tests/basicswap/extended/test_pivx.py index 141823f..5ddb897 100644 --- a/tests/basicswap/extended/test_pivx.py +++ b/tests/basicswap/extended/test_pivx.py @@ -538,13 +538,13 @@ class Test(unittest.TestCase): def test_08_withdrawal(self): logging.info('---------- Test PIVX withdrawals') - pivx_addr = pivxRpc('getnewaddress \"Withdrawal test\"') - wallets0 = read_json_api(TEST_HTTP_PORT + 0, 'wallets') - assert (float(wallets0['PIVX']['balance']) > 100) + addr = pivxRpc('getnewaddress \"Withdrawal test\"') + wallets = read_json_api(TEST_HTTP_PORT + 0, 'wallets') + assert (float(wallets['PIVX']['balance']) > 100) post_json = { 'value': 100, - 'address': pivx_addr, + 'address': addr, 'subfee': False, } json_rv = json.loads(post_json_req('http://127.0.0.1:{}/json/wallets/pivx/withdraw'.format(TEST_HTTP_PORT + 0), post_json)) diff --git a/tests/basicswap/extended/test_xmr_persistent.py b/tests/basicswap/extended/test_xmr_persistent.py index 8ed1d17..a398a20 100644 --- a/tests/basicswap/extended/test_xmr_persistent.py +++ b/tests/basicswap/extended/test_xmr_persistent.py @@ -34,6 +34,7 @@ from basicswap.rpc import ( callrpc, ) from tests.basicswap.common import ( + make_boolean, read_json_api, waitForServer, BASE_RPC_PORT, @@ -46,10 +47,6 @@ from tests.basicswap.common_xmr import ( import bin.basicswap_run as runSystem -def make_boolean(s): - return s.lower() in ['1', 'true'] - - test_path = os.path.expanduser(os.getenv('TEST_PATH', '/tmp/test_persistent')) RESET_TEST = make_boolean(os.getenv('RESET_TEST', 'false')) diff --git a/tests/basicswap/test_btc_xmr.py b/tests/basicswap/test_btc_xmr.py index ad51d55..9a48cb7 100644 --- a/tests/basicswap/test_btc_xmr.py +++ b/tests/basicswap/test_btc_xmr.py @@ -27,39 +27,217 @@ from tests.basicswap.common import ( read_json_api, wait_for_offer, wait_for_none_active, + BTC_BASE_RPC_PORT, ) - -from .test_xmr import BaseTest, test_delay_event +from basicswap.contrib.test_framework.messages import ( + ToHex, + FromHex, + CTxIn, + COutPoint, + CTransaction, + CTxInWitness, +) +from basicswap.contrib.test_framework.script import ( + CScript, + OP_CHECKLOCKTIMEVERIFY, + OP_CHECKSEQUENCEVERIFY, +) +from .test_xmr import BaseTest, test_delay_event, callnoderpc logger = logging.getLogger() class Test(BaseTest): __test__ = True - - @classmethod - def setUpClass(cls): - if not hasattr(cls, 'test_coin_from'): - cls.test_coin_from = Coins.BTC - if not hasattr(cls, 'start_ltc_nodes'): - cls.start_ltc_nodes = False - if not hasattr(cls, 'start_pivx_nodes'): - cls.start_pivx_nodes = False - super(Test, cls).setUpClass() - - @classmethod - def tearDownClass(cls): - logging.info('Finalising BTC Test') - super(Test, cls).tearDownClass() + test_coin_from = Coins.BTC + start_ltc_nodes = False 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 getXmrBalance(self, js_wallets): - return float(js_wallets[Coins.XMR.name]['unconfirmed']) + float(js_wallets[Coins.XMR.name]['balance']) + def callnoderpc(self, method, params=[], wallet=None, node_id=0): + return callnoderpc(node_id, method, params, wallet, base_rpc_port=BTC_BASE_RPC_PORT) + + def test_001_nested_segwit(self): + logging.info('---------- Test {} p2sh nested segwit'.format(self.test_coin_from.name)) + + addr_p2sh_segwit = self.callnoderpc('getnewaddress', ['segwit test', 'p2sh-segwit']) + addr_info = self.callnoderpc('getaddressinfo', [addr_p2sh_segwit, ]) + assert addr_info['script'] == 'witness_v0_keyhash' + + txid = self.callnoderpc('sendtoaddress', [addr_p2sh_segwit, 1.0]) + assert len(txid) == 64 + + self.callnoderpc('generatetoaddress', [1, self.btc_addr]) + ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_p2sh_segwit)]]) + assert (len(ro['unspents']) == 1) + assert (ro['unspents'][0]['txid'] == txid) + + tx_wallet = self.callnoderpc('gettransaction', [txid, ])['hex'] + tx = self.callnoderpc('decoderawtransaction', [tx_wallet, ]) + + prevout_n = -1 + for txo in tx['vout']: + if addr_p2sh_segwit == txo['scriptPubKey']['address']: + prevout_n = txo['n'] + break + assert prevout_n > -1 + + tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_p2sh_segwit: 0.99}]) + tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded, ])['hex'] + tx_funded_decoded = self.callnoderpc('decoderawtransaction', [tx_funded, ]) + tx_signed_decoded = self.callnoderpc('decoderawtransaction', [tx_signed, ]) + assert tx_funded_decoded['txid'] != tx_signed_decoded['txid'] + + # Add scriptsig for txids to match + addr_p2sh_segwit_info = self.callnoderpc('getaddressinfo', [addr_p2sh_segwit, ]) + decoded_tx = FromHex(CTransaction(), tx_funded) + decoded_tx.vin[0].scriptSig = bytes.fromhex('16' + addr_p2sh_segwit_info['hex']) + txid_with_scriptsig = decoded_tx.rehash() + assert txid_with_scriptsig == tx_signed_decoded['txid'] + + def test_002_native_segwit(self): + logging.info('---------- Test {} p2sh native segwit'.format(self.test_coin_from.name)) + + addr_segwit = self.callnoderpc('getnewaddress', ['segwit test', 'bech32']) + addr_info = self.callnoderpc('getaddressinfo', [addr_segwit, ]) + assert addr_info['iswitness'] is True + + txid = self.callnoderpc('sendtoaddress', [addr_segwit, 1.0]) + assert len(txid) == 64 + tx_wallet = self.callnoderpc('gettransaction', [txid, ])['hex'] + tx = self.callnoderpc('decoderawtransaction', [tx_wallet, ]) + + self.callnoderpc('generatetoaddress', [1, self.btc_addr]) + ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_segwit)]]) + assert (len(ro['unspents']) == 1) + assert (ro['unspents'][0]['txid'] == txid) + + prevout_n = -1 + for txo in tx['vout']: + if addr_segwit == txo['scriptPubKey']['address']: + prevout_n = txo['n'] + break + assert prevout_n > -1 + + tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_segwit: 0.99}]) + tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded, ])['hex'] + tx_funded_decoded = self.callnoderpc('decoderawtransaction', [tx_funded, ]) + tx_signed_decoded = self.callnoderpc('decoderawtransaction', [tx_signed, ]) + assert tx_funded_decoded['txid'] == tx_signed_decoded['txid'] + + def test_003_cltv(self): + logging.info('---------- Test {} cltv'.format(self.test_coin_from.name)) + ci = self.swap_clients[0].ci(self.test_coin_from) + + chain_height = self.callnoderpc('getblockcount') + script = CScript([chain_height + 3, OP_CHECKLOCKTIMEVERIFY, ]) + + script_dest = ci.getScriptDest(script) + tx = CTransaction() + tx.nVersion = ci.txVersion() + tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest)) + tx_hex = ToHex(tx) + tx_funded = self.callnoderpc('fundrawtransaction', [tx_hex]) + utxo_pos = 0 if tx_funded['changepos'] == 1 else 1 + tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex'] + txid = self.callnoderpc('sendrawtransaction', [tx_signed, ]) + + addr_out = self.callnoderpc('getnewaddress', ['csv test', 'bech32']) + pkh = ci.decodeSegwitAddress(addr_out) + script_out = ci.getScriptForPubkeyHash(pkh) + + tx_spend = CTransaction() + tx_spend.nVersion = ci.txVersion() + tx_spend.nLockTime = chain_height + 3 + tx_spend.vin.append(CTxIn(COutPoint(int(txid, 16), utxo_pos))) + tx_spend.vout.append(ci.txoType()(ci.make_int(1.0999), script_out)) + tx_spend.wit.vtxinwit.append(CTxInWitness()) + tx_spend.wit.vtxinwit[0].scriptWitness.stack = [script, ] + tx_spend_hex = ToHex(tx_spend) + try: + txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ]) + assert False, 'Should fail' + except Exception as e: + assert ('non-final' in str(e)) + + self.callnoderpc('generatetoaddress', [49, self.btc_addr]) + txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ]) + self.callnoderpc('generatetoaddress', [1, self.btc_addr]) + ro = self.callnoderpc('listreceivedbyaddress', [0, ]) + sum_addr = 0 + for entry in ro: + if entry['address'] == addr_out: + sum_addr += entry['amount'] + assert (sum_addr == 1.0999) + + def test_004_csv(self): + logging.info('---------- Test {} csv'.format(self.test_coin_from.name)) + swap_clients = self.swap_clients + ci = self.swap_clients[0].ci(self.test_coin_from) + + script = CScript([3, OP_CHECKSEQUENCEVERIFY, ]) + + script_dest = ci.getScriptDest(script) + tx = CTransaction() + tx.nVersion = ci.txVersion() + tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest)) + tx_hex = ToHex(tx) + tx_funded = self.callnoderpc('fundrawtransaction', [tx_hex]) + utxo_pos = 0 if tx_funded['changepos'] == 1 else 1 + tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded['hex'], ])['hex'] + txid = self.callnoderpc('sendrawtransaction', [tx_signed, ]) + + addr_out = self.callnoderpc('getnewaddress', ['csv test', 'bech32']) + pkh = ci.decodeSegwitAddress(addr_out) + script_out = ci.getScriptForPubkeyHash(pkh) + + tx_spend = CTransaction() + tx_spend.nVersion = ci.txVersion() + tx_spend.vin.append(CTxIn(COutPoint(int(txid, 16), utxo_pos), + nSequence=3)) + tx_spend.vout.append(ci.txoType()(ci.make_int(1.0999), script_out)) + tx_spend.wit.vtxinwit.append(CTxInWitness()) + tx_spend.wit.vtxinwit[0].scriptWitness.stack = [script, ] + tx_spend_hex = ToHex(tx_spend) + try: + txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ]) + assert False, 'Should fail' + except Exception as e: + assert ('non-BIP68-final' in str(e)) + + self.callnoderpc('generatetoaddress', [3, self.btc_addr]) + txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ]) + self.callnoderpc('generatetoaddress', [1, self.btc_addr]) + ro = self.callnoderpc('listreceivedbyaddress', [0, ]) + sum_addr = 0 + for entry in ro: + if entry['address'] == addr_out: + sum_addr += entry['amount'] + assert (sum_addr == 1.0999) + + def test_005_watchonly(self): + logging.info('---------- Test {} watchonly'.format(self.test_coin_from.name)) + + addr = self.callnoderpc('getnewaddress', ['watchonly test', 'bech32']) + ro = self.callnoderpc('importaddress', [addr, '', False], node_id=1) + txid = self.callnoderpc('sendtoaddress', [addr, 1.0]) + tx_hex = self.callnoderpc('getrawtransaction', [txid, ]) + self.callnoderpc('sendrawtransaction', [tx_hex, ], node_id=1) + ro = self.callnoderpc('gettransaction', [txid, ], node_id=1) + assert (ro['txid'] == txid) + balances = self.callnoderpc('getbalances', node_id=1) + assert (balances['watchonly']['trusted'] + balances['watchonly']['untrusted_pending'] >= 1.0) + + def test_006_getblock_verbosity(self): + logging.info('---------- Test {} getblock verbosity'.format(self.test_coin_from.name)) + + best_hash = self.callnoderpc('getbestblockhash') + block = self.callnoderpc('getblock', [best_hash, 2]) + assert ('vin' in block['tx'][0]) def test_01_full_swap(self): - logging.info('---------- Test {} to XMR'.format(str(self.test_coin_from))) + logging.info('---------- Test {} to XMR'.format(self.test_coin_from.name)) swap_clients = self.swap_clients js_0 = read_json_api(1800, 'wallets') @@ -108,7 +286,7 @@ class Test(BaseTest): assert (node1_xmr_after > node1_xmr_before + (amount_to_float - 0.02)) def test_02_leader_recover_a_lock_tx(self): - logging.info('---------- Test {} to XMR leader recovers coin a lock tx'.format(str(self.test_coin_from))) + logging.info('---------- Test {} to XMR leader recovers coin a lock tx'.format(self.test_coin_from.name)) swap_clients = self.swap_clients js_w0_before = read_json_api(1800, 'wallets') @@ -143,7 +321,7 @@ class Test(BaseTest): # 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(str(self.test_coin_from))) + logging.info('---------- Test {} to XMR follower recovers coin a lock tx'.format(self.test_coin_from.name)) swap_clients = self.swap_clients js_w0_before = read_json_api(1800, 'wallets') @@ -186,7 +364,7 @@ class Test(BaseTest): 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(str(self.test_coin_from))) + logging.info('---------- Test {} to XMR follower recovers coin b lock tx'.format(self.test_coin_from.name)) swap_clients = self.swap_clients diff --git a/tests/basicswap/test_other.py b/tests/basicswap/test_other.py index af10dc4..6f7d716 100644 --- a/tests/basicswap/test_other.py +++ b/tests/basicswap/test_other.py @@ -212,7 +212,7 @@ class Test(unittest.TestCase): pk = ci.getPubkey(vk) sig = ci.signCompact(vk, 'test signing message') assert (len(sig) == 64) - ci.verifyCompact(pk, 'test signing message', sig) + ci.verifyCompactSig(pk, 'test signing message', sig) def test_pubkey_to_address(self): coin_settings = {'rpcport': 0, 'rpcauth': 'none'} diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index 5d6da61..061f04b 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -59,6 +59,7 @@ from basicswap.http_server import ( ) from tests.basicswap.common import ( prepareDataDir, + make_boolean, make_rpc_func, checkForks, stopDaemons, @@ -80,8 +81,6 @@ from tests.basicswap.common import ( BTC_BASE_RPC_PORT, LTC_BASE_PORT, LTC_BASE_RPC_PORT, - PIVX_BASE_PORT, - PIVX_BASE_RPC_PORT, PREFIX_SECRET_KEY_REGTEST, ) from bin.basicswap_run import startDaemon, startXmrDaemon @@ -93,7 +92,6 @@ NUM_NODES = 3 NUM_XMR_NODES = 3 NUM_BTC_NODES = 3 NUM_LTC_NODES = 3 -NUM_PIVX_NODES = 3 TEST_DIR = cfg.TEST_DATADIRS XMR_BASE_P2P_PORT = 17792 @@ -102,6 +100,7 @@ XMR_BASE_ZMQ_PORT = 22792 XMR_BASE_WALLET_RPC_PORT = 23792 test_delay_event = threading.Event() +RESET_TEST = make_boolean(os.getenv('RESET_TEST', 'true')) def prepareXmrDataDir(datadir, node_id, conf_file): @@ -153,7 +152,7 @@ def startXmrWalletRPC(node_dir, bin_dir, wallet_bin, node_id, opts=[]): return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=wallet_stdout, stderr=wallet_stderr, cwd=data_dir) -def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_coins=set()): +def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_coins=set(), cls=None): basicswap_dir = os.path.join(datadir, 'basicswap_' + str(node_id)) if not os.path.exists(basicswap_dir): os.makedirs(basicswap_dir) @@ -229,17 +228,8 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey, with_c 'use_segwit': True, } - if Coins.PIVX in with_coins: - settings['chainclients']['pivx'] = { - 'connection_type': 'rpc', - 'manage_daemon': False, - 'rpcport': PIVX_BASE_RPC_PORT + node_id, - 'rpcuser': 'test' + str(node_id), - 'rpcpassword': 'test_pass' + str(node_id), - 'datadir': os.path.join(datadir, 'pivx_' + str(node_id)), - 'bindir': cfg.PIVX_BINDIR, - 'use_segwit': False, - } + if cls: + cls.addCoinSettings(settings, datadir, node_id) with open(settings_path, 'w') as fp: json.dump(settings, fp, indent=4) @@ -253,10 +243,6 @@ def ltcCli(cmd, node_id=0): return callrpc_cli(cfg.LITECOIN_BINDIR, os.path.join(TEST_DIR, 'ltc_' + str(node_id)), 'regtest', cmd, cfg.LITECOIN_CLI) -def pivxCli(cmd, node_id=0): - return callrpc_cli(cfg.PIVX_BINDIR, os.path.join(TEST_DIR, 'pivx_' + str(node_id)), 'regtest', cmd, cfg.PIVX_CLI) - - def signal_handler(sig, frame): logging.info('signal {} detected.'.format(sig)) test_delay_event.set() @@ -298,14 +284,7 @@ def run_coins_loop(cls): while not test_delay_event.is_set(): pause_event.wait() try: - if cls.btc_addr is not None: - btcCli('generatetoaddress 1 {}'.format(cls.btc_addr)) - if cls.ltc_addr is not None: - ltcCli('generatetoaddress 1 {}'.format(cls.ltc_addr)) - if cls.pivx_addr is not None: - pivxCli('generatetoaddress 1 {}'.format(cls.pivx_addr)) - if cls.xmr_addr is not None: - callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1}) + cls.coins_loop() except Exception as e: logging.warning('run_coins_loop ' + str(e)) test_delay_event.wait(1.0) @@ -320,34 +299,34 @@ def run_loop(cls): class BaseTest(unittest.TestCase): __test__ = False + update_thread = None + coins_update_thread = None + http_threads = [] + swap_clients = [] + part_daemons = [] + btc_daemons = [] + ltc_daemons = [] + xmr_daemons = [] + xmr_wallet_auth = [] + restore_instance = False + + start_ltc_nodes = False + start_xmr_nodes = True + + xmr_addr = None + btc_addr = None + ltc_addr = None + + @classmethod + def getRandomPubkey(cls): + eckey = ECKey() + eckey.generate() + return eckey.get_pubkey().get_bytes() @classmethod def setUpClass(cls): - if not hasattr(cls, 'start_ltc_nodes'): - cls.start_ltc_nodes = False - if not hasattr(cls, 'start_pivx_nodes'): - cls.start_pivx_nodes = False - if not hasattr(cls, 'start_xmr_nodes'): - cls.start_xmr_nodes = True - random.seed(time.time()) - cls.update_thread = None - cls.coins_update_thread = None - cls.http_threads = [] - cls.swap_clients = [] - cls.part_daemons = [] - cls.btc_daemons = [] - cls.ltc_daemons = [] - cls.pivx_daemons = [] - cls.xmr_daemons = [] - cls.xmr_wallet_auth = [] - - cls.xmr_addr = None - cls.btc_addr = None - cls.ltc_addr = None - cls.pivx_addr = None - logger.propagate = False logger.handlers = [] logger.setLevel(logging.INFO) # DEBUG shows many messages from requests.post @@ -356,16 +335,24 @@ class BaseTest(unittest.TestCase): stream_stdout.setFormatter(formatter) logger.addHandler(stream_stdout) + diagrams_dir = 'doc/protocols/sequence_diagrams' + cls.states_bidder = extract_states_from_xu_file(os.path.join(diagrams_dir, 'xmr.bidder.alt.xu'), 'B') + cls.states_offerer = extract_states_from_xu_file(os.path.join(diagrams_dir, 'xmr.offerer.alt.xu'), 'O') + if os.path.isdir(TEST_DIR): - logging.info('Removing ' + TEST_DIR) - for name in os.listdir(TEST_DIR): - if name == 'pivx-params': - continue - fullpath = os.path.join(TEST_DIR, name) - if os.path.isdir(fullpath): - shutil.rmtree(fullpath) - else: - os.remove(fullpath) + if RESET_TEST: + logging.info('Removing ' + TEST_DIR) + for name in os.listdir(TEST_DIR): + if name == 'pivx-params': + continue + fullpath = os.path.join(TEST_DIR, name) + if os.path.isdir(fullpath): + shutil.rmtree(fullpath) + else: + os.remove(fullpath) + else: + logging.info('Restoring instance from ' + TEST_DIR) + cls.restore_instance = True if not os.path.exists(TEST_DIR): os.makedirs(TEST_DIR) @@ -373,39 +360,38 @@ class BaseTest(unittest.TestCase): cls.stream_fp.setFormatter(formatter) logger.addHandler(cls.stream_fp) - diagrams_dir = 'doc/protocols/sequence_diagrams' - cls.states_bidder = extract_states_from_xu_file(os.path.join(diagrams_dir, 'xmr.bidder.alt.xu'), 'B') - cls.states_offerer = extract_states_from_xu_file(os.path.join(diagrams_dir, 'xmr.offerer.alt.xu'), 'O') - try: logging.info('Preparing coin nodes.') for i in range(NUM_NODES): - data_dir = prepareDataDir(TEST_DIR, i, 'particl.conf', 'part_') - if os.path.exists(os.path.join(cfg.PARTICL_BINDIR, 'particl-wallet')): - callrpc_cli(cfg.PARTICL_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'particl-wallet') + if not cls.restore_instance: + data_dir = prepareDataDir(TEST_DIR, i, 'particl.conf', 'part_') + if os.path.exists(os.path.join(cfg.PARTICL_BINDIR, 'particl-wallet')): + callrpc_cli(cfg.PARTICL_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'particl-wallet') cls.part_daemons.append(startDaemon(os.path.join(TEST_DIR, 'part_' + str(i)), cfg.PARTICL_BINDIR, cfg.PARTICLD)) logging.info('Started %s %d', cfg.PARTICLD, cls.part_daemons[-1].pid) - for i in range(NUM_NODES): - # Load mnemonics after all nodes have started to avoid staking getting stuck in TryToSync - rpc = make_rpc_func(i) - waitForRPC(rpc) - if i == 0: - rpc('extkeyimportmaster', ['abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb']) - elif i == 1: - rpc('extkeyimportmaster', ['pact mammal barrel matrix local final lecture chunk wasp survey bid various book strong spread fall ozone daring like topple door fatigue limb olympic', '', 'true']) - rpc('getnewextaddress', ['lblExtTest']) - rpc('rescanblockchain') - else: - rpc('extkeyimportmaster', [rpc('mnemonic', ['new'])['master']]) - # Lower output split threshold for more stakeable outputs - rpc('walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}]) + if not cls.restore_instance: + for i in range(NUM_NODES): + # Load mnemonics after all nodes have started to avoid staking getting stuck in TryToSync + rpc = make_rpc_func(i) + waitForRPC(rpc) + if i == 0: + rpc('extkeyimportmaster', ['abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb']) + elif i == 1: + rpc('extkeyimportmaster', ['pact mammal barrel matrix local final lecture chunk wasp survey bid various book strong spread fall ozone daring like topple door fatigue limb olympic', '', 'true']) + rpc('getnewextaddress', ['lblExtTest']) + rpc('rescanblockchain') + else: + rpc('extkeyimportmaster', [rpc('mnemonic', ['new'])['master']]) + # Lower output split threshold for more stakeable outputs + rpc('walletsettings', ['stakingoptions', {'stakecombinethreshold': 100, 'stakesplitthreshold': 200}]) 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) - if os.path.exists(os.path.join(cfg.BITCOIN_BINDIR, 'bitcoin-wallet')): - callrpc_cli(cfg.BITCOIN_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'bitcoin-wallet') + if not cls.restore_instance: + data_dir = prepareDataDir(TEST_DIR, i, 'bitcoin.conf', 'btc_', base_p2p_port=BTC_BASE_PORT, base_rpc_port=BTC_BASE_RPC_PORT) + if os.path.exists(os.path.join(cfg.BITCOIN_BINDIR, 'bitcoin-wallet')): + callrpc_cli(cfg.BITCOIN_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'bitcoin-wallet') cls.btc_daemons.append(startDaemon(os.path.join(TEST_DIR, 'btc_' + str(i)), cfg.BITCOIN_BINDIR, cfg.BITCOIND)) logging.info('Started %s %d', cfg.BITCOIND, cls.part_daemons[-1].pid) @@ -414,29 +400,20 @@ class BaseTest(unittest.TestCase): if cls.start_ltc_nodes: for i in range(NUM_LTC_NODES): - data_dir = prepareDataDir(TEST_DIR, i, 'litecoin.conf', 'ltc_', base_p2p_port=LTC_BASE_PORT, base_rpc_port=LTC_BASE_RPC_PORT) - if os.path.exists(os.path.join(cfg.LITECOIN_BINDIR, 'litecoin-wallet')): - callrpc_cli(cfg.LITECOIN_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'litecoin-wallet') + if not cls.restore_instance: + data_dir = prepareDataDir(TEST_DIR, i, 'litecoin.conf', 'ltc_', base_p2p_port=LTC_BASE_PORT, base_rpc_port=LTC_BASE_RPC_PORT) + if os.path.exists(os.path.join(cfg.LITECOIN_BINDIR, 'litecoin-wallet')): + callrpc_cli(cfg.LITECOIN_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'litecoin-wallet') cls.ltc_daemons.append(startDaemon(os.path.join(TEST_DIR, 'ltc_' + str(i)), cfg.LITECOIN_BINDIR, cfg.LITECOIND)) logging.info('Started %s %d', cfg.LITECOIND, cls.part_daemons[-1].pid) waitForRPC(make_rpc_func(i, base_rpc_port=LTC_BASE_RPC_PORT)) - if cls.start_pivx_nodes: - for i in range(NUM_PIVX_NODES): - data_dir = prepareDataDir(TEST_DIR, i, 'pivx.conf', 'pivx_', base_p2p_port=PIVX_BASE_PORT, base_rpc_port=PIVX_BASE_RPC_PORT) - if os.path.exists(os.path.join(cfg.PIVX_BINDIR, 'pivx-wallet')): - callrpc_cli(cfg.PIVX_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'pivx-wallet') - - cls.pivx_daemons.append(startDaemon(os.path.join(TEST_DIR, 'pivx_' + str(i)), cfg.PIVX_BINDIR, cfg.PIVXD)) - logging.info('Started %s %d', cfg.PIVXD, cls.part_daemons[-1].pid) - - waitForRPC(make_rpc_func(i, base_rpc_port=PIVX_BASE_RPC_PORT)) - if cls.start_xmr_nodes: for i in range(NUM_XMR_NODES): - prepareXmrDataDir(TEST_DIR, i, 'monerod.conf') + if not cls.restore_instance: + prepareXmrDataDir(TEST_DIR, i, 'monerod.conf') cls.xmr_daemons.append(startXmrDaemon(os.path.join(TEST_DIR, 'xmr_' + str(i)), cfg.XMR_BINDIR, cfg.XMRD)) logging.info('Started %s %d', cfg.XMRD, cls.xmr_daemons[-1].pid) @@ -450,14 +427,20 @@ class BaseTest(unittest.TestCase): waitForXMRWallet(i, cls.xmr_wallet_auth[i]) - cls.callxmrnodewallet(cls, i, 'create_wallet', {'filename': 'testwallet', 'language': 'English'}) + if not cls.restore_instance: + cls.callxmrnodewallet(cls, i, 'create_wallet', {'filename': 'testwallet', 'language': 'English'}) cls.callxmrnodewallet(cls, i, 'open_wallet', {'filename': 'testwallet'}) + for i in range(NUM_NODES): + # Hook for descendant classes + cls.prepareExtraDataDir(i) + logging.info('Preparing swap clients.') - eckey = ECKey() - eckey.generate() - cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, eckey.get_bytes()) - cls.network_pubkey = eckey.get_pubkey().get_bytes().hex() + if not cls.restore_instance: + eckey = ECKey() + eckey.generate() + cls.network_key = toWIF(PREFIX_SECRET_KEY_REGTEST, eckey.get_bytes()) + cls.network_pubkey = eckey.get_pubkey().get_bytes().hex() for i in range(NUM_NODES): start_nodes = set() @@ -465,13 +448,15 @@ class BaseTest(unittest.TestCase): start_nodes.add(Coins.LTC) if cls.start_xmr_nodes: start_nodes.add(Coins.XMR) - if cls.start_pivx_nodes: - start_nodes.add(Coins.PIVX) - prepare_swapclient_dir(TEST_DIR, i, cls.network_key, cls.network_pubkey, start_nodes) + if not cls.restore_instance: + prepare_swapclient_dir(TEST_DIR, i, cls.network_key, cls.network_pubkey, start_nodes, cls) basicswap_dir = os.path.join(os.path.join(TEST_DIR, 'basicswap_' + str(i))) settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME) with open(settings_path) as fs: settings = json.load(fs) + if cls.restore_instance and i == 1: + cls.network_key = settings['network_key'] + cls.network_pubkey = settings['network_pubkey'] fp = open(os.path.join(basicswap_dir, 'basicswap.log'), 'w') sc = BasicSwap(fp, basicswap_dir, settings, 'regtest', log_name='BasicSwap{}'.format(i)) sc.setDaemonPID(Coins.BTC, cls.btc_daemons[i].pid) @@ -479,6 +464,7 @@ class BaseTest(unittest.TestCase): if cls.start_ltc_nodes: sc.setDaemonPID(Coins.LTC, cls.ltc_daemons[i].pid) + cls.addPIDInfo(sc, i) sc.start() if cls.start_xmr_nodes: @@ -490,74 +476,75 @@ class BaseTest(unittest.TestCase): t = HttpThread(cls.swap_clients[i].fp, TEST_HTTP_HOST, TEST_HTTP_PORT + i, False, cls.swap_clients[i]) cls.http_threads.append(t) t.start() - # Set future block rewards to nowhere (a random address), so wallet amounts stay constant - eckey = ECKey() - eckey.generate() - void_block_rewards_pubkey = eckey.get_pubkey().get_bytes() + void_block_rewards_pubkey = cls.getRandomPubkey() + if cls.restore_instance: + cls.btc_addr = cls.swap_clients[0].ci(Coins.BTC).pubkey_to_segwit_address(void_block_rewards_pubkey) + if cls.start_ltc_nodes: + cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey) + if cls.start_xmr_nodes: + cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address'] + else: + cls.btc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT) + num_blocks = 400 # Mine enough to activate segwit + logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr) + callnoderpc(0, 'generatetoaddress', [num_blocks, cls.btc_addr], base_rpc_port=BTC_BASE_RPC_PORT) - cls.btc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=BTC_BASE_RPC_PORT) - num_blocks = 400 # Mine enough to activate segwit - logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr) - callnoderpc(0, 'generatetoaddress', [num_blocks, cls.btc_addr], base_rpc_port=BTC_BASE_RPC_PORT) - - # Switch addresses so wallet amounts stay constant - num_blocks = 100 - cls.btc_addr = cls.swap_clients[0].ci(Coins.BTC).pubkey_to_segwit_address(void_block_rewards_pubkey) - logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr) - callnoderpc(0, 'generatetoaddress', [num_blocks, cls.btc_addr], base_rpc_port=BTC_BASE_RPC_PORT) - - checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=BTC_BASE_RPC_PORT)) - - if cls.start_ltc_nodes: - num_blocks = 400 - cls.ltc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=LTC_BASE_RPC_PORT) - logging.info('Mining %d Litecoin blocks to %s', num_blocks, cls.ltc_addr) - callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT) - - num_blocks = 31 - cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey) - logging.info('Mining %d Litecoin blocks to %s', num_blocks, cls.ltc_addr) - callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT) - - # https://github.com/litecoin-project/litecoin/issues/807 - # Block 432 is when MWEB activates. It requires a peg-in. You'll need to generate an mweb address and send some coins to it. Then it will allow you to mine the next block. - mweb_addr = callnoderpc(2, 'getnewaddress', ['mweb_addr', 'mweb'], base_rpc_port=LTC_BASE_RPC_PORT) - callnoderpc(0, 'sendtoaddress', [mweb_addr, 1], base_rpc_port=LTC_BASE_RPC_PORT) - - num_blocks = 69 - cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey) - callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT) - - checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=LTC_BASE_RPC_PORT)) - - if cls.start_pivx_nodes: - num_blocks = 400 - cls.pivx_addr = callnoderpc(0, 'getnewaddress', ['mining_addr'], base_rpc_port=PIVX_BASE_RPC_PORT) - logging.info('Mining %d PIVX blocks to %s', num_blocks, cls.pivx_addr) - callnoderpc(0, 'generatetoaddress', [num_blocks, cls.pivx_addr], base_rpc_port=PIVX_BASE_RPC_PORT) + btc_addr1 = callnoderpc(1, 'getnewaddress', ['initial addr'], base_rpc_port=BTC_BASE_RPC_PORT) + for i in range(5): + callnoderpc(0, 'sendtoaddress', [btc_addr1, 100], base_rpc_port=BTC_BASE_RPC_PORT) # Switch addresses so wallet amounts stay constant num_blocks = 100 - cls.pivx_addr = cls.swap_clients[0].ci(Coins.PIVX).pubkey_to_address(void_block_rewards_pubkey) - logging.info('Mining %d PIVX blocks to %s', num_blocks, cls.pivx_addr) - callnoderpc(0, 'generatetoaddress', [num_blocks, cls.pivx_addr], base_rpc_port=PIVX_BASE_RPC_PORT) + cls.btc_addr = cls.swap_clients[0].ci(Coins.BTC).pubkey_to_segwit_address(void_block_rewards_pubkey) + logging.info('Mining %d Bitcoin blocks to %s', num_blocks, cls.btc_addr) + callnoderpc(0, 'generatetoaddress', [num_blocks, cls.btc_addr], base_rpc_port=BTC_BASE_RPC_PORT) - num_blocks = 100 - if cls.start_xmr_nodes: - cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address'] - if callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'] < num_blocks: - logging.info('Mining %d Monero blocks to %s.', num_blocks, cls.xmr_addr) - callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': num_blocks}) - logging.info('XMR blocks: %d', callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count']) + checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=BTC_BASE_RPC_PORT)) - logging.info('Adding anon outputs') - outputs = [] - for i in range(8): - sx_addr = callnoderpc(1, 'getnewstealthaddress') - outputs.append({'address': sx_addr, 'amount': 0.5}) - for i in range(6): - callnoderpc(0, 'sendtypeto', ['part', 'anon', outputs]) + if cls.start_ltc_nodes: + num_blocks = 400 + cls.ltc_addr = callnoderpc(0, 'getnewaddress', ['mining_addr', 'bech32'], base_rpc_port=LTC_BASE_RPC_PORT) + logging.info('Mining %d Litecoin blocks to %s', num_blocks, cls.ltc_addr) + callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT) + + num_blocks = 31 + cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey) + logging.info('Mining %d Litecoin blocks to %s', num_blocks, cls.ltc_addr) + callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT) + + # https://github.com/litecoin-project/litecoin/issues/807 + # Block 432 is when MWEB activates. It requires a peg-in. You'll need to generate an mweb address and send some coins to it. Then it will allow you to mine the next block. + mweb_addr = callnoderpc(2, 'getnewaddress', ['mweb_addr', 'mweb'], base_rpc_port=LTC_BASE_RPC_PORT) + callnoderpc(0, 'sendtoaddress', [mweb_addr, 1], base_rpc_port=LTC_BASE_RPC_PORT) + + num_blocks = 69 + cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey) + callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT) + + checkForks(callnoderpc(0, 'getblockchaininfo', base_rpc_port=LTC_BASE_RPC_PORT)) + + num_blocks = 100 + if cls.start_xmr_nodes: + cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address'] + if callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'] < num_blocks: + logging.info('Mining %d Monero blocks to %s.', num_blocks, cls.xmr_addr) + callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': num_blocks}) + logging.info('XMR blocks: %d', callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count']) + + logging.info('Adding anon outputs') + outputs = [] + for i in range(8): + sx_addr = callnoderpc(1, 'getnewstealthaddress') + outputs.append({'address': sx_addr, 'amount': 0.5}) + for i in range(6): + callnoderpc(0, 'sendtypeto', ['part', 'anon', outputs]) + + part_addr1 = callnoderpc(1, 'getnewaddress', ['initial addr']) + part_addr2 = callnoderpc(1, 'getnewaddress', ['initial addr 2']) + callnoderpc(0, 'sendtypeto', ['part', 'part', [{'address': part_addr1, 'amount': 100}, {'address': part_addr2, 'amount': 100}]]) + + cls.prepareExtraCoins() logging.info('Starting update thread.') signal.signal(signal.SIGINT, signal_handler) @@ -599,13 +586,40 @@ class BaseTest(unittest.TestCase): stopDaemons(cls.part_daemons) stopDaemons(cls.btc_daemons) stopDaemons(cls.ltc_daemons) - stopDaemons(cls.pivx_daemons) super(BaseTest, cls).tearDownClass() + @classmethod + def addCoinSettings(cls, settings, datadir, node_id): + pass + + @classmethod + def prepareExtraDataDir(cls, i): + pass + + @classmethod + def addPIDInfo(cls, sc, i): + pass + + @classmethod + def prepareExtraCoins(cls): + pass + + @classmethod + def coins_loop(cls): + if cls.btc_addr is not None: + btcCli('generatetoaddress 1 {}'.format(cls.btc_addr)) + if cls.ltc_addr is not None: + ltcCli('generatetoaddress 1 {}'.format(cls.ltc_addr)) + if cls.xmr_addr is not None: + callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1}) + def callxmrnodewallet(self, node_id, method, params=None): return callrpc_xmr(XMR_BASE_WALLET_RPC_PORT + node_id, self.xmr_wallet_auth[node_id], method, params) + def getXmrBalance(self, js_wallets): + return float(js_wallets[Coins.XMR.name]['unconfirmed']) + float(js_wallets[Coins.XMR.name]['balance']) + class Test(BaseTest): __test__ = True @@ -617,9 +631,9 @@ class Test(BaseTest): logging.info('---------- Test PART to XMR') swap_clients = self.swap_clients + start_xmr_amount = self.getXmrBalance(read_json_api(1800, 'wallets')) js_1 = read_json_api(1801, 'wallets') - assert (make_int(js_1[Coins.XMR.name]['balance'], scale=12) > 0) - assert (make_int(js_1[Coins.XMR.name]['unconfirmed'], scale=12) > 0) + assert (self.getXmrBalance(js_1) > 0.0) offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 100 * COIN, 0.11 * XMR_COIN, 100 * COIN, SwapTypes.XMR_SWAP) wait_for_offer(test_delay_event, swap_clients[1], offer_id) @@ -640,8 +654,9 @@ class Test(BaseTest): wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True) js_0_end = read_json_api(1800, 'wallets') - end_xmr = float(js_0_end['XMR']['balance']) + float(js_0_end['XMR']['unconfirmed']) - assert (end_xmr > 10.9 and end_xmr < 11.0) + end_xmr_amount = self.getXmrBalance(js_0_end) + xmr_amount_diff = end_xmr_amount - start_xmr_amount + assert (xmr_amount_diff > 10.9 and xmr_amount_diff < 11.0) bid_id_hex = bid_id.hex() path = f'bids/{bid_id_hex}/states' From c440f9e3a3dfc0a40b7d76be3d30d9c0e00af935 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Tue, 8 Nov 2022 16:43:28 +0200 Subject: [PATCH 2/7] coins: Fix Firo seedid --- basicswap/basicswap.py | 2 +- basicswap/interface/btc.py | 3 +++ basicswap/interface/firo.py | 3 +++ tests/basicswap/extended/test_firo.py | 23 +++++++++++++++++++++-- tests/basicswap/test_btc_xmr.py | 12 ++++++++++++ 5 files changed, 40 insertions(+), 3 deletions(-) diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index ac11c27..22106b5 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -727,7 +727,7 @@ class BasicSwap(BaseApp): return root_key = self.getWalletKey(coin_type, 1) - root_hash = ci.getAddressHashFromKey(root_key)[::-1] + root_hash = ci.getSeedHash(root_key) try: ci.initialiseWallet(root_key) diff --git a/basicswap/interface/btc.py b/basicswap/interface/btc.py index aeff269..1115f42 100644 --- a/basicswap/interface/btc.py +++ b/basicswap/interface/btc.py @@ -371,6 +371,9 @@ class BTCInterface(CoinInterface): pk = self.getPubkey(key) return hash160(pk) + def getSeedHash(self, seed): + return self.getAddressHashFromKey(seed)[::-1] + def verifyKey(self, k): i = b2i(k) return (i < ep.o and i > 0) diff --git a/basicswap/interface/firo.py b/basicswap/interface/firo.py index a6e46d8..4ec421e 100644 --- a/basicswap/interface/firo.py +++ b/basicswap/interface/firo.py @@ -156,6 +156,9 @@ class FIROInterface(BTCInterface): return CScript([OP_HASH160, script_hash_hash, OP_EQUAL]) + def getSeedHash(self, seed): + return hash160(seed)[::-1] + def encodeScriptDest(self, script): # Extract hash from script script_hash = script[2:-1] diff --git a/tests/basicswap/extended/test_firo.py b/tests/basicswap/extended/test_firo.py index 638beb8..1e00ee7 100644 --- a/tests/basicswap/extended/test_firo.py +++ b/tests/basicswap/extended/test_firo.py @@ -112,6 +112,13 @@ class Test(BaseTest): test_atomic = True test_xmr = False + # Particl node mnemonics are set in test/basicswap/mnemonics.py + firo_seeds = [ + 'd90b7ed1be614e1c172653aee1f3b6230f43b7fa99cf07fa984a17966ad81de7', + '6c81d6d74ba33a0db9e41518c2b6789fbe938e98018a4597dac661cfc5f2dfc1', + 'c5de2be44834e7e47ad7dc8e35c6b77c79f17c6bb40d5509a00fc3dff384a865', + ] + @classmethod def setUpClass(cls): cls.start_ltc_nodes = False @@ -121,11 +128,13 @@ class Test(BaseTest): @classmethod def prepareExtraDataDir(cls, i): if not cls.restore_instance: + seed_hex = cls.firo_seeds[i] + extra_opts = [f'-hdseed={seed_hex}', ] data_dir = prepareDataDir(cfg.TEST_DATADIRS, i, 'firo.conf', 'firo_', base_p2p_port=FIRO_BASE_PORT, base_rpc_port=FIRO_BASE_RPC_PORT) if os.path.exists(os.path.join(cfg.FIRO_BINDIR, 'firo-wallet')): callrpc_cli(cfg.FIRO_BINDIR, data_dir, 'regtest', '-wallet=wallet.dat create', 'firo-wallet') - cls.firo_daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, 'firo_' + str(i)), cfg.FIRO_BINDIR, cfg.FIROD)) + cls.firo_daemons.append(startDaemon(os.path.join(cfg.TEST_DATADIRS, 'firo_' + str(i)), cfg.FIRO_BINDIR, cfg.FIROD, opts=extra_opts)) logging.info('Started %s %d', cfg.FIROD, cls.part_daemons[-1].pid) waitForRPC(make_rpc_func(i, base_rpc_port=FIRO_BASE_RPC_PORT)) @@ -193,7 +202,7 @@ class Test(BaseTest): def callnoderpc(self, method, params=[], wallet=None, node_id=0): return callnoderpc(node_id, method, params, wallet, base_rpc_port=FIRO_BASE_RPC_PORT) - def test_01_firo(self): + def test_001_firo(self): logging.info('---------- Test {} segwit'.format(self.test_coin_from.name)) ''' @@ -244,6 +253,16 @@ class Test(BaseTest): assert tx_funded_decoded['txid'] != tx_signed_decoded['txid'] assert txid_with_scriptsig == tx_signed_decoded['txid'] + def test_007_hdwallet(self): + logging.info('---------- Test {} hdwallet'.format(self.test_coin_from.name)) + + swap_client = self.swap_clients[0] + # Run initialiseWallet to set 'main_wallet_seedid_' + swap_client.initialiseWallet(self.test_coin_from) + ci = swap_client.ci(self.test_coin_from) + assert ('490ba1e2c3894d5534c467141ee3cdf77292c362' == ci.getWalletSeedID()) + assert swap_client.checkWalletSeed(self.test_coin_from) is True + def test_02_part_coin(self): logging.info('---------- Test PART to {}'.format(self.test_coin_from.name)) if not self.test_atomic: diff --git a/tests/basicswap/test_btc_xmr.py b/tests/basicswap/test_btc_xmr.py index 9a48cb7..7e7d964 100644 --- a/tests/basicswap/test_btc_xmr.py +++ b/tests/basicswap/test_btc_xmr.py @@ -236,6 +236,18 @@ class Test(BaseTest): block = self.callnoderpc('getblock', [best_hash, 2]) assert ('vin' in block['tx'][0]) + def test_007_hdwallet(self): + logging.info('---------- Test {} hdwallet'.format(self.test_coin_from.name)) + + test_seed = '8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b' + test_wif = self.swap_clients[0].ci(self.test_coin_from).encodeKey(bytes.fromhex(test_seed)) + new_wallet_name = random.randbytes(10).hex() + self.callnoderpc('createwallet', [new_wallet_name]) + self.callnoderpc('sethdseed', [True, test_wif], wallet=new_wallet_name) + addr = self.callnoderpc('getnewaddress', wallet=new_wallet_name) + 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)) swap_clients = self.swap_clients From 5a73fab045955115ed47f22bc6930c5b876cb751 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Tue, 8 Nov 2022 16:48:25 +0200 Subject: [PATCH 3/7] coins: Add temporary firo release. --- bin/basicswap_prepare.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/bin/basicswap_prepare.py b/bin/basicswap_prepare.py index 3551e8b..07ee0e9 100755 --- a/bin/basicswap_prepare.py +++ b/bin/basicswap_prepare.py @@ -53,7 +53,7 @@ PIVX_VERSION_TAG = os.getenv('PIVX_VERSION_TAG', '_scantxoutset') DASH_VERSION = os.getenv('DASH_VERSION', '18.1.0') DASH_VERSION_TAG = os.getenv('DASH_VERSION_TAG', '') -FIRO_VERSION = os.getenv('FIRO_VERSION', '0.14.11.1') +FIRO_VERSION = os.getenv('FIRO_VERSION', '0.14.99.1') FIRO_VERSION_TAG = os.getenv('FIRO_VERSION_TAG', '') @@ -65,7 +65,8 @@ known_coins = { 'monero': (MONERO_VERSION, MONERO_VERSION_TAG, ('binaryfate',)), 'pivx': (PIVX_VERSION, PIVX_VERSION_TAG, ('tecnovert',)), 'dash': (DASH_VERSION, DASH_VERSION_TAG, ('pasta',)), - 'firo': (FIRO_VERSION, FIRO_VERSION_TAG, ('reuben',)), + # 'firo': (FIRO_VERSION, FIRO_VERSION_TAG, ('reuben',)), + 'firo': (FIRO_VERSION, FIRO_VERSION_TAG, ('tecnovert',)), } expected_key_ids = { @@ -443,7 +444,7 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}): assert_filename = '{}-{}-{}-build.assert'.format(coin, os_name, major_version) assert_url = 'https://raw.githubusercontent.com/dashpay/gitian.sigs/master/%s-%s/%s/%s' % (version + version_tag, os_dir_name, signing_key_name, assert_filename) elif coin == 'firo': - raise ValueError('TODO: scantxoutset release') + ''' if BIN_ARCH == 'x86_64-linux-gnu': arch_name = 'linux64' file_ext = 'tar.gz' @@ -454,8 +455,17 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}): else: raise ValueError('Firo: Unknown architecture') release_filename = '{}-{}-{}{}.{}'.format('firo', version + version_tag, arch_name, filename_extra, file_ext) - release_url = 'https://github.com/firoorg/firo/releases/download/v{}/{}'.format(version + version_tag, release_filename) - assert_url = 'https://github.com/firoorg/firo/releases/download/v%s/SHA256SUMS' % (version + version_tag) + # release_url = 'https://github.com/firoorg/firo/releases/download/v{}/{}'.format(version + version_tag, release_filename) + # assert_url = 'https://github.com/firoorg/firo/releases/download/v%s/SHA256SUMS' % (version + version_tag) + ''' + if BIN_ARCH == 'x86_64-linux-gnu': + release_filename = 'firo-0.14.99.1-x86_64-linux-gnu.tar.gz' + elif BIN_ARCH == 'osx64': + release_filename = 'firo-0.14.99.1-osx-unsigned.tar.gz' + else: + raise ValueError('Firo: Unknown architecture') + release_url = 'https://github.com/tecnovert/particl-core/releases/download/v{}/{}'.format(version + version_tag, release_filename) + assert_url = 'https://github.com/tecnovert/particl-core/releases/download/v%s/SHA256SUMS.asc' % (version + version_tag) else: raise ValueError('Unknown coin') @@ -505,7 +515,7 @@ def prepareCore(coin, version_data, settings, data_dir, extra_opts={}): for key in rv.fingerprints: gpg.trust_keys(rv.fingerprints[0], 'TRUST_FULLY') - if coin == 'pivx': + if coin in ('pivx', 'firo'): pubkey_filename = '{}_{}.pgp'.format('particl', signing_key_name) else: pubkey_filename = '{}_{}.pgp'.format(coin, signing_key_name) From 8b2d2b446bc67d5ab2b169e916a36f03536b6fef Mon Sep 17 00:00:00 2001 From: tecnovert Date: Tue, 8 Nov 2022 22:30:28 +0200 Subject: [PATCH 4/7] api: Add show_extra parameter to bids endpoint Add itx_refund_tx_est_final and ptx_refund_tx_est_final to bid extra data --- basicswap/basicswap.py | 26 ++++++++++++++++---------- basicswap/js_server.py | 5 ++++- basicswap/ui/util.py | 10 ++++++++++ doc/api.md | 8 ++++++++ 4 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 doc/api.md diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 22106b5..a562bcd 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -1825,6 +1825,15 @@ class BasicSwap(BaseApp): session.remove() self.mxDB.release() + def setTxBlockInfoFromHeight(self, ci, tx, height): + try: + tx.block_height = height + block_header = ci.getBlockHeaderFromHeight(height) + tx.block_hash = bytes.fromhex(block_header['hash']) + tx.block_time = block_header['time'] # Or median_time? + except Exception as e: + self.log.warning(f'setTxBlockInfoFromHeight failed {e}') + def loadBidTxns(self, bid, session): bid.txns = {} for stx in session.query(SwapTx).filter(sa.and_(SwapTx.bid_id == bid.bid_id)): @@ -3009,11 +3018,7 @@ class BasicSwap(BaseApp): if not bid.xmr_a_lock_tx.chain_height and lock_tx_chain_info['height'] != 0: self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_SEEN, '', session) - - block_header = ci_from.getBlockHeaderFromHeight(lock_tx_chain_info['height']) - bid.xmr_a_lock_tx.block_hash = bytes.fromhex(block_header['hash']) - bid.xmr_a_lock_tx.block_height = block_header['height'] - bid.xmr_a_lock_tx.block_time = block_header['time'] # Or median_time? + self.setTxBlockInfoFromHeight(ci_from, bid.xmr_a_lock_tx, lock_tx_chain_info['height']) bid_changed = True if bid.xmr_a_lock_tx.chain_height != lock_tx_chain_info['height'] and lock_tx_chain_info['height'] != 0: @@ -3121,10 +3126,7 @@ class BasicSwap(BaseApp): lock_refund_tx_chain_info = ci_from.getLockTxHeight(refund_tx.txid, refund_tx_addr, 0, bid.chain_a_height_start) if lock_refund_tx_chain_info is not None and lock_refund_tx_chain_info.get('height', 0) > 0: - block_header = ci_from.getBlockHeaderFromHeight(lock_refund_tx_chain_info['height']) - refund_tx.block_hash = bytes.fromhex(block_header['hash']) - refund_tx.block_height = block_header['height'] - refund_tx.block_time = block_header['time'] # Or median_time? + self.setTxBlockInfoFromHeight(ci_from, refund_tx, lock_refund_tx_chain_info['height']) self.saveBidInSession(bid_id, bid, session, xmr_swap) session.commit() @@ -3167,6 +3169,7 @@ class BasicSwap(BaseApp): index = None tx_height = None last_initiate_txn_conf = bid.initiate_tx.conf + ci_from = self.ci(coin_from) if coin_from == Coins.PART: # Has txindex try: initiate_txn = self.callcoinrpc(coin_from, 'getrawtransaction', [initiate_txnid_hex, True]) @@ -3190,7 +3193,6 @@ class BasicSwap(BaseApp): else: addr = p2sh - ci_from = self.ci(coin_from) found = ci_from.getLockTxHeight(bytes.fromhex(initiate_txnid_hex), addr, bid.amount, bid.chain_a_height_start, find_index=True) if found: bid.initiate_tx.conf = found['depth'] @@ -3207,6 +3209,8 @@ class BasicSwap(BaseApp): bid.initiate_tx.vout = index # Start checking for spends of initiate_txn before fully confirmed bid.initiate_tx.chain_height = self.setLastHeightChecked(coin_from, tx_height) + self.setTxBlockInfoFromHeight(ci_from, bid.initiate_tx, tx_height) + self.addWatchedOutput(coin_from, bid_id, initiate_txnid_hex, bid.initiate_tx.vout, BidStates.SWAP_INITIATED) if bid.getITxState() is None or bid.getITxState() < TxStates.TX_SENT: bid.setITxState(TxStates.TX_SENT) @@ -3243,6 +3247,8 @@ class BasicSwap(BaseApp): self.addParticipateTxn(bid_id, bid, coin_to, found['txid'], found['index'], found['height']) bid.setPTxState(TxStates.TX_SENT) save_bid = True + if found['height'] > 0 and bid.participate_tx.block_height is None: + self.setTxBlockInfoFromHeight(ci_to, bid.participate_tx, found['height']) if bid.participate_tx.conf is not None: self.log.debug('participate txid %s confirms %d', bid.participate_tx.txid.hex(), bid.participate_tx.conf) diff --git a/basicswap/js_server.py b/basicswap/js_server.py index a5ce8ff..4f736a7 100644 --- a/basicswap/js_server.py +++ b/basicswap/js_server.py @@ -241,6 +241,7 @@ def js_bids(self, url_split, post_string, is_json): bid_id = bytes.fromhex(url_split[3]) assert (len(bid_id) == 28) + show_txns = False if post_string != '': if is_json: post_data = json.loads(post_string) @@ -252,6 +253,9 @@ def js_bids(self, url_split, post_string, is_json): elif have_data_entry(post_data, 'debugind'): swap_client.setBidDebugInd(bid_id, int(get_data_entry(post_data, 'debugind'))) + if have_data_entry(post_data, 'show_extra'): + show_txns = True + bid, xmr_swap, offer, xmr_offer, events = swap_client.getXmrBidAndOffer(bid_id) assert (bid), 'Unknown bid ID' @@ -267,7 +271,6 @@ def js_bids(self, url_split, post_string, is_json): return bytes(json.dumps(old_states), 'UTF-8') edit_bid = False - show_txns = False data = describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, events, edit_bid, show_txns, for_api=True) return bytes(json.dumps(data), 'UTF-8') diff --git a/basicswap/ui/util.py b/basicswap/ui/util.py index 4411f49..11ceb14 100644 --- a/basicswap/ui/util.py +++ b/basicswap/ui/util.py @@ -356,6 +356,16 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b if 'view_tx_hex' in data: data['view_tx_desc'] = json.dumps(ci_from.describeTx(data['view_tx_hex']), indent=4) + else: + if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME: + if bid.initiate_tx and bid.initiate_tx.block_time is not None: + raw_sequence = ci_from.getExpectedSequence(offer.lock_type, offer.lock_value) + seconds_locked = ci_from.decodeSequence(raw_sequence) + data['itx_refund_tx_est_final'] = bid.initiate_tx.block_time + seconds_locked + if bid.participate_tx and bid.participate_tx.block_time is not None: + raw_sequence = ci_to.getExpectedSequence(offer.lock_type, offer.lock_value // 2) + seconds_locked = ci_to.decodeSequence(raw_sequence) + data['ptx_refund_tx_est_final'] = bid.participate_tx.block_time + seconds_locked return data diff --git a/doc/api.md b/doc/api.md new file mode 100644 index 0000000..f79c3d5 --- /dev/null +++ b/doc/api.md @@ -0,0 +1,8 @@ + + +Examples: + + curl --header "Content-Type: application/json" \ + --request POST \ + --data '{"show_extra":true}' \ + http://localhost:12701/json/bids/00000000636ab87a5c8950b66684e86b5ed3684f175c8d05a8f0bfb6 From ae2ddc40496527903790ae5fbc9b51e14bb94289 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Tue, 8 Nov 2022 23:07:58 +0200 Subject: [PATCH 5/7] Log events for all sent transactions. --- basicswap/basicswap.py | 5 +++++ basicswap/basicswap_util.py | 18 ++++++++++++++++++ basicswap/protocols/atomic_swap_1.py | 7 +++++++ 3 files changed, 30 insertions(+) diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index a562bcd..ead0982 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -2040,6 +2040,7 @@ class BasicSwap(BaseApp): script=script, ) bid.setITxState(TxStates.TX_SENT) + self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.ITX_PUBLISHED, '', None) # Check non-bip68 final try: @@ -2758,6 +2759,7 @@ class BasicSwap(BaseApp): txid = self.ci(coin_to).publishTx(bytes.fromhex(txn)) self.log.debug('Submitted participate txn %s to %s chain for bid %s', txid, chainparams[coin_to]['name'], bid_id.hex()) bid.setPTxState(TxStates.TX_SENT) + self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.PTX_PUBLISHED, '', None) else: bid.participate_tx = SwapTx( bid_id=bid_id, @@ -2811,6 +2813,7 @@ class BasicSwap(BaseApp): txn = self.createRedeemTxn(ci_to.coin_type(), bid) txid = ci_to.publishTx(bytes.fromhex(txn)) self.log.debug('Submitted participate redeem txn %s to %s chain for bid %s', txid, ci_to.coin_name(), bid_id.hex()) + self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.PTX_REDEEM_PUBLISHED, '', None) # TX_REDEEMED will be set when spend is detected # TODO: Wait for depth? @@ -3291,6 +3294,7 @@ class BasicSwap(BaseApp): try: txid = ci_from.publishTx(bid.initiate_txn_refund) self.log.debug('Submitted initiate refund txn %s to %s chain for bid %s', txid, chainparams[coin_from]['name'], bid_id.hex()) + self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.ITX_REFUND_PUBLISHED, '', None) # State will update when spend is detected except Exception as ex: if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex): @@ -3301,6 +3305,7 @@ class BasicSwap(BaseApp): try: txid = ci_to.publishTx(bid.participate_txn_refund) self.log.debug('Submitted participate refund txn %s to %s chain for bid %s', txid, chainparams[coin_to]['name'], bid_id.hex()) + self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.PTX_REFUND_PUBLISHED, '', None) # State will update when spend is detected except Exception as ex: if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex): diff --git a/basicswap/basicswap_util.py b/basicswap/basicswap_util.py index bc25a45..00b414f 100644 --- a/basicswap/basicswap_util.py +++ b/basicswap/basicswap_util.py @@ -162,6 +162,12 @@ class EventLogTypes(IntEnum): ERROR = auto() AUTOMATION_CONSTRAINT = auto() AUTOMATION_ACCEPTING_BID = auto() + ITX_PUBLISHED = auto() + ITX_REDEEM_PUBLISHED = auto() + ITX_REFUND_PUBLISHED = auto() + PTX_PUBLISHED = auto() + PTX_REDEEM_PUBLISHED = auto() + PTX_REFUND_PUBLISHED = auto() class XmrSplitMsgTypes(IntEnum): @@ -357,6 +363,18 @@ def describeEventEntry(event_type, event_msg): return 'Failed auto accepting' if event_type == EventLogTypes.AUTOMATION_ACCEPTING_BID: return 'Auto accepting' + if event_type == EventLogTypes.ITX_PUBLISHED: + return 'Initiate tx published' + if event_type == EventLogTypes.ITX_REDEEM_PUBLISHED: + return 'Initiate tx redeem tx published' + if event_type == EventLogTypes.ITX_REFUND_PUBLISHED: + return 'Initiate tx refund tx published' + if event_type == EventLogTypes.PTX_PUBLISHED: + return 'Participate tx published' + if event_type == EventLogTypes.PTX_REDEEM_PUBLISHED: + return 'Participate tx redeem tx published' + if event_type == EventLogTypes.PTX_REFUND_PUBLISHED: + return 'Participate tx refund tx published' def getVoutByAddress(txjs, p2sh): diff --git a/basicswap/protocols/atomic_swap_1.py b/basicswap/protocols/atomic_swap_1.py index 62774ae..325f3ef 100644 --- a/basicswap/protocols/atomic_swap_1.py +++ b/basicswap/protocols/atomic_swap_1.py @@ -4,12 +4,18 @@ # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. +from basicswap.db import ( + Concepts, +) from basicswap.util import ( SerialiseNum, ) from basicswap.script import ( OpCodes, ) +from basicswap.basicswap_util import ( + EventLogTypes, +) INITIATE_TX_TIMEOUT = 40 * 60 # TODO: make variable per coin ABS_LOCK_TIME_LEEWAY = 10 * 60 @@ -59,3 +65,4 @@ def redeemITx(self, bid_id, session): bid.initiate_tx.spend_txid = bytes.fromhex(txid) self.log.debug('Submitted initiate redeem txn %s to %s chain for bid %s', txid, ci_from.coin_name(), bid_id.hex()) + self.logEvent(Concepts.BID, bid_id, EventLogTypes.ITX_REDEEM_PUBLISHED, '', session) From f65ae07cf98bbea3ebc2481947c87e3866650da8 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Wed, 9 Nov 2022 19:10:17 +0200 Subject: [PATCH 6/7] docker: Add PIVX, Dash and Firo to isolated config Changed pivx-params location to PIVX datadir. Still need a way to set the Firo wallet seed. --- bin/basicswap_prepare.py | 32 +++++++++++-------- .../production/compose-fragments/1_dash.yml | 16 ++++++++++ .../production/compose-fragments/1_firo.yml | 16 ++++++++++ .../production/compose-fragments/1_pivx.yml | 16 ++++++++++ docker/production/dash/Dockerfile | 27 ++++++++++++++++ docker/production/dash/entrypoint.sh | 11 +++++++ docker/production/example.env | 18 +++++++++++ docker/production/firo/Dockerfile | 25 +++++++++++++++ docker/production/firo/entrypoint.sh | 11 +++++++ docker/production/notes.md | 14 ++++++++ docker/production/pivx/Dockerfile | 25 +++++++++++++++ docker/production/pivx/entrypoint.sh | 11 +++++++ 12 files changed, 208 insertions(+), 14 deletions(-) create mode 100644 docker/production/compose-fragments/1_dash.yml create mode 100644 docker/production/compose-fragments/1_firo.yml create mode 100644 docker/production/compose-fragments/1_pivx.yml create mode 100644 docker/production/dash/Dockerfile create mode 100755 docker/production/dash/entrypoint.sh create mode 100644 docker/production/firo/Dockerfile create mode 100755 docker/production/firo/entrypoint.sh create mode 100644 docker/production/pivx/Dockerfile create mode 100755 docker/production/pivx/entrypoint.sh diff --git a/bin/basicswap_prepare.py b/bin/basicswap_prepare.py index 07ee0e9..f56cdee 100755 --- a/bin/basicswap_prepare.py +++ b/bin/basicswap_prepare.py @@ -159,6 +159,9 @@ TEST_ONION_LINK = toBool(os.getenv('TEST_ONION_LINK', 'false')) BITCOIN_FASTSYNC_URL = os.getenv('BITCOIN_FASTSYNC_URL', 'http://utxosets.blob.core.windows.net/public/') BITCOIN_FASTSYNC_FILE = os.getenv('BITCOIN_FASTSYNC_FILE', 'utxo-snapshot-bitcoin-mainnet-720179.tar') +# Set to false when running individual docker containers +START_DAEMONS = toBool(os.getenv('START_DAEMONS', 'true')) + use_tor_proxy = False default_socket = socket.socket @@ -688,11 +691,11 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}): elif coin == 'namecoin': fp.write('prune=2000\n') elif coin == 'pivx': - base_dir = extra_opts.get('data_dir', data_dir) - params_dir = os.path.join(base_dir, 'pivx-params') + params_dir = os.path.join(data_dir, 'pivx-params') downloadPIVXParams(params_dir) fp.write('prune=4000\n') - fp.write(f'paramsdir={params_dir}\n') + PIVX_PARAMSDIR = os.getenv('PIVX_PARAMSDIR', params_dir) + fp.write(f'paramsdir={PIVX_PARAMSDIR}\n') if PIVX_RPC_USER != '': fp.write('rpcauth={}:{}${}\n'.format(PIVX_RPC_USER, salt, password_to_hmac(salt, PIVX_RPC_PWD))) elif coin == 'dash': @@ -929,19 +932,20 @@ def initialise_wallets(particl_wallet_mnemonic, with_coins, data_dir, settings, coin_settings = settings['chainclients'][coin_name] c = swap_client.getCoinIdFromName(coin_name) - if c == Coins.XMR: - if coin_settings['manage_wallet_daemon']: - daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], 'monero-wallet-rpc')) - else: - if coin_settings['manage_daemon']: - filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '') - coin_args = ['-nofindpeers', '-nostaking'] if c == Coins.PART else [] + if START_DAEMONS: + if c == Coins.XMR: + if coin_settings['manage_wallet_daemon']: + daemons.append(startXmrWalletDaemon(coin_settings['datadir'], coin_settings['bindir'], 'monero-wallet-rpc')) + else: + if coin_settings['manage_daemon']: + filename = coin_name + 'd' + ('.exe' if os.name == 'nt' else '') + coin_args = ['-nofindpeers', '-nostaking'] if c == Coins.PART else [] - if c == Coins.FIRO: - coin_args += ['-hdseed={}'.format(swap_client.getWalletKey(Coins.FIRO, 1).hex())] + if c == Coins.FIRO: + coin_args += ['-hdseed={}'.format(swap_client.getWalletKey(Coins.FIRO, 1).hex())] - daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args)) - swap_client.setDaemonPID(c, daemons[-1].pid) + daemons.append(startDaemon(coin_settings['datadir'], coin_settings['bindir'], filename, daemon_args + coin_args)) + swap_client.setDaemonPID(c, daemons[-1].pid) swap_client.setCoinRunParams(c) swap_client.createCoinInterface(c) diff --git a/docker/production/compose-fragments/1_dash.yml b/docker/production/compose-fragments/1_dash.yml new file mode 100644 index 0000000..924887e --- /dev/null +++ b/docker/production/compose-fragments/1_dash.yml @@ -0,0 +1,16 @@ + dash_core: + image: i_dash + build: + context: dash + dockerfile: Dockerfile + container_name: dash_core + volumes: + - ${DATA_PATH}/dash:/data + expose: + - ${DASH_RPC_PORT} + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + restart: unless-stopped diff --git a/docker/production/compose-fragments/1_firo.yml b/docker/production/compose-fragments/1_firo.yml new file mode 100644 index 0000000..f2e4da0 --- /dev/null +++ b/docker/production/compose-fragments/1_firo.yml @@ -0,0 +1,16 @@ + firo_core: + image: i_firo + build: + context: firo + dockerfile: Dockerfile + container_name: firo_core + volumes: + - ${DATA_PATH}/firo:/data + expose: + - ${FIRO_RPC_PORT} + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + restart: unless-stopped diff --git a/docker/production/compose-fragments/1_pivx.yml b/docker/production/compose-fragments/1_pivx.yml new file mode 100644 index 0000000..e3a0ccf --- /dev/null +++ b/docker/production/compose-fragments/1_pivx.yml @@ -0,0 +1,16 @@ + pivx_core: + image: i_pivx + build: + context: pivx + dockerfile: Dockerfile + container_name: pivx_core + volumes: + - ${DATA_PATH}/pivx:/data + expose: + - ${PIVX_RPC_PORT} + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + restart: unless-stopped diff --git a/docker/production/dash/Dockerfile b/docker/production/dash/Dockerfile new file mode 100644 index 0000000..de8713c --- /dev/null +++ b/docker/production/dash/Dockerfile @@ -0,0 +1,27 @@ +# https://github.com/NicolasDorier/docker-bitcoin/blob/master/README.md + +FROM i_swapclient as install_stage + +RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=dash --withoutcoins=particl && \ + find /coin_bin -name *.tar.gz -delete + +FROM debian:bullseye-slim +COPY --from=install_stage /coin_bin . + +ENV DASH_DATA /data + +RUN groupadd -r dash && useradd -r -m -g dash dash \ + && apt-get update \ + && apt-get install -qq --no-install-recommends gosu \ + && rm -rf /var/lib/apt/lists/* \ + && mkdir "$DASH_DATA" \ + && chown -R dash:dash "$DASH_DATA" \ + && ln -sfn "$DASH_DATA" /home/dash/.dash \ + && chown -h dash:dash /home/dash/.dash +VOLUME /data + +COPY entrypoint.sh /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] + +EXPOSE 9999 9998 +CMD ["/dash/dashd", "--datadir=/data"] diff --git a/docker/production/dash/entrypoint.sh b/docker/production/dash/entrypoint.sh new file mode 100755 index 0000000..35e831a --- /dev/null +++ b/docker/production/dash/entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +if [[ "$1" == "dash-cli" || "$1" == "dash-tx" || "$1" == "dashd" || "$1" == "test_dash" ]]; then + mkdir -p "$DASH_DATA" + + chown -h dash:dash /home/dash/.dash + exec gosu dash "$@" +else + exec "$@" +fi diff --git a/docker/production/example.env b/docker/production/example.env index 024d1f0..c9a9e02 100644 --- a/docker/production/example.env +++ b/docker/production/example.env @@ -35,3 +35,21 @@ XMR_WALLET_RPC_HOST=monero_wallet BASE_XMR_WALLET_PORT=29998 XMR_WALLET_RPC_USER=xmr_wallet_user XMR_WALLET_RPC_PWD=xmr_wallet_pwd + +PIVX_DATA_DIR=/data/pivx +PIVX_RPC_HOST=pivx_core +PIVX_RPC_PORT=51473 +PIVX_RPC_USER=pivx_user +PIVX_RPC_PWD=pivx_pwd + +DASH_DATA_DIR=/data/dash +DASH_RPC_HOST=dash_core +DASH_RPC_PORT=9998 +DASH_RPC_USER=dash_user +DASH_RPC_PWD=dash_pwd + +FIRO_DATA_DIR=/data/firo +FIRO_RPC_HOST=firo_core +FIRO_RPC_PORT=8888 +FIRO_RPC_USER=firo_user +FIRO_RPC_PWD=firo_pwd diff --git a/docker/production/firo/Dockerfile b/docker/production/firo/Dockerfile new file mode 100644 index 0000000..b245a89 --- /dev/null +++ b/docker/production/firo/Dockerfile @@ -0,0 +1,25 @@ +FROM i_swapclient as install_stage + +RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=firo --withoutcoins=particl && \ + find /coin_bin -name *.tar.gz -delete + +FROM debian:bullseye-slim +COPY --from=install_stage /coin_bin . + +ENV FIRO_DATA /data + +RUN groupadd -r firo && useradd -r -m -g firo firo \ + && apt-get update \ + && apt-get install -qq --no-install-recommends gosu \ + && rm -rf /var/lib/apt/lists/* \ + && mkdir "$FIRO_DATA" \ + && chown -R firo:firo "$FIRO_DATA" \ + && ln -sfn "$FIRO_DATA" /home/firo/.firo \ + && chown -h firo:firo /home/firo/.firo +VOLUME /data + +COPY entrypoint.sh /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] + +EXPOSE 8168 8888 +CMD ["/firo/firod", "--datadir=/data"] diff --git a/docker/production/firo/entrypoint.sh b/docker/production/firo/entrypoint.sh new file mode 100755 index 0000000..9cbca08 --- /dev/null +++ b/docker/production/firo/entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +if [[ "$1" == "firo-cli" || "$1" == "firo-tx" || "$1" == "firod" || "$1" == "test_firo" ]]; then + mkdir -p "$FIRO_DATA" + + chown -h firo:firo /home/firo/.firo + exec gosu firo "$@" +else + exec "$@" +fi diff --git a/docker/production/notes.md b/docker/production/notes.md index a481ce1..36607f3 100644 --- a/docker/production/notes.md +++ b/docker/production/notes.md @@ -8,6 +8,16 @@ Copy and edit .env config: cp example.env .env +Optionally set random RPC passwords: + + for KEY in $(grep -o '^.*_RPC_PWD' .env) + do + echo "Replacing: $KEY" + NEW_PWD=$(cat /dev/random | base16 | head -c 48) + sed -i "s/${KEY}=.*$/${KEY}=${NEW_PWD}/g" .env + done + + Set the latest Monero chain height, or the height your wallet must restore from: echo "DEFAULT_XMR_RESTORE_HEIGHT=$(curl https://localmonero.co/blocks/api/get_stats | jq .height)" >> .env @@ -21,6 +31,9 @@ Create docker-compose config: cat compose-fragments/1_bitcoin.yml >> docker-compose.yml cat compose-fragments/1_litecoin.yml >> docker-compose.yml cat compose-fragments/1_monero-wallet.yml >> docker-compose.yml + cat compose-fragments/1_pivx.yml >> docker-compose.yml + cat compose-fragments/1_dash.yml >> docker-compose.yml + cat compose-fragments/1_firo.yml >> docker-compose.yml # Copy for prepare script config cp docker-compose.yml docker-compose-prepare.yml @@ -28,6 +41,7 @@ Create docker-compose config: # Add the Monero daemon if required (should not go in docker-compose-prepare.yml) cat compose-fragments/8_monero-daemon.yml >> docker-compose.yml + # Add the swapclient cat compose-fragments/8_swapclient.yml >> docker-compose.yml diff --git a/docker/production/pivx/Dockerfile b/docker/production/pivx/Dockerfile new file mode 100644 index 0000000..6e16d79 --- /dev/null +++ b/docker/production/pivx/Dockerfile @@ -0,0 +1,25 @@ +FROM i_swapclient as install_stage + +RUN basicswap-prepare --preparebinonly --bindir=/coin_bin --withcoin=pivx --withoutcoins=particl && \ + find /coin_bin -name *.tar.gz -delete + +FROM debian:bullseye-slim +COPY --from=install_stage /coin_bin . + +ENV PIVX_DATA /data + +RUN groupadd -r pivx && useradd -r -m -g pivx pivx \ + && apt-get update \ + && apt-get install -qq --no-install-recommends gosu \ + && rm -rf /var/lib/apt/lists/* \ + && mkdir "$PIVX_DATA" \ + && chown -R pivx:pivx "$PIVX_DATA" \ + && ln -sfn "$PIVX_DATA" /home/pivx/.pivx \ + && chown -h pivx:pivx /home/pivx/.pivx +VOLUME /data + +COPY entrypoint.sh /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] + +EXPOSE 51472 51473 +CMD ["/pivx/pivxd", "--datadir=/data"] diff --git a/docker/production/pivx/entrypoint.sh b/docker/production/pivx/entrypoint.sh new file mode 100755 index 0000000..e4e44ef --- /dev/null +++ b/docker/production/pivx/entrypoint.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +if [[ "$1" == "pivx-cli" || "$1" == "pivx-tx" || "$1" == "pivxd" || "$1" == "test_pivx" ]]; then + mkdir -p "$PIVX_DATA" + + chown -h pivx:pivx /home/pivx/.pivx + exec gosu pivx "$@" +else + exec "$@" +fi From 601d469801de9c15d073d4290839d7a237db25b1 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Thu, 10 Nov 2022 23:41:59 +0200 Subject: [PATCH 7/7] test: Fix test class inheritance --- tests/basicswap/extended/test_dash.py | 37 ++-- tests/basicswap/extended/test_firo.py | 40 ++-- tests/basicswap/extended/test_nmc.py | 37 ++-- tests/basicswap/extended/test_pivx.py | 37 ++-- tests/basicswap/test_btc_xmr.py | 30 +-- tests/basicswap/test_ltc_xmr.py | 259 ++++++-------------------- tests/basicswap/test_run.py | 15 +- tests/basicswap/test_xmr.py | 4 + 8 files changed, 136 insertions(+), 323 deletions(-) diff --git a/tests/basicswap/extended/test_dash.py b/tests/basicswap/extended/test_dash.py index 2b12a4d..7039a2f 100644 --- a/tests/basicswap/extended/test_dash.py +++ b/tests/basicswap/extended/test_dash.py @@ -382,11 +382,8 @@ class Test(unittest.TestCase): offer_id = swap_clients[0].postOffer(Coins.PART, Coins.DASH, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST) wait_for_offer(delay_event, swap_clients[1], offer_id) - offers = swap_clients[1].listOffers() - assert (len(offers) == 1) - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + 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) @@ -409,10 +406,8 @@ class Test(unittest.TestCase): offer_id = swap_clients[1].postOffer(Coins.DASH, Coins.PART, 10 * COIN, 9.0 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST) wait_for_offer(delay_event, swap_clients[0], offer_id) - offers = swap_clients[0].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) + offer = swap_clients[0].getOffer(offer_id) + bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) wait_for_bid(delay_event, swap_clients[1], bid_id) swap_clients[1].acceptBid(bid_id) @@ -434,10 +429,8 @@ class Test(unittest.TestCase): offer_id = swap_clients[0].postOffer(Coins.DASH, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST) wait_for_offer(delay_event, swap_clients[1], offer_id) - offers = swap_clients[1].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + 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) swap_clients[0].acceptBid(bid_id) @@ -464,10 +457,8 @@ class Test(unittest.TestCase): TxLockTypes.SEQUENCE_LOCK_BLOCKS, 10) wait_for_offer(delay_event, swap_clients[1], offer_id) - offers = swap_clients[1].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + 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) swap_clients[1].abandonBid(bid_id) @@ -490,10 +481,8 @@ class Test(unittest.TestCase): offer_id = swap_clients[0].postOffer(Coins.DASH, Coins.BTC, 10 * COIN, 10 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST) wait_for_offer(delay_event, swap_clients[0], offer_id) - offers = swap_clients[0].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) + offer = swap_clients[0].getOffer(offer_id) + bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) wait_for_bid(delay_event, swap_clients[0], bid_id) swap_clients[0].acceptBid(bid_id) @@ -514,10 +503,8 @@ class Test(unittest.TestCase): offer_id = swap_clients[0].postOffer(Coins.DASH, Coins.BTC, 0.001 * COIN, 1.0 * COIN, 0.001 * COIN, SwapTypes.SELLER_FIRST) wait_for_offer(delay_event, swap_clients[0], offer_id) - offers = swap_clients[0].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) + offer = swap_clients[0].getOffer(offer_id) + bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) wait_for_bid(delay_event, swap_clients[0], bid_id) swap_clients[0].acceptBid(bid_id) diff --git a/tests/basicswap/extended/test_firo.py b/tests/basicswap/extended/test_firo.py index 1e00ee7..c8e9112 100644 --- a/tests/basicswap/extended/test_firo.py +++ b/tests/basicswap/extended/test_firo.py @@ -273,11 +273,8 @@ class Test(BaseTest): offer_id = swap_clients[0].postOffer(Coins.PART, self.test_coin_from, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST) wait_for_offer(test_delay_event, swap_clients[1], offer_id) - offers = swap_clients[1].listOffers() - assert (len(offers) == 1) - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + 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) swap_clients[0].acceptBid(bid_id) @@ -298,10 +295,8 @@ class Test(BaseTest): offer_id = swap_clients[1].postOffer(self.test_coin_from, Coins.PART, 10 * COIN, 9.0 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST) wait_for_offer(test_delay_event, swap_clients[0], offer_id) - offers = swap_clients[0].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) + offer = swap_clients[0].getOffer(offer_id) + bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) wait_for_bid(test_delay_event, swap_clients[1], bid_id) swap_clients[1].acceptBid(bid_id) @@ -323,10 +318,8 @@ class Test(BaseTest): offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST) wait_for_offer(test_delay_event, swap_clients[1], offer_id) - offers = swap_clients[1].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + 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) swap_clients[0].acceptBid(bid_id) @@ -353,10 +346,8 @@ class Test(BaseTest): TxLockTypes.SEQUENCE_LOCK_BLOCKS, 10) wait_for_offer(test_delay_event, swap_clients[1], offer_id) - offers = swap_clients[1].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + 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) swap_clients[1].abandonBid(bid_id) @@ -379,10 +370,8 @@ class Test(BaseTest): offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.BTC, 10 * COIN, 10 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST) wait_for_offer(test_delay_event, swap_clients[0], offer_id) - offers = swap_clients[0].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) + offer = swap_clients[0].getOffer(offer_id) + bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) wait_for_bid(test_delay_event, swap_clients[0], bid_id) swap_clients[0].acceptBid(bid_id) @@ -403,10 +392,8 @@ class Test(BaseTest): offer_id = swap_clients[0].postOffer(self.test_coin_from, Coins.BTC, 0.001 * COIN, 1.0 * COIN, 0.001 * COIN, SwapTypes.SELLER_FIRST) wait_for_offer(test_delay_event, swap_clients[0], offer_id) - offers = swap_clients[0].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) + offer = swap_clients[0].getOffer(offer_id) + bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) wait_for_bid(test_delay_event, swap_clients[0], bid_id) swap_clients[0].acceptBid(bid_id) @@ -449,8 +436,7 @@ class Test(BaseTest): 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) wait_for_offer(test_delay_event, swap_clients[1], offer_id) - offers = swap_clients[0].listOffers(filters={'offer_id': offer_id}) - offer = offers[0] + offer = swap_clients[0].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) diff --git a/tests/basicswap/extended/test_nmc.py b/tests/basicswap/extended/test_nmc.py index 98800b9..fbe97f3 100644 --- a/tests/basicswap/extended/test_nmc.py +++ b/tests/basicswap/extended/test_nmc.py @@ -381,11 +381,8 @@ class Test(unittest.TestCase): offer_id = swap_clients[0].postOffer(Coins.PART, Coins.NMC, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME) wait_for_offer(delay_event, swap_clients[1], offer_id) - offers = swap_clients[1].listOffers() - assert (len(offers) == 1) - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + 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) @@ -408,10 +405,8 @@ class Test(unittest.TestCase): offer_id = swap_clients[1].postOffer(Coins.NMC, Coins.PART, 10 * COIN, 9.0 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME) wait_for_offer(delay_event, swap_clients[0], offer_id) - offers = swap_clients[0].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) + offer = swap_clients[0].getOffer(offer_id) + bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) wait_for_bid(delay_event, swap_clients[1], bid_id) swap_clients[1].acceptBid(bid_id) @@ -433,10 +428,8 @@ class Test(unittest.TestCase): offer_id = swap_clients[0].postOffer(Coins.NMC, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME) wait_for_offer(delay_event, swap_clients[1], offer_id) - offers = swap_clients[1].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + 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) swap_clients[0].acceptBid(bid_id) @@ -463,10 +456,8 @@ class Test(unittest.TestCase): TxLockTypes.ABS_LOCK_BLOCKS, 10) wait_for_offer(delay_event, swap_clients[1], offer_id) - offers = swap_clients[1].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + 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) swap_clients[1].abandonBid(bid_id) @@ -489,10 +480,8 @@ class Test(unittest.TestCase): offer_id = swap_clients[0].postOffer(Coins.NMC, Coins.BTC, 10 * COIN, 10 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME) wait_for_offer(delay_event, swap_clients[0], offer_id) - offers = swap_clients[0].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) + offer = swap_clients[0].getOffer(offer_id) + bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) wait_for_bid(delay_event, swap_clients[0], bid_id) swap_clients[0].acceptBid(bid_id) @@ -513,10 +502,8 @@ class Test(unittest.TestCase): offer_id = swap_clients[0].postOffer(Coins.NMC, Coins.BTC, 0.001 * COIN, 1.0 * COIN, 0.001 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME) wait_for_offer(delay_event, swap_clients[0], offer_id) - offers = swap_clients[0].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) + offer = swap_clients[0].getOffer(offer_id) + bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) wait_for_bid(delay_event, swap_clients[0], bid_id) swap_clients[0].acceptBid(bid_id) diff --git a/tests/basicswap/extended/test_pivx.py b/tests/basicswap/extended/test_pivx.py index 5ddb897..136b03e 100644 --- a/tests/basicswap/extended/test_pivx.py +++ b/tests/basicswap/extended/test_pivx.py @@ -392,11 +392,8 @@ class Test(unittest.TestCase): offer_id = swap_clients[0].postOffer(Coins.PART, Coins.PIVX, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME) wait_for_offer(delay_event, swap_clients[1], offer_id) - offers = swap_clients[1].listOffers() - assert (len(offers) == 1) - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + 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) @@ -419,10 +416,8 @@ class Test(unittest.TestCase): offer_id = swap_clients[1].postOffer(Coins.PIVX, Coins.PART, 10 * COIN, 9.0 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME) wait_for_offer(delay_event, swap_clients[0], offer_id) - offers = swap_clients[0].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) + offer = swap_clients[0].getOffer(offer_id) + bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) wait_for_bid(delay_event, swap_clients[1], bid_id) swap_clients[1].acceptBid(bid_id) @@ -444,10 +439,8 @@ class Test(unittest.TestCase): offer_id = swap_clients[0].postOffer(Coins.PIVX, Coins.BTC, 10 * COIN, 0.1 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME) wait_for_offer(delay_event, swap_clients[1], offer_id) - offers = swap_clients[1].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + 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) swap_clients[0].acceptBid(bid_id) @@ -474,10 +467,8 @@ class Test(unittest.TestCase): TxLockTypes.ABS_LOCK_BLOCKS, 10) wait_for_offer(delay_event, swap_clients[1], offer_id) - offers = swap_clients[1].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + 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) swap_clients[1].abandonBid(bid_id) @@ -500,10 +491,8 @@ class Test(unittest.TestCase): offer_id = swap_clients[0].postOffer(Coins.PIVX, Coins.BTC, 10 * COIN, 10 * COIN, 10 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME) wait_for_offer(delay_event, swap_clients[0], offer_id) - offers = swap_clients[0].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) + offer = swap_clients[0].getOffer(offer_id) + bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) wait_for_bid(delay_event, swap_clients[0], bid_id) swap_clients[0].acceptBid(bid_id) @@ -524,10 +513,8 @@ class Test(unittest.TestCase): offer_id = swap_clients[0].postOffer(Coins.PIVX, Coins.BTC, 0.001 * COIN, 1.0 * COIN, 0.001 * COIN, SwapTypes.SELLER_FIRST, TxLockTypes.ABS_LOCK_TIME) wait_for_offer(delay_event, swap_clients[0], offer_id) - offers = swap_clients[0].listOffers() - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) + offer = swap_clients[0].getOffer(offer_id) + bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) wait_for_bid(delay_event, swap_clients[0], bid_id) swap_clients[0].acceptBid(bid_id) diff --git a/tests/basicswap/test_btc_xmr.py b/tests/basicswap/test_btc_xmr.py index 7e7d964..bcb6e27 100644 --- a/tests/basicswap/test_btc_xmr.py +++ b/tests/basicswap/test_btc_xmr.py @@ -47,16 +47,17 @@ from .test_xmr import BaseTest, test_delay_event, callnoderpc logger = logging.getLogger() -class Test(BaseTest): - __test__ = True - test_coin_from = Coins.BTC - start_ltc_nodes = False +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 callnoderpc(self, method, params=[], wallet=None, node_id=0): - return callnoderpc(node_id, method, params, wallet, base_rpc_port=BTC_BASE_RPC_PORT) + return callnoderpc(node_id, method, params, wallet, self.base_rpc_port) + + def mineBlock(self, num_blocks=1): + self.callnoderpc('generatetoaddress', [num_blocks, self.btc_addr]) def test_001_nested_segwit(self): logging.info('---------- Test {} p2sh nested segwit'.format(self.test_coin_from.name)) @@ -68,7 +69,7 @@ class Test(BaseTest): txid = self.callnoderpc('sendtoaddress', [addr_p2sh_segwit, 1.0]) assert len(txid) == 64 - self.callnoderpc('generatetoaddress', [1, self.btc_addr]) + self.mineBlock() ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_p2sh_segwit)]]) assert (len(ro['unspents']) == 1) assert (ro['unspents'][0]['txid'] == txid) @@ -108,7 +109,7 @@ class Test(BaseTest): tx_wallet = self.callnoderpc('gettransaction', [txid, ])['hex'] tx = self.callnoderpc('decoderawtransaction', [tx_wallet, ]) - self.callnoderpc('generatetoaddress', [1, self.btc_addr]) + self.mineBlock() ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_segwit)]]) assert (len(ro['unspents']) == 1) assert (ro['unspents'][0]['txid'] == txid) @@ -161,9 +162,9 @@ class Test(BaseTest): except Exception as e: assert ('non-final' in str(e)) - self.callnoderpc('generatetoaddress', [49, self.btc_addr]) + self.mineBlock(5) txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ]) - self.callnoderpc('generatetoaddress', [1, self.btc_addr]) + self.mineBlock() ro = self.callnoderpc('listreceivedbyaddress', [0, ]) sum_addr = 0 for entry in ro: @@ -206,9 +207,9 @@ class Test(BaseTest): except Exception as e: assert ('non-BIP68-final' in str(e)) - self.callnoderpc('generatetoaddress', [3, self.btc_addr]) + self.mineBlock(3) txid = self.callnoderpc('sendrawtransaction', [tx_spend_hex, ]) - self.callnoderpc('generatetoaddress', [1, self.btc_addr]) + self.mineBlock(1) ro = self.callnoderpc('listreceivedbyaddress', [0, ]) sum_addr = 0 for entry in ro: @@ -418,5 +419,12 @@ class Test(BaseTest): assert (node1_xmr_before - node1_xmr_after < 0.02) +class TestBTC(BasicSwapTest): + __test__ = True + test_coin_from = Coins.BTC + start_ltc_nodes = False + base_rpc_port = BTC_BASE_RPC_PORT + + if __name__ == '__main__': unittest.main() diff --git a/tests/basicswap/test_ltc_xmr.py b/tests/basicswap/test_ltc_xmr.py index b1fc275..694e3d0 100644 --- a/tests/basicswap/test_ltc_xmr.py +++ b/tests/basicswap/test_ltc_xmr.py @@ -13,237 +13,98 @@ from basicswap.basicswap import ( Coins, SwapTypes, BidStates, - DebugTypes, -) -from basicswap.basicswap_util import ( - TxLockTypes, ) from basicswap.util import ( - make_int, - format_amount, + COIN, ) from tests.basicswap.common import ( wait_for_bid, read_json_api, wait_for_offer, - wait_for_none_active, + wait_for_in_progress, + LTC_BASE_RPC_PORT, ) - -''' -# Should work? - -from .test_btc_xmr import Test, test_delay_event +from .test_btc_xmr import BasicSwapTest, test_delay_event logger = logging.getLogger() -class TestLTC(Test): +class TestLTC(BasicSwapTest): __test__ = True + test_coin_from = Coins.LTC + start_ltc_nodes = True + base_rpc_port = LTC_BASE_RPC_PORT - @classmethod - def setUpClass(cls): - cls.test_coin_from = Coins.LTC - cls.start_ltc_nodes = True - super(TestLTC, cls).setUpClass() + def mineBlock(self, num_blocks=1): + self.callnoderpc('generatetoaddress', [num_blocks, self.ltc_addr]) - @classmethod - def tearDownClass(cls): - logging.info('Finalising LTC Test') - super(TestLTC, cls).tearDownClass() -''' + def test_001_nested_segwit(self): + logging.info('---------- Test {} p2sh nested segwit'.format(self.test_coin_from.name)) + logging.info('Skipped') -from .test_xmr import BaseTest, test_delay_event + def test_002_native_segwit(self): + logging.info('---------- Test {} p2sh native segwit'.format(self.test_coin_from.name)) -logger = logging.getLogger() + addr_segwit = self.callnoderpc('getnewaddress', ['segwit test', 'bech32']) + addr_info = self.callnoderpc('getaddressinfo', [addr_segwit, ]) + assert addr_info['iswitness'] is True + txid = self.callnoderpc('sendtoaddress', [addr_segwit, 1.0]) + assert len(txid) == 64 + tx_wallet = self.callnoderpc('gettransaction', [txid, ])['hex'] + tx = self.callnoderpc('decoderawtransaction', [tx_wallet, ]) -class Test(BaseTest): - __test__ = True + self.mineBlock() + ro = self.callnoderpc('scantxoutset', ['start', ['addr({})'.format(addr_segwit)]]) + assert (len(ro['unspents']) == 1) + assert (ro['unspents'][0]['txid'] == txid) - @classmethod - def setUpClass(cls): - cls.test_coin_from = Coins.LTC - cls.start_ltc_nodes = True - super(Test, cls).setUpClass() + prevout_n = -1 + for txo in tx['vout']: + if addr_segwit in txo['scriptPubKey']['addresses']: + prevout_n = txo['n'] + break + assert prevout_n > -1 - @classmethod - def tearDownClass(cls): - logging.info('Finalising LTC Test') - super(Test, cls).tearDownClass() + tx_funded = self.callnoderpc('createrawtransaction', [[{'txid': txid, 'vout': prevout_n}], {addr_segwit: 0.99}]) + tx_signed = self.callnoderpc('signrawtransactionwithwallet', [tx_funded, ])['hex'] + tx_funded_decoded = self.callnoderpc('decoderawtransaction', [tx_funded, ]) + tx_signed_decoded = self.callnoderpc('decoderawtransaction', [tx_signed, ]) + assert tx_funded_decoded['txid'] == tx_signed_decoded['txid'] - 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 test_007_hdwallet(self): + logging.info('---------- Test {} hdwallet'.format(self.test_coin_from.name)) - def getXmrBalance(self, js_wallets): - return float(js_wallets[Coins.XMR.name]['unconfirmed']) + float(js_wallets[Coins.XMR.name]['balance']) + test_seed = '8e54a313e6df8918df6d758fafdbf127a115175fdd2238d0e908dd8093c9ac3b' + test_wif = self.swap_clients[0].ci(self.test_coin_from).encodeKey(bytes.fromhex(test_seed)) + new_wallet_name = random.randbytes(10).hex() + self.callnoderpc('createwallet', [new_wallet_name]) + self.callnoderpc('sethdseed', [True, test_wif], wallet=new_wallet_name) + addr = self.callnoderpc('getnewaddress', wallet=new_wallet_name) + self.callnoderpc('unloadwallet', [new_wallet_name]) + assert (addr == 'rltc1qps7hnjd866e9ynxadgseprkc2l56m00djr82la') - def test_01_full_swap(self): - logging.info('---------- Test {} to XMR'.format(str(self.test_coin_from))) + def test_20_btc_coin(self): + logging.info('---------- Test BTC to {}'.format(self.test_coin_from.name)) swap_clients = self.swap_clients - js_0 = read_json_api(1800, 'wallets') - node0_from_before = self.getBalance(js_0) + offer_id = swap_clients[0].postOffer(Coins.BTC, self.test_coin_from, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST) - js_1 = read_json_api(1801, 'wallets') - node1_from_before = self.getBalance(js_1) - - js_0_xmr = read_json_api(1800, 'wallets/xmr') - js_1_xmr = read_json_api(1801, 'wallets/xmr') - - 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) - wait_for_offer(test_delay_event, swap_clients[1], offer_id) - offers = swap_clients[0].listOffers(filters={'offer_id': offer_id}) - offer = offers[0] - - 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) - - swap_clients[0].acceptXmrBid(bid_id) - - 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)) - 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)) - - js_0 = read_json_api(1800, 'wallets') - node0_from_after = self.getBalance(js_0) - # 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') - - 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)) - - def test_02_leader_recover_a_lock_tx(self): - logging.info('---------- Test {} to XMR leader recovers coin a lock tx'.format(str(self.test_coin_from))) - swap_clients = self.swap_clients - - js_w0_before = read_json_api(1800, 'wallets') - node0_from_before = self.getBalance(js_w0_before) - - 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, - lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=12) 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) - bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from) + wait_for_bid(test_delay_event, swap_clients[0], bid_id) + swap_clients[0].acceptBid(bid_id) - wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_RECEIVED) + wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True) + wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=60) + wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60) - bid, xmr_swap = swap_clients[0].getXmrBid(bid_id) - assert (xmr_swap) - - swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_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.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) - - # 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(str(self.test_coin_from))) - swap_clients = self.swap_clients - - 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) - offer_id = swap_clients[0].postOffer( - self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP, - lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=12) - 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) - assert (xmr_swap) - - swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK) - swap_clients[0].setBidDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND) - - swap_clients[0].acceptXmrBid(bid_id) - - wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.BID_STALLED_FOR_TEST, wait_for=180) - wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_SWIPED, wait_for=80, sent=True) - - js_w1_after = read_json_api(1801, 'wallets') - node1_from_before = self.getBalance(js_w1_before) - node1_from_after = self.getBalance(js_w1_after) - amount_from = float(format_amount(amt_swap, 8)) - # TODO: Discard block rewards - # assert (node1_from_after - node1_from_before > (amount_from - 0.02)) - - swap_clients[0].abandonBid(bid_id) - - 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(str(self.test_coin_from))) - - swap_clients = self.swap_clients - - 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) - offer_id = swap_clients[0].postOffer( - self.test_coin_from, Coins.XMR, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP, - lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=28) - 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) - assert (xmr_swap) - - 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) - - 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) - - # 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) + js_0 = read_json_api(1800) + js_1 = read_json_api(1801) + assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0) + assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0) if __name__ == '__main__': diff --git a/tests/basicswap/test_run.py b/tests/basicswap/test_run.py index ef68d38..fbe5e23 100644 --- a/tests/basicswap/test_run.py +++ b/tests/basicswap/test_run.py @@ -154,11 +154,8 @@ class Test(BaseTest): offer_id = swap_clients[0].postOffer(Coins.PART, Coins.LTC, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST) wait_for_offer(test_delay_event, swap_clients[1], offer_id) - offers = swap_clients[1].listOffers() - assert (len(offers) == 1) - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + 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) @@ -275,7 +272,6 @@ class Test(BaseTest): wait_for_offer(test_delay_event, swap_clients[0], offer_id) offer = swap_clients[0].getOffer(offer_id) - offers = swap_clients[0].listOffers() bid_id = swap_clients[0].postBid(offer_id, offer.amount_from) wait_for_bid(test_delay_event, swap_clients[0], bid_id) @@ -326,11 +322,8 @@ class Test(BaseTest): offer_id = swap_clients[0].postOffer(Coins.PART, Coins.LTC, 100 * COIN, 0.1 * COIN, 100 * COIN, SwapTypes.SELLER_FIRST, auto_accept_bids=True) wait_for_offer(test_delay_event, swap_clients[1], offer_id) - offers = swap_clients[1].listOffers() - assert (len(offers) >= 1) - for offer in offers: - if offer.offer_id == offer_id: - bid_id = swap_clients[1].postBid(offer_id, offer.amount_from) + 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.SWAP_COMPLETED, wait_for=60) wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=60) diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index 061f04b..3f079e6 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -518,6 +518,10 @@ class BaseTest(unittest.TestCase): mweb_addr = callnoderpc(2, 'getnewaddress', ['mweb_addr', 'mweb'], base_rpc_port=LTC_BASE_RPC_PORT) callnoderpc(0, 'sendtoaddress', [mweb_addr, 1], base_rpc_port=LTC_BASE_RPC_PORT) + ltc_addr1 = callnoderpc(1, 'getnewaddress', ['initial addr'], base_rpc_port=LTC_BASE_RPC_PORT) + for i in range(5): + callnoderpc(0, 'sendtoaddress', [ltc_addr1, 100], base_rpc_port=LTC_BASE_RPC_PORT) + num_blocks = 69 cls.ltc_addr = cls.swap_clients[0].ci(Coins.LTC).pubkey_to_address(void_block_rewards_pubkey) callnoderpc(0, 'generatetoaddress', [num_blocks, cls.ltc_addr], base_rpc_port=LTC_BASE_RPC_PORT)