diff --git a/.travis.yml b/.travis.yml index 78f69a1..3f21e7f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ stages: env: global: - TEST_DIR=~/test_basicswap2/ - - PARTICL_BINDIR=/opt/binaries/particl-0.18.1.4/bin/ + - PARTICL_BINDIR=/opt/binaries/particl-0.18.1.5/bin/ - BITCOIN_BINDIR=/opt/binaries/bitcoin-0.18.1/bin/ - LITECOIN_BINDIR=/opt/binaries/litecoin-0.17.1/bin/ before_install: @@ -16,10 +16,10 @@ before_script: - if [ ! -d "/opt/binaries" ]; then mkdir -p "/opt/binaries" ; fi - if [ ! -d "$BITCOIN_BINDIR" ]; then cd "/opt/binaries" && wget https://bitcoincore.org/bin/bitcoin-core-0.18.1/bitcoin-0.18.1-x86_64-linux-gnu.tar.gz && tar xvf bitcoin-0.18.1-x86_64-linux-gnu.tar.gz ; fi - if [ ! -d "$LITECOIN_BINDIR" ]; then cd "/opt/binaries" && wget https://download.litecoin.org/litecoin-0.17.1/linux/litecoin-0.17.1-x86_64-linux-gnu.tar.gz && tar xvf litecoin-0.17.1-x86_64-linux-gnu.tar.gz ; fi - - if [ ! -d "$PARTICL_BINDIR" ]; then cd "/opt/binaries" && wget https://github.com/particl/particl-core/releases/download/v0.18.1.4/particl-0.18.1.4-x86_64-linux-gnu_nousb.tar.gz && tar xvf particl-0.18.1.4-x86_64-linux-gnu_nousb.tar.gz ; fi + - if [ ! -d "$PARTICL_BINDIR" ]; then cd "/opt/binaries" && wget https://github.com/particl/particl-core/releases/download/v0.18.1.5/particl-0.18.1.5-x86_64-linux-gnu_nousb.tar.gz && tar xvf particl-0.18.1.5-x86_64-linux-gnu_nousb.tar.gz ; fi script: - cd $TRAVIS_BUILD_DIR - - export PARTICL_BINDIR=/opt/binaries/particl-0.18.1.4/bin/ + - export PARTICL_BINDIR=/opt/binaries/particl-0.18.1.5/bin/ - export BITCOIN_BINDIR=/opt/binaries/bitcoin-0.18.1/bin/ - export LITECOIN_BINDIR=/opt/binaries/litecoin-0.17.1/bin/ - export DATADIRS=~/test_basicswap2/ diff --git a/basicswap/http_server.py b/basicswap/http_server.py index b3a3fd0..deb926b 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -127,6 +127,23 @@ def validateAmountString(amount): raise ValueError('Too many decimal places in amount {}'.format(amount)) +def inputAmount(amount_str): + validateAmountString(amount_str) + return makeInt(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 html_content_start(title, h2=None, refresh=None): content = '\n
' \ + '' \ @@ -169,7 +186,7 @@ class HttpHandler(BaseHTTPRequestHandler): 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): + def js_offers(self, url_split, post_string, sent=False): if len(url_split) > 3: if url_split[3] == 'new': if post_string == '': @@ -180,15 +197,76 @@ class HttpHandler(BaseHTTPRequestHandler): return bytes(json.dumps(rv), 'UTF-8') offer_id = bytes.fromhex(url_split[3]) - assert(False), 'TODO' - return bytes(json.dumps(self.server.swap_client.listOffers()), 'UTF-8') + filters = { + 'coin_from': -1, + 'coin_to': -1, + 'page_no': 1, + 'limit': PAGE_LIMIT, + 'sort_by': 'created_at', + 'sort_dir': 'desc', + } + if post_string != '': + post_data = urllib.parse.parse_qs(post_string) + filters['coin_from'] = setCoinFilter(form_data, b'coin_from') + filters['coin_to'] = setCoinFilter(form_data, b'coin_to') + + if b'sort_by' in post_data: + sort_by = post_data[b'sort_by'][0].decode('utf-8') + assert(sort_by in ['created_at', 'rate']), 'Invalid sort by' + filters['sort_by'] = sort_by + if b'sort_dir' in post_data: + sort_dir = post_data[b'sort_dir'][0].decode('utf-8') + assert(sort_dir in ['asc', 'desc']), 'Invalid sort dir' + filters['sort_dir'] = sort_dir + + if b'offset' in post_data: + filters['offset'] = int(post_data[b'offset'][0]) + if b'limit' in post_data: + filters['limit'] = int(post_data[b'limit'][0]) + assert(filters['limit'] > 0 and filters['limit'] <= PAGE_LIMIT), 'Invalid limit' + + offers = self.server.swap_client.listOffers(sent, filters) + rv = [] + for o in offers: + rv.append({ + 'offer_id': o.offer_id.hex(), + 'created_at': time.strftime('%Y-%m-%d %H:%M', time.localtime(o.created_at)), + 'coin_from': getCoinName(Coins(o.coin_from)), + 'coin_to': getCoinName(Coins(o.coin_to)), + 'amount_from': format8(o.amount_from), + 'amount_to': format8((o.amount_from * o.rate) // COIN), + 'rate': format8(o.rate) + }) + + return bytes(json.dumps(rv), 'UTF-8') def js_sentoffers(self, url_split, post_string): - assert(False), 'TODO' - return bytes(json.dumps(self.server.swap_client.listOffers(sent=True)), 'UTF-8') + return self.js_offers(url_split, post_string, True) def js_bids(self, url_split, post_string): if len(url_split) > 3: + if url_split[3] == 'new': + if post_string == '': + raise ValueError('No post data') + post_data = urllib.parse.parse_qs(post_string) + + offer_id = bytes.fromhex(post_data[b'offer_id'][0].decode('utf-8')) + assert(len(offer_id) == 28) + + amount_from = inputAmount(post_data[b'amount_from'][0].decode('utf-8')) + + addr_from = None + if b'addr_from' in post_data: + addr_from = post_data[b'addr_from'][0].decode('utf-8') + if addr_from == '-1': + addr_from = None + + swap_client = self.server.swap_client + bid_id = swap_client.postBid(offer_id, amount_from, addr_send_from=addr_from).hex() + + rv = {'bid_id': bid_id} + return bytes(json.dumps(rv), 'UTF-8') + bid_id = bytes.fromhex(url_split[3]) assert(len(bid_id) == 28) return bytes(json.dumps(self.server.swap_client.viewBid(bid_id)), 'UTF-8') @@ -382,13 +460,8 @@ class HttpHandler(BaseHTTPRequestHandler): except Exception: raise ValueError('Unknown Coin To') - value_from = form_data[b'amt_from'][0].decode('utf-8') - value_to = form_data[b'amt_to'][0].decode('utf-8') - - validateAmountString(value_from) - validateAmountString(value_to) - value_from = makeInt(value_from) - value_to = makeInt(value_to) + value_from = inputAmount(form_data[b'amt_from'][0].decode('utf-8')) + value_to = inputAmount(form_data[b'amt_to'][0].decode('utf-8')) min_bid = int(value_from) rate = int((value_to / value_from) * COIN) @@ -447,7 +520,7 @@ class HttpHandler(BaseHTTPRequestHandler): if addr_from == '-1': addr_from = None - sent_bid_id = swap_client.postBid(offer_id, offer.amount_from).hex() + sent_bid_id = swap_client.postBid(offer_id, offer.amount_from, addr_send_from=addr_from).hex() coin_from = Coins(offer.coin_from) coin_to = Coins(offer.coin_to) @@ -503,24 +576,14 @@ class HttpHandler(BaseHTTPRequestHandler): messages = [] form_data = self.checkForm(post_string, 'offers', messages) if form_data and b'applyfilters' in form_data: - coin_from = int(form_data[b'coin_from'][0]) - if coin_from > -1: - try: - filters['coin_from'] = Coins(coin_from) - except Exception: - raise ValueError('Unknown Coin From') - coin_to = int(form_data[b'coin_to'][0]) - if coin_to > -1: - try: - filters['coin_to'] = Coins(coin_to) - except Exception: - raise ValueError('Unknown Coin From') + filters['coin_from'] = setCoinFilter(form_data, b'coin_from') + filters['coin_to'] = setCoinFilter(form_data, b'coin_to') if b'sort_by' in form_data: sort_by = form_data[b'sort_by'][0].decode('utf-8') assert(sort_by in ['created_at', 'rate']), 'Invalid sort by' filters['sort_by'] = sort_by - elif b'sort_dir' in form_data: + if b'sort_dir' in form_data: sort_dir = form_data[b'sort_dir'][0].decode('utf-8') assert(sort_dir in ['asc', 'desc']), 'Invalid sort dir' filters['sort_dir'] = sort_dir diff --git a/bin/basicswap_prepare.py b/bin/basicswap_prepare.py index 1010acd..9894efa 100644 --- a/bin/basicswap_prepare.py +++ b/bin/basicswap_prepare.py @@ -40,7 +40,7 @@ else: BIN_ARCH = 'x86_64-linux-gnu.tar.gz' known_coins = { - 'particl': '0.18.1.4', + 'particl': '0.18.1.5', 'litecoin': '0.17.1', 'bitcoin': '0.18.1', 'namecoin': '0.18.0', diff --git a/tests/basicswap/test_reload.py b/tests/basicswap/test_reload.py index 7666a43..60c514b 100644 --- a/tests/basicswap/test_reload.py +++ b/tests/basicswap/test_reload.py @@ -9,8 +9,10 @@ export TEST_RELOAD_PATH=/tmp/test_basicswap mkdir -p ${TEST_RELOAD_PATH}/bin/{particl,bitcoin} -cp ~/tmp/particl-0.18.1.4-x86_64-linux-gnu.tar.gz ${TEST_RELOAD_PATH}/bin/particl +cp ~/tmp/particl-0.18.1.5-x86_64-linux-gnu.tar.gz ${TEST_RELOAD_PATH}/bin/particl cp ~/tmp/bitcoin-0.18.1-x86_64-linux-gnu.tar.gz ${TEST_RELOAD_PATH}/bin/bitcoin +export PYTHONPATH=$(pwd) +python tests/basicswap/test_reload.py """ @@ -28,6 +30,10 @@ from unittest.mock import patch from urllib.request import urlopen from urllib import parse +from basicswap.util import ( + callrpc_cli, +) + import bin.basicswap_prepare as prepareSystem import bin.basicswap_run as runSystem @@ -41,6 +47,12 @@ if not len(logger.handlers): logger.addHandler(logging.StreamHandler(sys.stdout)) +def btcRpc(client_no, cmd): + bin_path = os.path.join(test_path, 'bin', 'bitcoin') + data_path = os.path.join(test_path, 'client{}'.format(client_no), 'bitcoin') + return callrpc_cli(bin_path, data_path, 'regtest', cmd, 'bitcoin-cli') + + def waitForServer(): for i in range(20): try: @@ -51,6 +63,24 @@ def waitForServer(): traceback.print_exc() +def waitForNumOffers(port, offers): + for i in range(20): + summary = json.loads(urlopen('http://localhost:{}/json'.format(port)).read()) + if summary['num_network_offers'] >= offers: + return + time.sleep(1) + raise ValueError('waitForNumOffers failed') + + +def waitForNumBids(port, bids): + for i in range(20): + summary = json.loads(urlopen('http://localhost:{}/json'.format(port)).read()) + if summary['num_recv_bids'] >= bids: + return + time.sleep(1) + raise ValueError('waitForNumBids failed') + + class Test(unittest.TestCase): @classmethod def setUpClass(cls): @@ -72,7 +102,7 @@ class Test(unittest.TestCase): testargs = [ 'basicswap-prepare', '-datadir="{}"'.format(client_path), - '-bindir="{}"'.format(test_path + '/bin'), + '-bindir="{}"'.format(os.path.join(test_path, 'bin')), '-portoffset={}'.format(i), '-particl_mnemonic="{}"'.format(mnemonics[i]), '-regtest', '-withoutcoin=litecoin', '-withcoin=bitcoin'] @@ -81,10 +111,26 @@ class Test(unittest.TestCase): with open(os.path.join(client_path, 'particl', 'particl.conf'), 'a') as fp: fp.write('port={}\n'.format(PARTICL_PORT_BASE + i)) + fp.write('bind=127.0.0.1\n') + fp.write('dnsseed=0\n') for ip in range(3): fp.write('addnode=localhost:{}\n'.format(PARTICL_PORT_BASE + ip)) - with open(os.path.join(client_path, 'bitcoin', 'bitcoin.conf'), 'a') as fp: + + # Pruned nodes don't provide blocks + with open(os.path.join(client_path, 'bitcoin', 'bitcoin.conf'), 'r') as fp: + lines = fp.readlines() + with open(os.path.join(client_path, 'bitcoin', 'bitcoin.conf'), 'w') as fp: + for line in lines: + if not line.startswith('prune'): + fp.write(line) fp.write('port={}\n'.format(BITCOIN_PORT_BASE + i)) + fp.write('discover=0\n') + fp.write('dnsseed=0\n') + fp.write('listenonion=0\n') + fp.write('upnp=0\n') + fp.write('bind=127.0.0.1\n') + for ip in range(3): + fp.write('connect=localhost:{}\n'.format(BITCOIN_PORT_BASE + ip)) assert(os.path.exists(config_path)) @@ -104,6 +150,17 @@ class Test(unittest.TestCase): try: waitForServer() + num_blocks = 500 + btc_addr = btcRpc(1, 'getnewaddress mining_addr bech32') + logging.info('Mining %d bitcoin blocks to %s', num_blocks, btc_addr) + btcRpc(1, 'generatetoaddress {} {}'.format(num_blocks, btc_addr)) + + for i in range(20): + blocks = btcRpc(0, 'getblockchaininfo')['blocks'] + if blocks >= 500: + break + assert(blocks >= 500) + data = parse.urlencode({ 'addr_from': '-1', 'coin_from': '1', @@ -118,8 +175,22 @@ class Test(unittest.TestCase): except Exception: traceback.print_exc() + logger.info('Waiting for offer:') + waitForNumOffers(12701, 1) + + offers = json.loads(urlopen('http://localhost:12701/json/offers').read()) + offer = offers[0] + + data = parse.urlencode({ + 'offer_id': offer['offer_id'], + 'amount_from': offer['amount_from']}).encode() + + bid_id = json.loads(urlopen('http://localhost:12701/json/bids/new', data=data).read()) + + waitForNumBids(12700, 1) + + logger.warning('TODO') - time.sleep(5) for p in processes: p.terminate()