diff --git a/.travis.yml b/.travis.yml index 6b29c61..dc6a34b 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.1/bin/ + - PARTICL_BINDIR=/opt/binaries/particl-0.18.1.2/bin/ - BITCOIN_BINDIR=/opt/binaries/bitcoin-0.18.0/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.0/bitcoin-0.18.0-x86_64-linux-gnu.tar.gz && tar xvf bitcoin-0.18.0-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.1/particl-0.18.1.1-x86_64-linux-gnu_nousb.tar.gz && tar xvf particl-0.18.1.1-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.2/particl-0.18.1.2-x86_64-linux-gnu_nousb.tar.gz && tar xvf particl-0.18.1.2-x86_64-linux-gnu_nousb.tar.gz ; fi script: - cd $TRAVIS_BUILD_DIR - - export PARTICL_BINDIR=/opt/binaries/particl-0.18.1.1/bin/ + - export PARTICL_BINDIR=/opt/binaries/particl-0.18.1.2/bin/ - export BITCOIN_BINDIR=/opt/binaries/bitcoin-0.18.0/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 64f246f..b3a3fd0 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -123,7 +123,7 @@ def validateAmountString(amount): if type(amount) != str: return ar = amount.split('.') - if len(ar) > 0 and len(ar[1]) > 8: + if len(ar) > 1 and len(ar[1]) > 8: raise ValueError('Too many decimal places in amount {}'.format(amount)) @@ -166,18 +166,28 @@ class HttpHandler(BaseHTTPRequestHandler): error_str_json = json.dumps({'error': error_str}) return bytes(error_str_json, 'UTF-8') - def js_wallets(self, url_split): + 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): + def js_offers(self, url_split, post_string): + if len(url_split) > 3: + if url_split[3] == 'new': + if post_string == '': + raise ValueError('No post data') + form_data = urllib.parse.parse_qs(post_string) + offer_id = self.postNewOffer(form_data) + rv = {'offer_id': offer_id.hex()} + 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') - def js_sentoffers(self, url_split): + def js_sentoffers(self, url_split, post_string): assert(False), 'TODO' return bytes(json.dumps(self.server.swap_client.listOffers(sent=True)), 'UTF-8') - def js_bids(self, url_split): + def js_bids(self, url_split, post_string): if len(url_split) > 3: bid_id = bytes.fromhex(url_split[3]) assert(len(bid_id) == 28) @@ -185,10 +195,10 @@ class HttpHandler(BaseHTTPRequestHandler): assert(False), 'TODO' return bytes(json.dumps(self.server.swap_client.listBids()), 'UTF-8') - def js_sentbids(self, url_split): + 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): + def js_index(self, url_split, post_string): return bytes(json.dumps(self.server.swap_client.getSummary()), 'UTF-8') def page_explorers(self, url_split, post_string): @@ -357,47 +367,52 @@ class HttpHandler(BaseHTTPRequestHandler): form_id=os.urandom(8).hex(), ), 'UTF-8') + def postNewOffer(self, form_data): + swap_client = self.server.swap_client + addr_from = form_data[b'addr_from'][0].decode('utf-8') + if addr_from == '-1': + addr_from = None + + try: + coin_from = Coins(int(form_data[b'coin_from'][0])) + except Exception: + raise ValueError('Unknown Coin From') + try: + coin_to = Coins(int(form_data[b'coin_to'][0])) + 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) + + min_bid = int(value_from) + rate = int((value_to / value_from) * COIN) + autoaccept = True if b'autoaccept' in form_data else False + lock_seconds = int(form_data[b'lockhrs'][0]) * 60 * 60 + # TODO: More accurate rate + # assert(value_to == (value_from * rate) // COIN) + + if swap_client.coin_clients[coin_from]['use_csv'] and swap_client.coin_clients[coin_to]['use_csv']: + lock_type = SEQUENCE_LOCK_TIME + else: + lock_type = ABS_LOCK_TIME + + offer_id = swap_client.postOffer(coin_from, coin_to, value_from, rate, min_bid, SwapTypes.SELLER_FIRST, lock_type=lock_type, lock_value=lock_seconds, auto_accept_bids=autoaccept, addr_send_from=addr_from) + return offer_id + def page_newoffer(self, url_split, post_string): swap_client = self.server.swap_client messages = [] form_data = self.checkForm(post_string, 'newoffer', messages) if form_data: - addr_from = form_data[b'addr_from'][0].decode('utf-8') - if addr_from == '-1': - addr_from = None - - try: - coin_from = Coins(int(form_data[b'coin_from'][0])) - except Exception: - raise ValueError('Unknown Coin From') - try: - coin_to = Coins(int(form_data[b'coin_to'][0])) - 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) - - min_bid = int(value_from) - rate = int((value_to / value_from) * COIN) - autoaccept = True if b'autoaccept' in form_data else False - lock_seconds = int(form_data[b'lockhrs'][0]) * 60 * 60 - # TODO: More accurate rate - # assert(value_to == (value_from * rate) // COIN) - - if swap_client.coin_clients[coin_from]['use_csv'] and swap_client.coin_clients[coin_to]['use_csv']: - lock_type = SEQUENCE_LOCK_TIME - else: - lock_type = ABS_LOCK_TIME - - offer_id = swap_client.postOffer(coin_from, coin_to, value_from, rate, min_bid, SwapTypes.SELLER_FIRST, lock_type=lock_type, lock_value=lock_seconds, auto_accept_bids=autoaccept, addr_send_from=addr_from) - messages.append('Sent Offer ' + offer_id.hex() + '
Rate: ' + format8(rate)) + offer_id = self.postNewOffer(form_data) + messages.append('Sent Offer {}'.format(offer_id.hex())) template = env.get_template('offer_new.html') return bytes(template.render( @@ -755,8 +770,10 @@ class HttpHandler(BaseHTTPRequestHandler): 'bids': self.js_bids, 'sentbids': self.js_sentbids, }.get(url_split[2], self.js_index) - return func(url_split) + return func(url_split, post_string) except Exception as ex: + if self.server.swap_client.debug is True: + traceback.print_exc() return self.js_error(str(ex)) try: self.putHeaders(status_code, 'text/html') @@ -793,7 +810,8 @@ class HttpHandler(BaseHTTPRequestHandler): return self.page_shutdown(url_split, post_string) return self.page_index(url_split) except Exception as ex: - traceback.print_exc() # TODO: Remove + if self.server.swap_client.debug is True: + traceback.print_exc() return self.page_error(str(ex)) def do_GET(self): diff --git a/bin/basicswap_prepare.py b/bin/basicswap_prepare.py index 1557619..5c6ef60 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.1', + 'particl': '0.18.1.2', 'litecoin': '0.17.1', 'bitcoin': '0.18.0', 'namecoin': '0.18.0', @@ -247,6 +247,7 @@ def printHelp(): logger.info('\n--help, -h Print help.') logger.info('--version, -v Print version.') logger.info('--datadir=PATH Path to basicswap data directory, default:~/.basicswap.') + logger.info('--bindir=PATH Path to cores directory, default:datadir/bin.') logger.info('--mainnet Run in mainnet mode.') logger.info('--testnet Run in testnet mode.') logger.info('--regtest Run in regtest mode.') @@ -257,6 +258,7 @@ def printHelp(): logger.info('--addcoin= Add coin to existing setup.') logger.info('--disablecoin= Make coin inactive.') logger.info('--preparebinonly Don\'t prepare settings or datadirs.') + logger.info('--portoffset=n Raise all ports by n.') logger.info('\n' + 'Known coins: %s', ', '.join(known_coins.keys())) @@ -293,6 +295,8 @@ def exitWithError(error_msg): def main(): data_dir = None + bin_dir = None + port_offset = None chain = 'mainnet' particl_wallet_mnemonic = None prepare_bin_only = False @@ -331,10 +335,16 @@ def main(): if len(s) == 2: if name == 'datadir': - data_dir = os.path.expanduser(s[1]) + data_dir = os.path.expanduser(s[1].strip('"')) + continue + if name == 'bindir': + bin_dir = os.path.expanduser(s[1].strip('"')) + continue + if name == 'portoffset': + port_offset = int(s[1]) continue if name == 'particl_mnemonic': - particl_wallet_mnemonic = s[1] + particl_wallet_mnemonic = s[1].strip('"') continue if name == 'withcoin': if s[1] not in known_coins: @@ -363,9 +373,14 @@ def main(): if data_dir is None: default_datadir = '~/.basicswap' data_dir = os.path.join(os.path.expanduser(default_datadir)) + if bin_dir is None: + bin_dir = os.path.join(data_dir, 'bin') + logger.info('Using datadir: %s', data_dir) logger.info('Chain: %s', chain) - port_offset = 300 if chain == 'testnet' else 0 + + if port_offset is None: + port_offset = 300 if chain == 'testnet' else 0 if not os.path.exists(data_dir): os.makedirs(data_dir) @@ -378,7 +393,7 @@ def main(): 'manage_daemon': True, 'rpcport': 19792 + port_offset, 'datadir': os.path.join(data_dir, 'particl'), - 'bindir': os.path.join(data_dir, 'bin', 'particl'), + 'bindir': os.path.join(bin_dir, 'particl'), 'blocks_confirmed': 2, 'override_feerate': 0.002, 'conf_target': 2, @@ -390,7 +405,7 @@ def main(): 'manage_daemon': True if 'litecoin' in with_coins else False, 'rpcport': 19795 + port_offset, 'datadir': os.path.join(data_dir, 'litecoin'), - 'bindir': os.path.join(data_dir, 'bin', 'litecoin'), + 'bindir': os.path.join(bin_dir, 'litecoin'), 'use_segwit': True, 'blocks_confirmed': 2, 'conf_target': 2, @@ -402,7 +417,7 @@ def main(): 'manage_daemon': True if 'bitcoin' in with_coins else False, 'rpcport': 19796 + port_offset, 'datadir': os.path.join(data_dir, 'bitcoin'), - 'bindir': os.path.join(data_dir, 'bin', 'bitcoin'), + 'bindir': os.path.join(bin_dir, 'bitcoin'), 'use_segwit': True, 'blocks_confirmed': 1, 'conf_target': 2, @@ -414,7 +429,7 @@ def main(): 'manage_daemon': True if 'namecoin' in with_coins else False, 'rpcport': 19798 + port_offset, 'datadir': os.path.join(data_dir, 'namecoin'), - 'bindir': os.path.join(data_dir, 'bin', 'namecoin'), + 'bindir': os.path.join(bin_dir, 'namecoin'), 'use_segwit': False, 'use_csv': False, 'blocks_confirmed': 1, diff --git a/tests/basicswap/test_reload.py b/tests/basicswap/test_reload.py index bb581bc..a2eb34c 100644 --- a/tests/basicswap/test_reload.py +++ b/tests/basicswap/test_reload.py @@ -8,21 +8,25 @@ """ mkdir -p /tmp/test_basicswap/bin/{particl,bitcoin} -cp ~/tmp/particl-0.18.1.1-x86_64-linux-gnu.tar.gz /tmp/test_basicswap/bin/particl +cp ~/tmp/particl-0.18.1.2-x86_64-linux-gnu.tar.gz /tmp/test_basicswap/bin/particl cp ~/tmp/bitcoin-0.18.0-x86_64-linux-gnu.tar.gz /tmp/test_basicswap/bin/bitcoin - """ import os import sys import time import unittest -from unittest.mock import patch import logging import shutil import threading +import json +import traceback +from unittest.mock import patch +from urllib.request import urlopen +from urllib import parse + import bin.basicswap_prepare as prepareSystem import bin.basicswap_run as runSystem @@ -34,41 +38,78 @@ if not len(logger.handlers): logger.addHandler(logging.StreamHandler(sys.stdout)) +def waitForServer(): + for i in range(20): + try: + time.sleep(1) + summary = json.loads(urlopen('http://localhost:12700/json').read()) + break + except Exception: + traceback.print_exc() + + class Test(unittest.TestCase): @classmethod def setUpClass(cls): super(Test, cls).setUpClass() - config_path = os.path.join(test_path, 'basicswap.json') - try: - os.remove(config_path) - shutil.rmtree(os.path.join(test_path, 'particl')) - shutil.rmtree(os.path.join(test_path, 'bitcoin')) - except Exception as ex: - logger.warning('setUpClass %s', str(ex)) + mnemonics = [ + 'abandon baby cabbage dad eager fabric gadget habit ice kangaroo lab absorb', + 'actuel comédie poésie noble facile éprouver brave cellule rotule académie hilarant chambre', + 'ちしき いてざ きおち あしあと ぽちぶくろ こえる さつえい むえき あける ほんき むさぼる ねいろ', + ] - testargs = ['basicswap-prepare', '-datadir=' + test_path, '-regtest', '-withoutcoin=litecoin', '-withcoin=bitcoin'] - with patch.object(sys, 'argv', testargs): - prepareSystem.main() + for i in range(3): + client_path = os.path.join(test_path, 'client{}'.format(i)) + config_path = os.path.join(client_path, 'basicswap.json') + try: + shutil.rmtree(client_path) + except Exception as ex: + logger.warning('setUpClass %s', str(ex)) + testargs = ['basicswap-prepare', + '-datadir="{}"'.format(client_path), + '-bindir="{}"'.format(test_path + '/bin'), + '-portoffset={}'.format(i), + '-particl_mnemonic="{}"'.format(mnemonics[i]), + '-regtest', '-withoutcoin=litecoin', '-withcoin=bitcoin'] + with patch.object(sys, 'argv', testargs): + prepareSystem.main() - assert(os.path.exists(config_path)) + assert(os.path.exists(config_path)) - def run_thread(self): - testargs = ['basicswap-run', '-datadir=' + test_path, '-regtest', '-testmode'] + def run_thread(self, client_id): + client_path = os.path.join(test_path, 'client{}'.format(client_id)) + testargs = ['basicswap-run', '-datadir=' + client_path, '-regtest', '-testmode'] with patch.object(sys, 'argv', testargs): runSystem.main() def test_reload(self): - thread = threading.Thread(target=self.run_thread) - thread.start() + thread0 = threading.Thread(target=self.run_thread, args=(0,)) + thread0.start() + + try: + waitForServer() + data = parse.urlencode({ + 'addr_from': '-1', + 'coin_from': '1', + 'coin_to': '2', + 'amt_from': '1', + 'amt_to': '1', + 'lockhrs': '24'}).encode() + + offer_id = json.loads(urlopen('http://localhost:12700/json/offers/new', data=data).read()) + summary = json.loads(urlopen('http://localhost:12700/json').read()) + assert(summary['num_sent_offers'] == 1) + except Exception: + traceback.print_exc() logger.warning('TODO') time.sleep(5) runSystem.swap_client.stopRunning() - thread.join() + thread0.join() if __name__ == '__main__':