diff --git a/.travis.yml b/.travis.yml index 30de7c0..6dfb7d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,7 +48,7 @@ jobs: before_script: script: - PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=basicswap/contrib,messages_pb2.py,.eggs - - codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,gitianpubkeys,*.pyc,basicswap/contrib + - codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,gitianpubkeys,*.pyc,*basicswap/contrib,*mnemonics.py after_success: - echo "End lint" - stage: test diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 7d8ae8f..b07d36f 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -41,7 +41,6 @@ from .util import ( toWIF, getKeyID, make_int, - dumpj ) from .chainparams import ( chainparams, @@ -81,8 +80,6 @@ from .db import ( from .explorers import ExplorerInsight, ExplorerBitAps, ExplorerChainz import basicswap.config as cfg from .base import BaseApp -from .ecc_util import ( - b2i, i2b) MIN_OFFER_VALID_TIME = 60 * 10 @@ -359,7 +356,7 @@ class WatchedOutput(): class WatchedTransaction(): - # Watch for presense in mempool (getrawtransaction) + # Watch for presence in mempool (getrawtransaction) def __init__(self, bid_id, txid_hex, tx_type, swap_type): self.bid_id = bid_id self.txid_hex = txid_hex @@ -1281,7 +1278,7 @@ class BasicSwap(BaseApp): bid.xmr_b_lock_tx = stx else: bid.txns[stx.tx_type] = stx - #self.log.warning('Unknown transaction type: {}'.format(stx.tx_type)) + def getXmrBid(self, bid_id, sent=False): self.mxDB.acquire() try: @@ -1524,14 +1521,10 @@ class BasicSwap(BaseApp): ro = self.callrpc('smsgsend', [bid_addr, offer.addr_from, payload_hex, False, msg_valid, False, options]) xmr_swap.bid_msg_id3 = bytes.fromhex(ro['msgid']) - bid = Bid( bid_id=xmr_swap.bid_id, offer_id=offer_id, amount=msg_buf.amount, - #pkhash_buyer=msg_buf.pkhash_buyer, - #proof_address=msg_buf.proof_address, - created_at=bid_created_at, contract_count=xmr_swap.contract_count, amount_to=(msg_buf.amount * offer.rate) // COIN, @@ -2358,22 +2351,19 @@ class BasicSwap(BaseApp): delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) self.log.info('Sending xmr swap chain B lock tx for bid %s in %d seconds', bid_id.hex(), delay) self.createEventInSession(delay, EventTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, session) - #bid.setState(BidStates.SWAP_DELAYING) + # bid.setState(BidStates.SWAP_DELAYING) session.commit() elif state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED: - #if bid.was_sent and TxTypes.XMR_SWAP_B_LOCK not in bid.txns: if bid.was_sent and bid.xmr_b_lock_tx is None: return rv found_tx = ci_to.findTxB(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, ci_to.blocks_confirmed, xmr_swap.b_restore_height) if found_tx is not None: - #if TxTypes.XMR_SWAP_B_LOCK not in bid.txns: if bid.xmr_b_lock_tx is None: b_lock_tx_id = bytes.fromhex(found_tx['txid']) - #bid.txns[TxTypes.XMR_SWAP_B_LOCK] = SwapTx( bid.xmr_b_lock_tx = SwapTx( bid_id=bid_id, tx_type=TxTypes.XMR_SWAP_B_LOCK, @@ -2400,7 +2390,6 @@ class BasicSwap(BaseApp): # TODO: Use explorer to get tx / block hash for getrawtransaction pass elif state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED: - #txid_hex = bid.txns[TxTypes.XMR_SWAP_B_LOCK].spend_txid.hex() txid_hex = bid.xmr_b_lock_tx.spend_txid.hex() found_tx = ci_to.findTxnByHash(txid_hex) @@ -2688,9 +2677,7 @@ class BasicSwap(BaseApp): bid.setState(BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED) # TODO: Wait for confirmation? if not bid.was_received: - #rv = True # Remove from swaps_in_progress bid.setState(BidStates.SWAP_COMPLETED) - self.saveBidInSession(bid_id, bid, session, xmr_swap) if bid.was_received: delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) self.log.info('Redeeming coin b lock tx for bid %s in %d seconds', bid_id.hex(), delay) @@ -2700,6 +2687,7 @@ class BasicSwap(BaseApp): self.log.warning('Coin a lock tx spend ignored due to bid state for bid {}'.format(bid_id.hex())) elif spending_txid == xmr_swap.a_lock_refund_tx_id: + self.log.debug('Coin a lock tx spent by lock refund tx.') pass else: self.setBidError(bid.bid_id, bid, 'Unexpected txn spent coin a lock tx: {}'.format(spend_txid_hex), save_bid=False) @@ -2751,7 +2739,6 @@ class BasicSwap(BaseApp): self.log.info('Recovering xmr swap chain B lock tx for bid %s in %d seconds', bid_id.hex(), delay) self.createEventInSession(delay, EventTypes.RECOVER_XMR_SWAP_LOCK_TX_B, bid_id, session) else: - #rv = True # Remove from swaps_in_progress bid.setState(BidStates.XMR_SWAP_FAILED_REFUNDED) if bid.was_received: @@ -3181,8 +3168,8 @@ class BasicSwap(BaseApp): now = int(time.time()) offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=True) - assert(offer and offer.was_sent), 'Offer not found: {}.'.format(offer_id.hex()) - assert(xmr_offer), 'XMR offer not found: {}.'.format(offer_id.hex()) + assert(offer and offer.was_sent), 'Offer not found: {}.'.format(bid.offer_id.hex()) + assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex()) coin_from = Coins(offer.coin_from) coin_to = Coins(offer.coin_to) @@ -3262,7 +3249,6 @@ class BasicSwap(BaseApp): if not ci_from.verifyPubkey(xmr_swap.pkarl): raise ValueError('Invalid pubkey.') - #bid.setState(BidStates.BID_ACCEPTED) bid.setState(BidStates.SWAP_DELAYING) self.saveBidInSession(bid.bid_id, bid, session, xmr_swap) @@ -3301,8 +3287,6 @@ class BasicSwap(BaseApp): bid_id=bid_id, offer_id=offer_id, amount=bid_data.amount, - #pkhash_buyer=bid_data.pkhash_buyer, - created_at=msg['sent'], amount_to=(bid_data.amount * offer.rate) // COIN, expire_at=msg['sent'] + bid_data.time_valid, @@ -3563,17 +3547,27 @@ class BasicSwap(BaseApp): if bid.debug_ind == DebugTypes.CREATE_INVALID_COIN_B_LOCK: self.log.debug('XMR bid %s: Debug %d - Reducing lock b txn amount by 10%%.', bid_id.hex(), bid.debug_ind) bid.amount_to -= int(bid.amount_to * 0.1) - b_lock_tx_id = ci_to.publishBLockTx(xmr_swap.pkbv, xmr_swap.pkbs, bid.amount_to, xmr_offer.b_fee_rate) + try: + b_lock_tx_id = ci_to.publishBLockTx(xmr_swap.pkbv, xmr_swap.pkbs, bid.amount_to, xmr_offer.b_fee_rate) + except Exception as ex: + self.log.error('publishBLockTx failed for bid {} with error {}'.format(bid_id.hex(), str(ex))) + + if 'not enough unlocked money' in str(ex): + delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) # TODO: New delay range + self.log.info('Retrying sending xmr swap chain B lock tx for bid %s in %d seconds', bid_id.hex(), delay) + self.createEventInSession(delay, EventTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, session) + else: + self.setBidError(bid_id, bid, 'publishBLockTx failed: ' + str(ex), save_bid=False) + self.saveBidInSession(bid_id, bid, session, xmr_swap) + return self.log.debug('Submitted lock txn %s to %s chain for bid %s', b_lock_tx_id.hex(), chainparams[coin_to]['name'], bid_id.hex()) bid.xmr_b_lock_tx = SwapTx( - #xmr_b_lock_tx = SwapTx( bid_id=bid_id, tx_type=TxTypes.XMR_SWAP_B_LOCK, txid=b_lock_tx_id, ) bid.xmr_b_lock_tx.setState(TxStates.TX_NONE) - #bid.txns[TxTypes.XMR_SWAP_B_LOCK] = xmr_b_lock_tx self.saveBidInSession(bid_id, bid, session, xmr_swap) @@ -3688,21 +3682,14 @@ class BasicSwap(BaseApp): assert(kbsf is not None) kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3, for_xmr=True) - vkbs = ci_to.sumKeys(kbsl, kbsf) - Kbs_test = ci_to.getPubkey(vkbs) - print('Kbs_test', Kbs_test.hex()) - print('Kbs', xmr_swap.pkbsf.hex()) address_to = ci_to.getMainWalletAddress() txid = ci_to.spendBLockTx(address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, xmr_swap.b_restore_height) bid.xmr_b_lock_tx.spend_txid = txid - #txn = bid.txns[TxTypes.XMR_SWAP_B_LOCK] - #print('[rm] TxTypes.XMR_SWAP_B_LOCK', txn.bid_id.hex()) - #txn.spend_txid = txid - #bid.txns[TxTypes.XMR_SWAP_B_LOCK].spend_txid = txid + # TODO: Why does using bid.txns error here? self.saveBidInSession(bid_id, bid, session, xmr_swap) # Update copy of bid in swaps_in_progress self.swaps_in_progress[bid_id] = (bid, offer) @@ -3730,11 +3717,7 @@ class BasicSwap(BaseApp): assert(kbsl is not None) kbsf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_xmr=True) - vkbs = ci_to.sumKeys(kbsl, kbsf) - Kbs_test = ci_to.getPubkey(vkbs) - print('Kbs_test', Kbs_test.hex()) - print('Kbs', xmr_swap.pkbsf.hex()) address_to = ci_to.getMainWalletAddress() @@ -3747,7 +3730,6 @@ class BasicSwap(BaseApp): # Update copy of bid in swaps_in_progress self.swaps_in_progress[bid_id] = (bid, offer) - def processXmrBidCoinALockSigs(self, msg): # Leader processing MSG3L self.log.debug('Processing xmr coin a follower lock sigs msg %s', msg['msgid']) diff --git a/basicswap/db.py b/basicswap/db.py index c2a1cf1..fa020da 100644 --- a/basicswap/db.py +++ b/basicswap/db.py @@ -310,4 +310,3 @@ class XmrSplitData(Base): msg_sequence = sa.Column(sa.Integer) dleag = sa.Column(sa.LargeBinary) created_at = sa.Column(sa.BigInteger) - diff --git a/basicswap/http_server.py b/basicswap/http_server.py index 27cb609..486eb06 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -5,7 +5,6 @@ # file LICENSE or http://www.opensource.org/licenses/mit-license.php. import os -import json import time import struct import traceback @@ -17,7 +16,6 @@ from jinja2 import Environment, PackageLoader from . import __version__ from .util import ( - make_int, dumpj, ) from .chainparams import ( @@ -26,9 +24,6 @@ from .chainparams import ( ) from .basicswap import ( SwapTypes, - BidStates, - TxStates, - TxTypes, strOfferState, strBidState, strTxState, @@ -45,6 +40,12 @@ from .js_server import ( js_sentbids, js_index, ) +from .ui import ( + PAGE_LIMIT, + inputAmount, + describeBid, + setCoinFilter, +) def format_timestamp(value): @@ -53,7 +54,6 @@ def format_timestamp(value): env = Environment(loader=PackageLoader('basicswap', 'templates')) env.filters['formatts'] = format_timestamp -PAGE_LIMIT = 50 def getCoinName(c): @@ -89,141 +89,6 @@ def listExplorerActions(swap_client): return actions -def listBidStates(): - rv = [] - for s in BidStates: - rv.append((int(s), strBidState(s))) - return rv - - -def getTxIdHex(bid, tx_type, prefix): - if tx_type == TxTypes.ITX: - obj = bid.initiate_tx - elif tx_type == TxTypes.PTX: - obj = bid.participate_tx - else: - return 'Unknown Type' - - if not obj: - return 'None' - if not obj.txid: - return 'None' - return obj.txid.hex() + prefix - - -def getTxSpendHex(bid, tx_type): - if tx_type == TxTypes.ITX: - obj = bid.initiate_tx - elif tx_type == TxTypes.PTX: - obj = bid.participate_tx - else: - return 'Unknown Type' - - if not obj: - return 'None' - if not obj.spend_txid: - return 'None' - return obj.spend_txid.hex() + ' {}'.format(obj.spend_n) - - -def validateAmountString(amount): - if type(amount) != str: - return - ar = amount.split('.') - if len(ar) > 1 and len(ar[1]) > 8: - raise ValueError('Too many decimal places in amount {}'.format(amount)) - - -def inputAmount(amount_str): - validateAmountString(amount_str) - return make_int(amount_str) - - -def setCoinFilter(form_data, field_name): - if field_name not in form_data: - return -1 - coin_type = int(form_data[field_name][0]) - if coin_type == -1: - return -1 - try: - return Coins(coin_type) - except Exception: - raise ValueError('Unknown Coin Type {}'.format(str(field_name))) - - -def describeBid(swap_client, bid, offer, edit_bid, show_txns): - - coin_from = Coins(offer.coin_from) - coin_to = Coins(offer.coin_to) - ci_from = swap_client.ci(coin_from) - ci_to = swap_client.ci(coin_to) - ticker_from = swap_client.getTicker(coin_from) - ticker_to = swap_client.getTicker(coin_to) - - if bid.state == BidStates.BID_SENT: - state_description = 'Waiting for seller to accept.' - elif bid.state == BidStates.BID_RECEIVED: - state_description = 'Waiting for seller to accept.' - elif bid.state == BidStates.BID_ACCEPTED: - if not bid.initiate_tx: - state_description = 'Waiting for seller to send initiate tx.' - else: - state_description = 'Waiting for initiate tx to confirm.' - elif bid.state == BidStates.SWAP_INITIATED: - state_description = 'Waiting for participate txn to be confirmed in {} chain'.format(ticker_to) - elif bid.state == BidStates.SWAP_PARTICIPATING: - state_description = 'Waiting for initiate txn to be spent in {} chain'.format(ticker_from) - elif bid.state == BidStates.SWAP_COMPLETED: - state_description = 'Swap completed' - if bid.getITxState() == TxStates.TX_REDEEMED and bid.getPTxState() == TxStates.TX_REDEEMED: - state_description += ' successfully' - else: - state_description += ', ITX ' + strTxState(bid.getITxState()) + ', PTX ' + strTxState(bid.getPTxState()) - elif bid.state == BidStates.SWAP_TIMEDOUT: - state_description = 'Timed out waiting for initiate txn' - elif bid.state == BidStates.BID_ABANDONED: - state_description = 'Bid abandoned' - elif bid.state == BidStates.BID_ERROR: - state_description = bid.state_note - else: - state_description = '' - - data = { - 'amt_from': ci_from.format_amount(bid.amount), - 'amt_to': ci_to.format_amount((bid.amount * offer.rate) // ci_from.COIN()), - 'ticker_from': ticker_from, - 'ticker_to': ticker_to, - 'bid_state': strBidState(bid.state), - 'state_description': state_description, - 'itx_state': strTxState(bid.getITxState()), - 'ptx_state': strTxState(bid.getPTxState()), - 'offer_id': bid.offer_id.hex(), - 'addr_from': bid.bid_addr, - 'addr_fund_proof': bid.proof_address, - 'created_at': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(bid.created_at)), - 'expired_at': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(bid.expire_at)), - 'was_sent': 'True' if bid.was_sent else 'False', - 'was_received': 'True' if bid.was_received else 'False', - 'initiate_tx': getTxIdHex(bid, TxTypes.ITX, ' ' + ticker_from), - 'initiate_conf': 'None' if (not bid.initiate_tx or not bid.initiate_tx.conf) else bid.initiate_tx.conf, - 'participate_tx': getTxIdHex(bid, TxTypes.PTX, ' ' + ticker_to), - 'participate_conf': 'None' if (not bid.participate_tx or not bid.participate_tx.conf) else bid.participate_tx.conf, - 'show_txns': show_txns, - } - - if edit_bid: - data['bid_state_ind'] = int(bid.state) - data['bid_states'] = listBidStates() - - if show_txns: - data['initiate_tx_refund'] = 'None' if not bid.initiate_txn_refund else bid.initiate_txn_refund.hex() - data['participate_tx_refund'] = 'None' if not bid.participate_txn_refund else bid.participate_txn_refund.hex() - data['initiate_tx_spend'] = getTxSpendHex(bid, TxTypes.ITX) - data['participate_tx_spend'] = getTxSpendHex(bid, TxTypes.PTX) - - return data - - def html_content_start(title, h2=None, refresh=None): content = '\n' \ + '' \ diff --git a/basicswap/interface_btc.py b/basicswap/interface_btc.py index ef0d8d8..ad7d477 100644 --- a/basicswap/interface_btc.py +++ b/basicswap/interface_btc.py @@ -362,7 +362,6 @@ class BTCInterface(CoinInterface): tx.nVersion = self.txVersion() tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), nSequence=0)) - #pubkeyhash = hash160(Kal) tx.vout.append(self.txoType(locked_coin, CScript([OP_0, pkh_refund_to]))) witness_bytes = len(script_lock_refund) @@ -591,9 +590,11 @@ class BTCInterface(CoinInterface): assert_cond(len(tx.vout) == 1, 'tx doesn\'t have one output') # Destination doesn't matter to the follower - #p2wpkh = CScript([OP_0, hash160(Kal)]) - #locked_n = findOutput(tx, p2wpkh) - #assert_cond(locked_n is not None, 'Output not found in lock refund spend tx') + ''' + p2wpkh = CScript([OP_0, hash160(Kal)]) + locked_n = findOutput(tx, p2wpkh) + assert_cond(locked_n is not None, 'Output not found in lock refund spend tx') + ''' tx_value = tx.vout[0].nValue fee_paid = prevout_value - tx_value @@ -711,8 +712,6 @@ class BTCInterface(CoinInterface): def signTxWithWallet(self, tx): rv = self.rpc_callback('signrawtransactionwithwallet', [tx.hex()]) - - #return FromHex(tx, rv['hex']) return bytes.fromhex(rv['hex']) def publishTx(self, tx): @@ -798,7 +797,6 @@ class BTCInterface(CoinInterface): def recoverEncKey(self, esig, sig, K): return ecdsaotves_rec_enc_key(K, esig, sig[:-1]) # Strip sighash type - #return otves.RecoverEncKey(esig, sig[:-1], K) # Strip sighash type def getTxVSize(self, tx, add_bytes=0, add_witness_bytes=0): wsf = self.witnessScaleFactor() @@ -845,12 +843,6 @@ class BTCInterface(CoinInterface): def getOutput(self, txid, dest_script, expect_value): # TODO: Use getrawtransaction if txindex is active utxos = self.rpc_callback('scantxoutset', ['start', ['raw({})'.format(dest_script.hex())]]) - ''' - bech32_prefix = chainparams[self.coin_type()][self._network]['hrp'] - address = segwit_addr.encode(bech32_prefix, 0, list(dest_script[2:])) - print('[rm] address', address) - utxos = self.rpc_callback('scantxoutset', ['start', ['addr({})'.format(address)]]) - ''' chain_height = utxos['height'] rv = [] for utxo in utxos['unspents']: @@ -871,7 +863,6 @@ class BTCInterface(CoinInterface): return rv - def testBTCInterface(): print('testBTCInterface') script_bytes = bytes.fromhex('6382012088a820aaf125ff9a34a74c7a17f5e7ee9d07d17cc5e53a539f345d5f73baa7e79b65e28852210224019219ad43c47288c937ae508f26998dd81ec066827773db128fd5e262c04f21039a0fd752bd1a2234820707852e7a30253620052ecd162948a06532a817710b5952ae670114b2755221038689deba25c5578e5457ddadbaf8aeb8badf438dc22f540503dbd4ae10e14f512103c9c5d5acc996216d10852a72cd67c701bfd4b9137a4076350fd32f08db39575552ae68') @@ -954,6 +945,5 @@ def testBTCInterface(): print('Passed.') - if __name__ == "__main__": testBTCInterface() diff --git a/basicswap/interface_ltc.py b/basicswap/interface_ltc.py index 31a1e49..064f5d0 100644 --- a/basicswap/interface_ltc.py +++ b/basicswap/interface_ltc.py @@ -8,6 +8,7 @@ from .interface_btc import BTCInterface from .chainparams import Coins + class LTCInterface(BTCInterface): @staticmethod def coin_type(): diff --git a/basicswap/interface_nmc.py b/basicswap/interface_nmc.py index 34a5190..2378ab3 100644 --- a/basicswap/interface_nmc.py +++ b/basicswap/interface_nmc.py @@ -8,6 +8,7 @@ from .interface_btc import BTCInterface from .chainparams import Coins + class NMCInterface(BTCInterface): @staticmethod def coin_type(): diff --git a/basicswap/interface_xmr.py b/basicswap/interface_xmr.py index 3ab2c3d..8f0778b 100644 --- a/basicswap/interface_xmr.py +++ b/basicswap/interface_xmr.py @@ -151,6 +151,7 @@ class XMRInterface(CoinInterface): return self.encodePubkey(edf.edwards_add(Ka_d, Kb_d)) def publishBLockTx(self, Kbv, Kbs, output_amount, feerate): + self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename}) shared_addr = xmr_util.encode_address(Kbv, Kbs) @@ -158,11 +159,18 @@ class XMRInterface(CoinInterface): params = {'destinations': [{'amount': output_amount, 'address': shared_addr}]} rv = self.rpc_wallet_cb('transfer', params) logging.info('publishBLockTx %s to address_b58 %s', rv['tx_hash'], shared_addr) + tx_hash = bytes.fromhex(rv['tx_hash']) - return bytes.fromhex(rv['tx_hash']) + # Debug + for i in range(10): + params = {'out': True, 'pending': True, 'failed': True, 'pool': True, } + rv = self.rpc_wallet_cb('get_transfers', params) + logging.info('[rm] get_transfers {}'.format(dumpj(rv))) + time.sleep(1) + + return tx_hash def findTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height): - #Kbv_enc = self.encodePubkey(self.pubkey(kbv)) Kbv = self.getPubkey(kbv) address_b58 = xmr_util.encode_address(Kbv, Kbs) @@ -176,7 +184,6 @@ class XMRInterface(CoinInterface): 'restore_height': restore_height, 'filename': address_b58, 'address': address_b58, - #'viewkey': b2h(intToBytes32_le(kbv)), 'viewkey': b2h(kbv_le), } @@ -220,7 +227,7 @@ class XMRInterface(CoinInterface): params = { 'filename': address_b58, 'address': address_b58, - 'viewkey': b2h(intToBytes32_le(kbv)), + 'viewkey': b2h(kbv[::-1]), 'restore_height': restore_height, } self.rpc_wallet_cb('generate_from_keys', params) diff --git a/basicswap/js_server.py b/basicswap/js_server.py index ed63c94..7b372d4 100644 --- a/basicswap/js_server.py +++ b/basicswap/js_server.py @@ -4,45 +4,31 @@ # 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 time -import struct -import traceback -import threading import urllib.parse -from . import __version__ -from .util import ( - COIN, - format8, - make_int, - dumpj, -) -from .chainparams import ( - chainparams, - Coins, -) +from .util import format8 from .basicswap import ( - SwapTypes, - BidStates, - TxStates, - TxTypes, - strOfferState, strBidState, - strTxState, - getLockName, - SEQUENCE_LOCK_TIME, - ABS_LOCK_TIME, ) +from .ui import ( + PAGE_LIMIT, + inputAmount, + describeBid, + setCoinFilter, +) + def js_error(self, error_str): error_str_json = json.dumps({'error': error_str}) return bytes(error_str_json, 'UTF-8') + def js_wallets(self, url_split, post_string): return bytes(json.dumps(self.server.swap_client.getWalletsInfo()), 'UTF-8') + def js_offers(self, url_split, post_string, sent=False): if len(url_split) > 3: if url_split[3] == 'new': @@ -82,27 +68,28 @@ def js_offers(self, url_split, post_string, sent=False): filters['limit'] = int(post_data[b'limit'][0]) assert(filters['limit'] > 0 and filters['limit'] <= PAGE_LIMIT), 'Invalid limit' - ci_from = self.server.swap_client.ci(o.coin_from) - ci_to = self.server.swap_client.ci(o.coin_to) - offers = self.server.swap_client.listOffers(sent, filters) rv = [] for o in offers: + ci_from = self.server.swap_client.ci(o.coin_from) + ci_to = self.server.swap_client.ci(o.coin_to) rv.append({ 'offer_id': o.offer_id.hex(), 'created_at': time.strftime('%Y-%m-%d %H:%M', time.localtime(o.created_at)), 'coin_from': ci_from.coin_name(), 'coin_to': ci_to.coin_name(), 'amount_from': ci_from.format_amount(o.amount_from), - 'amount_to': ci_to.format_amount((o.amount_from * o.rate) // COIN), - 'rate': format8(o.rate) + 'amount_to': ci_to.format_amount((o.amount_from * o.rate) // ci_from.COIN()), + 'rate': ci_to.format_amount(o.rate) }) return bytes(json.dumps(rv), 'UTF-8') + def js_sentoffers(self, url_split, post_string): return self.js_offers(url_split, post_string, True) + def js_bids(self, url_split, post_string): swap_client = self.server.swap_client if len(url_split) > 3: @@ -153,8 +140,10 @@ def js_bids(self, url_split, post_string): 'bid_state': strBidState(b[4]) } for b in bids]), 'UTF-8') + def js_sentbids(self, url_split, post_string): return bytes(json.dumps(self.server.swap_client.listBids(sent=True)), 'UTF-8') + def js_index(self, url_split, post_string): return bytes(json.dumps(self.server.swap_client.getSummary()), 'UTF-8') diff --git a/basicswap/network.py b/basicswap/network.py new file mode 100644 index 0000000..b7ed369 --- /dev/null +++ b/basicswap/network.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +''' +TODO: +''' + +class Peer: + pass diff --git a/basicswap/ui.py b/basicswap/ui.py new file mode 100644 index 0000000..4896f79 --- /dev/null +++ b/basicswap/ui.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +import time + +from .util import ( + make_int, +) +from .chainparams import ( + Coins, +) +from .basicswap import ( + BidStates, + TxStates, + TxTypes, + strBidState, + strTxState, +) + +PAGE_LIMIT = 50 + + +def validateAmountString(amount): + if type(amount) != str: + return + ar = amount.split('.') + if len(ar) > 1 and len(ar[1]) > 8: + raise ValueError('Too many decimal places in amount {}'.format(amount)) + + +def inputAmount(amount_str): + validateAmountString(amount_str) + return make_int(amount_str) + + +def setCoinFilter(form_data, field_name): + if field_name not in form_data: + return -1 + coin_type = int(form_data[field_name][0]) + if coin_type == -1: + return -1 + try: + return Coins(coin_type) + except Exception: + raise ValueError('Unknown Coin Type {}'.format(str(field_name))) + + +def getTxIdHex(bid, tx_type, prefix): + if tx_type == TxTypes.ITX: + obj = bid.initiate_tx + elif tx_type == TxTypes.PTX: + obj = bid.participate_tx + else: + return 'Unknown Type' + + if not obj: + return 'None' + if not obj.txid: + return 'None' + return obj.txid.hex() + prefix + + +def getTxSpendHex(bid, tx_type): + if tx_type == TxTypes.ITX: + obj = bid.initiate_tx + elif tx_type == TxTypes.PTX: + obj = bid.participate_tx + else: + return 'Unknown Type' + + if not obj: + return 'None' + if not obj.spend_txid: + return 'None' + return obj.spend_txid.hex() + ' {}'.format(obj.spend_n) + + +def listBidStates(): + rv = [] + for s in BidStates: + rv.append((int(s), strBidState(s))) + return rv + + +def describeBid(swap_client, bid, offer, edit_bid, show_txns): + + coin_from = Coins(offer.coin_from) + coin_to = Coins(offer.coin_to) + ci_from = swap_client.ci(coin_from) + ci_to = swap_client.ci(coin_to) + ticker_from = swap_client.getTicker(coin_from) + ticker_to = swap_client.getTicker(coin_to) + + if bid.state == BidStates.BID_SENT: + state_description = 'Waiting for seller to accept.' + elif bid.state == BidStates.BID_RECEIVED: + state_description = 'Waiting for seller to accept.' + elif bid.state == BidStates.BID_ACCEPTED: + if not bid.initiate_tx: + state_description = 'Waiting for seller to send initiate tx.' + else: + state_description = 'Waiting for initiate tx to confirm.' + elif bid.state == BidStates.SWAP_INITIATED: + state_description = 'Waiting for participate txn to be confirmed in {} chain'.format(ticker_to) + elif bid.state == BidStates.SWAP_PARTICIPATING: + state_description = 'Waiting for initiate txn to be spent in {} chain'.format(ticker_from) + elif bid.state == BidStates.SWAP_COMPLETED: + state_description = 'Swap completed' + if bid.getITxState() == TxStates.TX_REDEEMED and bid.getPTxState() == TxStates.TX_REDEEMED: + state_description += ' successfully' + else: + state_description += ', ITX ' + strTxState(bid.getITxState()) + ', PTX ' + strTxState(bid.getPTxState()) + elif bid.state == BidStates.SWAP_TIMEDOUT: + state_description = 'Timed out waiting for initiate txn' + elif bid.state == BidStates.BID_ABANDONED: + state_description = 'Bid abandoned' + elif bid.state == BidStates.BID_ERROR: + state_description = bid.state_note + else: + state_description = '' + + data = { + 'amt_from': ci_from.format_amount(bid.amount), + 'amt_to': ci_to.format_amount((bid.amount * offer.rate) // ci_from.COIN()), + 'ticker_from': ticker_from, + 'ticker_to': ticker_to, + 'bid_state': strBidState(bid.state), + 'state_description': state_description, + 'itx_state': strTxState(bid.getITxState()), + 'ptx_state': strTxState(bid.getPTxState()), + 'offer_id': bid.offer_id.hex(), + 'addr_from': bid.bid_addr, + 'addr_fund_proof': bid.proof_address, + 'created_at': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(bid.created_at)), + 'expired_at': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(bid.expire_at)), + 'was_sent': 'True' if bid.was_sent else 'False', + 'was_received': 'True' if bid.was_received else 'False', + 'initiate_tx': getTxIdHex(bid, TxTypes.ITX, ' ' + ticker_from), + 'initiate_conf': 'None' if (not bid.initiate_tx or not bid.initiate_tx.conf) else bid.initiate_tx.conf, + 'participate_tx': getTxIdHex(bid, TxTypes.PTX, ' ' + ticker_to), + 'participate_conf': 'None' if (not bid.participate_tx or not bid.participate_tx.conf) else bid.participate_tx.conf, + 'show_txns': show_txns, + } + + if edit_bid: + data['bid_state_ind'] = int(bid.state) + data['bid_states'] = listBidStates() + + if show_txns: + data['initiate_tx_refund'] = 'None' if not bid.initiate_txn_refund else bid.initiate_txn_refund.hex() + data['participate_tx_refund'] = 'None' if not bid.participate_txn_refund else bid.participate_txn_refund.hex() + data['initiate_tx_spend'] = getTxSpendHex(bid, TxTypes.ITX) + data['participate_tx_spend'] = getTxSpendHex(bid, TxTypes.PTX) + + return data diff --git a/tests/basicswap/test_other.py b/tests/basicswap/test_other.py index 52fd565..c4f161f 100644 --- a/tests/basicswap/test_other.py +++ b/tests/basicswap/test_other.py @@ -27,7 +27,6 @@ from basicswap.util import ( SerialiseNum, DeserialiseNum, make_int, - format8, format_amount, validate_amount, ) @@ -39,9 +38,6 @@ from basicswap.basicswap import ( SEQUENCE_LOCK_TIME, ) -from basicswap.ecc_util import ( - i2b) - class Test(unittest.TestCase): def test_serialise_num(self): @@ -214,6 +210,5 @@ class Test(unittest.TestCase): assert('12.00000000' == format_amount(amount_to, scale_to)) - if __name__ == '__main__': unittest.main() diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index e8dd0a8..06529f5 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -23,14 +23,12 @@ from basicswap.basicswap import ( Coins, SwapTypes, BidStates, - TxStates, DebugTypes, SEQUENCE_LOCK_BLOCKS, ) from basicswap.util import ( COIN, toWIF, - dumpje, make_int, ) from basicswap.rpc import ( @@ -340,7 +338,6 @@ class Test(unittest.TestCase): logger.propagate = False logger.handlers = [] logger.setLevel(logging.INFO) # DEBUG shows many messages from requests.post - #logger.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s %(levelname)s : %(message)s') stream_stdout = logging.StreamHandler() stream_stdout.setFormatter(formatter) @@ -573,15 +570,14 @@ class Test(unittest.TestCase): end_xmr = float(js_0_end['6']['balance']) + float(js_0_end['6']['unconfirmed']) assert(end_xmr > 10.9 and end_xmr < 11.0) - self.delay_for(600) # [rm] - def test_02_leader_recover_a_lock_tx(self): logging.info('---------- Test PART to XMR leader recovers coin a lock tx') swap_clients = self.swap_clients js_w0_before = json.loads(urlopen('http://localhost:1800/json/wallets').read()) - offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 101 * COIN, 0.12 * XMR_COIN, 101 * COIN, SwapTypes.XMR_SWAP, + offer_id = swap_clients[0].postOffer( + Coins.PART, Coins.XMR, 101 * COIN, 0.12 * XMR_COIN, 101 * COIN, SwapTypes.XMR_SWAP, lock_type=SEQUENCE_LOCK_BLOCKS, lock_value=12) self.wait_for_offer(swap_clients[1], offer_id) offer = swap_clients[1].getOffer(offer_id) @@ -610,7 +606,8 @@ class Test(unittest.TestCase): js_w0_before = json.loads(urlopen('http://localhost:1800/json/wallets').read()) - offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 101 * COIN, 0.13 * XMR_COIN, 101 * COIN, SwapTypes.XMR_SWAP, + offer_id = swap_clients[0].postOffer( + Coins.PART, Coins.XMR, 101 * COIN, 0.13 * XMR_COIN, 101 * COIN, SwapTypes.XMR_SWAP, lock_type=SEQUENCE_LOCK_BLOCKS, lock_value=12) self.wait_for_offer(swap_clients[1], offer_id) offer = swap_clients[1].getOffer(offer_id) @@ -637,7 +634,8 @@ class Test(unittest.TestCase): swap_clients = self.swap_clients - offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 101 * COIN, 0.14 * XMR_COIN, 101 * COIN, SwapTypes.XMR_SWAP, + offer_id = swap_clients[0].postOffer( + Coins.PART, Coins.XMR, 101 * COIN, 0.14 * XMR_COIN, 101 * COIN, SwapTypes.XMR_SWAP, lock_type=SEQUENCE_LOCK_BLOCKS, lock_value=18) self.wait_for_offer(swap_clients[1], offer_id) offer = swap_clients[1].getOffer(offer_id) @@ -676,8 +674,32 @@ class Test(unittest.TestCase): self.wait_for_bid(swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=180) self.wait_for_bid(swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True) - def pass_06_delay(self): - self.delay_for(60) + def test_06_multiple_swaps(self): + logging.info('---------- Test Multiple concurrent swaps') + swap_clients = self.swap_clients + offer1_id = swap_clients[0].postOffer(Coins.BTC, Coins.XMR, 10 * COIN, 100 * XMR_COIN, 10 * COIN, SwapTypes.XMR_SWAP) + offer2_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 10 * COIN, 0.14 * XMR_COIN, 10 * COIN, SwapTypes.XMR_SWAP) + + self.wait_for_offer(swap_clients[1], offer1_id) + offer1 = swap_clients[1].getOffer(offer1_id) + self.wait_for_offer(swap_clients[1], offer2_id) + offer2 = swap_clients[1].getOffer(offer2_id) + + bid1_id = swap_clients[1].postXmrBid(offer1_id, offer1.amount_from) + bid2_id = swap_clients[1].postXmrBid(offer2_id, offer2.amount_from) + + self.wait_for_bid(swap_clients[0], bid1_id, BidStates.BID_RECEIVED) + swap_clients[0].acceptXmrBid(bid1_id) + + self.wait_for_bid(swap_clients[0], bid2_id, BidStates.BID_RECEIVED) + swap_clients[0].acceptXmrBid(bid2_id) + + self.wait_for_bid(swap_clients[0], bid1_id, BidStates.SWAP_COMPLETED, wait_for=180) + self.wait_for_bid(swap_clients[1], bid1_id, BidStates.SWAP_COMPLETED, sent=True) + + self.wait_for_bid(swap_clients[0], bid2_id, BidStates.SWAP_COMPLETED, wait_for=180) + self.wait_for_bid(swap_clients[1], bid2_id, BidStates.SWAP_COMPLETED, sent=True) + if __name__ == '__main__': diff --git a/tests/lint/spelling.ignore-words.txt b/tests/lint/spelling.ignore-words.txt index 75835e2..16b3f2a 100644 --- a/tests/lint/spelling.ignore-words.txt +++ b/tests/lint/spelling.ignore-words.txt @@ -1,2 +1,2 @@ eventtypes - +wit