From 3e542e6bd0102d2544719ef1c1f6a172f95b87ee Mon Sep 17 00:00:00 2001 From: tecnovert Date: Tue, 6 Aug 2019 00:04:40 +0200 Subject: [PATCH] Adding settings page. --- basicswap/basicswap.py | 63 +++++++++++++++++++++++++++---- basicswap/db.py | 1 - basicswap/explorers.py | 23 ++++++++++- basicswap/http_server.py | 30 ++++++++++++++- basicswap/templates/index.html | 1 + basicswap/templates/settings.html | 27 +++++++++++++ bin/basicswap_prepare.py | 4 ++ 7 files changed, 138 insertions(+), 11 deletions(-) create mode 100644 basicswap/templates/settings.html diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index c517474..b6c6586 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -15,6 +15,8 @@ import hashlib import subprocess import logging import sqlalchemy as sa +import shutil +import json from sqlalchemy.orm import sessionmaker, scoped_session from enum import IntEnum, auto @@ -453,6 +455,7 @@ class BasicSwap(): 'pid': None, 'core_version': None, 'explorers': [], + 'chain_lookups': chain_client_settings.get('chain_lookups', 'local'), } def setDaemonPID(self, name, pid): @@ -637,13 +640,13 @@ class BasicSwap(): if bid.state == BidStates.BID_ABANDONED or bid.state == BidStates.SWAP_COMPLETED: # Return unused addrs to pool if bid.getITxState() != TxStates.TX_REDEEMED: - self.returnAddressToPool(bid_id, TxTypes.ITX_REDEEM) + self.returnAddressToPool(bid.bid_id, TxTypes.ITX_REDEEM) if bid.getITxState() != TxStates.TX_REFUNDED: - self.returnAddressToPool(bid_id, TxTypes.ITX_REFUND) + self.returnAddressToPool(bid.bid_id, TxTypes.ITX_REFUND) if bid.getPTxState() != TxStates.TX_REDEEMED: - self.returnAddressToPool(bid_id, TxTypes.PTX_REDEEM) + self.returnAddressToPool(bid.bid_id, TxTypes.PTX_REDEEM) if bid.getPTxState() != TxStates.TX_REFUNDED: - self.returnAddressToPool(bid_id, TxTypes.PTX_REFUND) + self.returnAddressToPool(bid.bid_id, TxTypes.PTX_REFUND) def loadFromDB(self): self.log.info('Loading data from db') @@ -1700,12 +1703,40 @@ class BasicSwap(): # bid saved in checkBidState + def getAddressBalance(self, coin_type, address): + if self.coin_clients[coin_type]['chain_lookups'] == 'explorer': + explorers = self.coin_clients[coin_type]['explorers'] + + # TODO: random offset into explorers, try blocks + for exp in explorers: + return exp.getBalance(address) + return self.lookupUnspentByAddress(coin_type, address, sum_output=True) + def lookupChainHeight(self, coin_type): return self.callcoinrpc(coin_type, 'getblockchaininfo')['blocks'] def lookupUnspentByAddress(self, coin_type, address, sum_output=False, assert_amount=None, assert_txid=None): - # TODO: Lookup from explorers + if self.coin_clients[coin_type]['chain_lookups'] == 'explorer': + explorers = self.coin_clients[coin_type]['explorers'] + + # 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) + + if assert_amount is not None: + assert(rv['value'] == int(assert_amount)), 'Incorrect output amount in txn {}: {} != {}.'.format(assert_txid, rv['value'], int(assert_amount)) + if assert_txid is not None: + assert(rv['txid)'] == assert_txid), 'Incorrect txid' + + return rv + + raise ValueError('No explorer for lookupUnspentByAddress {}'.format(str(coin_type))) + + if self.coin_clients[coin_type]['connection_type'] != 'rpc': + raise ValueError('No RPC connection for lookupUnspentByAddress {}'.format(str(coin_type))) if assert_txid is not None: try: @@ -1739,6 +1770,7 @@ class BasicSwap(): 'index': o['vout'], 'height': o['height'], 'n_conf': n_conf, + 'value': makeInt(o['amount']), } else: sum_unspent += o['amount'] * COIN @@ -2153,7 +2185,7 @@ class BasicSwap(): else: addr_search = bid_data.proof_address - sum_unspent = self.lookupUnspentByAddress(coin_to, addr_search, sum_output=True) + sum_unspent = self.getAddressBalance(coin_to, addr_search) self.log.debug('Proof of funds %s %s', bid_data.proof_address, format8(sum_unspent)) assert(sum_unspent >= bid_data.amount), 'Proof of funds failed' @@ -2366,7 +2398,7 @@ class BasicSwap(): if has_changed: session = scoped_session(self.session_factory) try: - self.saveBidInSession(session, bid) + self.saveBidInSession(bid_id, bid, session) session.commit() if bid.state and bid.state > BidStates.BID_RECEIVED and bid.state < BidStates.SWAP_COMPLETED: self.activateBid(session, bid) @@ -2380,6 +2412,23 @@ class BasicSwap(): finally: self.mxDB.release() + def editSettings(self, coin_name, data): + self.log.info('Updating settings %s', coin_name) + self.mxDB.acquire() + try: + if 'lookups' in data: + self.settings['chainclients'][coin_name]['chain_lookups'] = data['lookups'] + settings_path = os.path.join(self.data_dir, 'basicswap.json') + shutil.copyfile(settings_path, settings_path + '.last') + with open(settings_path, 'w') as fp: + json.dump(self.settings, fp, indent=4) + + for c in self.coin_clients: + if c['name'] == coin_name: + c['chain_lookups'] = data['lookups'] + finally: + self.mxDB.release() + def getSummary(self, opts=None): num_watched_outputs = 0 for c, v in self.coin_clients.items(): diff --git a/basicswap/db.py b/basicswap/db.py index 6f29519..f493220 100644 --- a/basicswap/db.py +++ b/basicswap/db.py @@ -190,4 +190,3 @@ class EventQueue(Base): trigger_at = sa.Column(sa.BigInteger) linked_id = sa.Column(sa.LargeBinary) event_type = sa.Column(sa.Integer) - diff --git a/basicswap/explorers.py b/basicswap/explorers.py index df53f69..bc68534 100644 --- a/basicswap/explorers.py +++ b/basicswap/explorers.py @@ -48,6 +48,7 @@ class ExplorerInsight(Explorer): 'index': utxo['vout'], 'height': utxo['height'], 'n_conf': utxo['confirmations'], + 'value': utxo['satoshis'], }) return rv @@ -66,10 +67,28 @@ class ExplorerBitAps(Explorer): def getBalance(self, address): data = json.loads(self.readURL(self.base_url + '/address/state/' + address)) - return data + return data['data']['balance'] def lookupUnspentByAddress(self, address): - return json.loads(self.readURL(self.base_url + '/address/transactions/' + address)) + # Can't get unspents return only if exactly one transaction exists + data = json.loads(self.readURL(self.base_url + '/address/transactions/' + address)) + try: + assert(data['data']['list'] == 1) + except Exception as ex: + self.log.debug('Explorer error: {}'.format(str(ex))) + return None + tx = data['data']['list'][0] + tx_data = json.loads(self.readURL(self.base_url + '/transaction/{}'.format(tx['txId'])))['data'] + + for i, vout in tx_data['vOut'].items(): + if vout['address'] == address: + return [{ + 'txid': tx_data['txId'], + 'index': int(i), + 'height': tx_data['blockHeight'], + 'n_conf': tx_data['confirmations'], + 'value': vout['value'], + }] class ExplorerChainz(Explorer): diff --git a/basicswap/http_server.py b/basicswap/http_server.py index 0a0a0a3..64f246f 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -204,7 +204,7 @@ class HttpHandler(BaseHTTPRequestHandler): explorer = form_data[b'explorer'][0].decode('utf-8') action = form_data[b'action'][0].decode('utf-8') - args = '' if not b'args' in form_data else form_data[b'args'][0].decode('utf-8') + args = '' if b'args' not in form_data else form_data[b'args'][0].decode('utf-8') try: c, e = explorer.split('_') exp = swap_client.coin_clients[Coins(int(c))]['explorers'][int(e)] @@ -331,6 +331,32 @@ class HttpHandler(BaseHTTPRequestHandler): form_id=os.urandom(8).hex(), ), 'UTF-8') + def page_settings(self, url_split, post_string): + swap_client = self.server.swap_client + + messages = [] + form_data = self.checkForm(post_string, 'settings', messages) + if form_data: + for name, c in swap_client.settings['chainclients'].items(): + if bytes('apply_' + name, 'utf-8') in form_data: + data = {'lookups': form_data[bytes('lookups_' + name, 'utf-8')][0].decode('utf-8')} + swap_client.editSettings(name, data) + chains_formatted = [] + + for name, c in swap_client.settings['chainclients'].items(): + chains_formatted.append({ + 'name': name, + 'lookups': c.get('chain_lookups', 'local') + }) + + template = env.get_template('settings.html') + return bytes(template.render( + title=self.server.title, + h2=self.server.title, + chains=chains_formatted, + form_id=os.urandom(8).hex(), + ), 'UTF-8') + def page_newoffer(self, url_split, post_string): swap_client = self.server.swap_client @@ -739,6 +765,8 @@ class HttpHandler(BaseHTTPRequestHandler): return self.page_active(url_split, post_string) if url_split[1] == 'wallets': return self.page_wallets(url_split, post_string) + if url_split[1] == 'settings': + return self.page_settings(url_split, post_string) if url_split[1] == 'rpc': return self.page_rpc(url_split, post_string) if url_split[1] == 'explorers': diff --git a/basicswap/templates/index.html b/basicswap/templates/index.html index 57890b1..22a11cb 100644 --- a/basicswap/templates/index.html +++ b/basicswap/templates/index.html @@ -8,6 +8,7 @@ Version: {{ version }}

View Wallets
+Settings
RPC Console
Explorers

diff --git a/basicswap/templates/settings.html b/basicswap/templates/settings.html new file mode 100644 index 0000000..5cfa437 --- /dev/null +++ b/basicswap/templates/settings.html @@ -0,0 +1,27 @@ +{% include 'header.html' %} + +

Settings

+ +{% for m in messages %} +

{{ m }}

+{% endfor %} + +
+ +{% for c in chains %} +

{{ c.name }}

+ + + +
Chain Lookups +
+{% endfor %} + + +
+ +

home

+ \ No newline at end of file diff --git a/bin/basicswap_prepare.py b/bin/basicswap_prepare.py index 54010bb..1557619 100644 --- a/bin/basicswap_prepare.py +++ b/bin/basicswap_prepare.py @@ -383,6 +383,7 @@ def main(): 'override_feerate': 0.002, 'conf_target': 2, 'core_version_group': 18, + 'chain_lookups': 'local', }, 'litecoin': { 'connection_type': 'rpc' if 'litecoin' in with_coins else 'none', @@ -394,6 +395,7 @@ def main(): 'blocks_confirmed': 2, 'conf_target': 2, 'core_version_group': 17, + 'chain_lookups': 'local', }, 'bitcoin': { 'connection_type': 'rpc' if 'bitcoin' in with_coins else 'none', @@ -405,6 +407,7 @@ def main(): 'blocks_confirmed': 1, 'conf_target': 2, 'core_version_group': 18, + 'chain_lookups': 'local', }, 'namecoin': { 'connection_type': 'rpc' if 'namecoin' in with_coins else 'none', @@ -417,6 +420,7 @@ def main(): 'blocks_confirmed': 1, 'conf_target': 2, 'core_version_group': 18, + 'chain_lookups': 'local', } }