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 %}
+
+
+
+home
+