diff --git a/basicswap/__init__.py b/basicswap/__init__.py
index 4d078a3..a9e322c 100644
--- a/basicswap/__init__.py
+++ b/basicswap/__init__.py
@@ -1,3 +1,3 @@
name = "basicswap"
-__version__ = "0.0.21"
+__version__ = "0.0.22"
diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py
index dfce850..2c2ee59 100644
--- a/basicswap/basicswap.py
+++ b/basicswap/basicswap.py
@@ -6,6 +6,7 @@
import os
import re
+import sys
import zmq
import json
import time
@@ -20,6 +21,7 @@ import threading
import traceback
import sqlalchemy as sa
import collections
+import concurrent.futures
from enum import IntEnum, auto
from sqlalchemy.orm import sessionmaker, scoped_session
@@ -83,6 +85,7 @@ from .db import (
XmrOffer,
XmrSwap,
XmrSplitData,
+ Wallets,
)
from .base import BaseApp
from .explorers import (
@@ -462,6 +465,8 @@ class BasicSwap(BaseApp):
self._last_checked_events = 0
self._last_checked_xmr_swaps = 0
self._possibly_revoked_offers = collections.deque([], maxlen=48) # TODO: improve
+ self._updating_wallets_info = {}
+ self._last_updated_wallets_info = 0
# TODO: Adjust ranges
self.min_delay_event = self.settings.get('min_delay_event', 10)
@@ -480,6 +485,7 @@ class BasicSwap(BaseApp):
self.SMSG_SECONDS_IN_HOUR = 60 * 60 # Note: Set smsgsregtestadjust=0 for regtest
self.threads = []
+ self.thread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=4, thread_name_prefix='bsp')
# Encode key to match network
wif_prefix = chainparams[Coins.PART][self.chain]['key_prefix']
@@ -573,6 +579,11 @@ class BasicSwap(BaseApp):
for t in self.threads:
t.join()
+ if sys.version_info[1] >= 9:
+ self.thread_pool.shutdown(cancel_futures=True)
+ else:
+ self.thread_pool.shutdown()
+
close_all_sessions()
self.engine.dispose()
@@ -828,6 +839,9 @@ class BasicSwap(BaseApp):
created_at BIGINT,
PRIMARY KEY (record_id))''')
db_version += 1
+ elif current_version == 9:
+ session.execute('ALTER TABLE wallets ADD COLUMN wallet_data VARCHAR')
+ db_version += 1
if current_version != db_version:
self.db_version = db_version
@@ -5093,6 +5107,46 @@ class BasicSwap(BaseApp):
return rv
+ def updateWalletInfo(self, coin):
+ wi = self.getWalletInfo(coin)
+
+ # Store wallet info to db so it's available after startup
+ self.mxDB.acquire()
+ try:
+ rv = []
+ now = int(time.time())
+ session = scoped_session(self.session_factory)
+
+ session.add(Wallets(coin_id=coin, wallet_data=json.dumps(wi), created_at=now))
+
+ coin_id = int(coin)
+ query_str = f'DELETE FROM wallets WHERE coin_id = {coin_id} AND record_id NOT IN (SELECT record_id FROM wallets WHERE coin_id = {coin_id} ORDER BY created_at DESC LIMIT 3 )'
+ session.execute(query_str)
+ session.commit()
+ except Exception as e:
+ self.log.error(f'updateWalletInfo {e}')
+
+ finally:
+ session.close()
+ session.remove()
+ self._updating_wallets_info[int(coin)] = False
+ self.mxDB.release()
+
+ def updateWalletsInfo(self, force_update=False, only_coin=None):
+ now = int(time.time())
+ if not force_update and now - self._last_updated_wallets_info < 30:
+ return
+ for c in Coins:
+ if only_coin is not None and c != only_coin:
+ continue
+ if c not in chainparams:
+ continue
+ if self.coin_clients[c]['connection_type'] == 'rpc':
+ self._updating_wallets_info[int(c)] = True
+ self.thread_pool.submit(self.updateWalletInfo, c)
+ if only_coin is None:
+ self._last_updated_wallets_info = int(time.time())
+
def getWalletsInfo(self, opts=None):
rv = {}
for c in Coins:
@@ -5105,6 +5159,44 @@ class BasicSwap(BaseApp):
rv[c] = {'name': chainparams[c]['name'].capitalize(), 'error': str(ex)}
return rv
+ def getCachedWalletsInfo(self, opts=None):
+ rv = {}
+ # Requires? self.mxDB.acquire()
+ try:
+ session = scoped_session(self.session_factory)
+ inner_str = 'SELECT coin_id, MAX(created_at) as max_created_at FROM wallets GROUP BY coin_id'
+ query_str = 'SELECT a.coin_id, wallet_data, created_at FROM wallets a, ({}) b WHERE a.coin_id = b.coin_id AND a.created_at = b.max_created_at'.format(inner_str)
+
+ q = session.execute(query_str)
+ for row in q:
+ coin_id = row[0]
+ wallet_data = json.loads(row[1])
+ wallet_data['lastupdated'] = row[2]
+ wallet_data['updating'] = self._updating_wallets_info.get(coin_id, False)
+
+ # Ensure the latest deposit address is displayed
+ q = session.execute('SELECT value FROM kv_string WHERE key = "receive_addr_{}"'.format(chainparams[coin_id]['name']))
+ for row in q:
+ wallet_data['deposit_address'] = row[0]
+
+ rv[coin_id] = wallet_data
+ finally:
+ session.close()
+ session.remove()
+
+ for c in Coins:
+ if c not in chainparams:
+ continue
+ if self.coin_clients[c]['connection_type'] == 'rpc':
+ coin_id = int(c)
+ if coin_id not in rv:
+ rv[coin_id] = {
+ 'name': chainparams[c]['name'].capitalize(),
+ 'updating': self._updating_wallets_info.get(coin_id, False),
+ }
+
+ return rv
+
def countAcceptedBids(self, offer_id=None):
self.mxDB.acquire()
try:
diff --git a/basicswap/db.py b/basicswap/db.py
index f0320f3..aa3edd6 100644
--- a/basicswap/db.py
+++ b/basicswap/db.py
@@ -12,7 +12,7 @@ from enum import IntEnum, auto
from sqlalchemy.ext.declarative import declarative_base
-CURRENT_DB_VERSION = 9
+CURRENT_DB_VERSION = 10
Base = declarative_base()
@@ -360,6 +360,7 @@ class Wallets(Base):
record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
coin_id = sa.Column(sa.Integer)
wallet_name = sa.Column(sa.String)
+ wallet_data = sa.Column(sa.String)
balance_type = sa.Column(sa.Integer)
amount = sa.Column(sa.BigInteger)
updated_at = sa.Column(sa.BigInteger)
diff --git a/basicswap/http_server.py b/basicswap/http_server.py
index 9f07f65..cbc9c02 100644
--- a/basicswap/http_server.py
+++ b/basicswap/http_server.py
@@ -279,11 +279,16 @@ class HttpHandler(BaseHTTPRequestHandler):
messages.append('Withdrew {} {} to address {}
In txid: {}'.format(value, ticker, address, txid))
except Exception as e:
messages.append('Error: {}'.format(str(e)))
+ swap_client.updateWalletsInfo(True, c)
- wallets = swap_client.getWalletsInfo()
+ swap_client.updateWalletsInfo()
+ wallets = swap_client.getCachedWalletsInfo()
wallets_formatted = []
- for k, w in wallets.items():
+ sk = sorted(wallets.keys())
+
+ for k in sk:
+ w = wallets[k]
if 'error' in w:
wallets_formatted.append({
'cid': str(int(k)),
@@ -291,6 +296,14 @@ class HttpHandler(BaseHTTPRequestHandler):
})
continue
+ if 'balance' not in w:
+ wallets_formatted.append({
+ 'name': w['name'],
+ 'havedata': False,
+ 'updating': w['updating'],
+ })
+ continue
+
ci = swap_client.ci(k)
fee_rate, fee_src = swap_client.getFeeRateForCoin(k)
est_fee = swap_client.estimateWithdrawFee(k, fee_rate)
@@ -308,11 +321,16 @@ class HttpHandler(BaseHTTPRequestHandler):
'deposit_address': w['deposit_address'],
'expected_seed': w['expected_seed'],
'balance_all': float(w['balance']) + float(w['unconfirmed']),
+ 'updating': w['updating'],
+ 'lastupdated': format_timestamp(w['lastupdated']),
+ 'havedata': True,
}
if float(w['unconfirmed']) > 0.0:
wf['unconfirmed'] = w['unconfirmed']
if k == Coins.PART:
+
+ wf['stealth_address'] = w['stealth_address']
wf['blind_balance'] = w['blind_balance']
if float(w['blind_unconfirmed']) > 0.0:
wf['blind_unconfirmed'] = w['blind_unconfirmed']
diff --git a/basicswap/templates/wallets.html b/basicswap/templates/wallets.html
index 8c56d5a..6ef5d50 100644
--- a/basicswap/templates/wallets.html
+++ b/basicswap/templates/wallets.html
@@ -1,5 +1,7 @@
{% include 'header.html' %}
+
Page Refresh: {{ refresh }} seconds
@@ -13,10 +15,15 @@ {% for w in wallets %}Error: {{ w.error }}
{% else %}Last updated: | {{ w.lastupdated }} | ||
Balance: | {{ w.balance }} | {% if w.unconfirmed %}Unconfirmed: | {{ w.unconfirmed }} | {% endif %}
Fee Rate: | {{ w.fee_rate }} | Est Fee: | {{ w.est_fee }} |