From 8daa76f9374678fb3c267e02f94ede55b87296bb Mon Sep 17 00:00:00 2001 From: tecnovert Date: Mon, 23 May 2022 23:51:06 +0200 Subject: [PATCH] refactor: Add automation tables. --- basicswap/basicswap.py | 229 +++++++++--------- basicswap/db.py | 63 ++++- basicswap/db_upgrades.py | 183 ++++++++++++++ basicswap/http_server.py | 22 +- .../templates/automation_strategies.html | 38 +++ basicswap/templates/automation_strategy.html | 27 +++ .../templates/automation_strategy_new.html | 11 + basicswap/templates/index.html | 1 + basicswap/ui/page_automation.py | 110 +++++++++ basicswap/ui/page_offers.py | 20 +- basicswap/ui/util.py | 11 + doc/install.md | 5 + .../basicswap/extended/test_xmr_persistent.py | 2 +- 13 files changed, 587 insertions(+), 135 deletions(-) create mode 100644 basicswap/db_upgrades.py create mode 100644 basicswap/templates/automation_strategies.html create mode 100644 basicswap/templates/automation_strategy.html create mode 100644 basicswap/templates/automation_strategy_new.html create mode 100644 basicswap/ui/page_automation.py diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index ed0ede8..88e24bc 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -94,7 +94,10 @@ from .db import ( XmrSplitData, Wallets, KnownIdentity, + AutomationLink, + AutomationStrategy, ) +from .db_upgrades import upgradeDatabase, upgradeDatabaseData from .base import BaseApp from .explorers import ( ExplorerInsight, @@ -130,6 +133,7 @@ from .protocols.xmr_swap_1 import ( addLockRefundSigs, recoverNoScriptTxnWithKey) + non_script_type_coins = (Coins.XMR, Coins.PART_ANON) @@ -266,6 +270,10 @@ class BasicSwap(BaseApp): value=self.db_version )) session.commit() + try: + self.db_data_version = session.query(DBKVInt).filter_by(key='db_data_version').first().value + except Exception: + self.db_data_version = 0 try: self._contract_count = session.query(DBKVInt).filter_by(key='contract_count').first().value except Exception: @@ -518,7 +526,8 @@ class BasicSwap(BaseApp): self.log.info('sqlalchemy version %s', sa.__version__) self.log.info('timezone offset: %d (%s)', time.timezone, time.tzname[0]) - self.upgradeDatabase(self.db_version) + upgradeDatabase(self, self.db_version) + upgradeDatabaseData(self, self.db_data_version) for c in Coins: if c not in chainparams: @@ -599,93 +608,6 @@ class BasicSwap(BaseApp): if self.coin_clients[c]['connection_type'] == 'rpc' and chain_client_settings['manage_daemon'] is True: self.stopDaemon(c) - def upgradeDatabase(self, db_version): - if db_version >= CURRENT_DB_VERSION: - return - - self.log.info('Upgrading database from version %d to %d.', db_version, CURRENT_DB_VERSION) - - while True: - session = scoped_session(self.session_factory) - - current_version = db_version - if current_version == 6: - session.execute('ALTER TABLE bids ADD COLUMN security_token BLOB') - session.execute('ALTER TABLE offers ADD COLUMN security_token BLOB') - db_version += 1 - elif current_version == 7: - session.execute('ALTER TABLE transactions ADD COLUMN block_hash BLOB') - session.execute('ALTER TABLE transactions ADD COLUMN block_height INTEGER') - session.execute('ALTER TABLE transactions ADD COLUMN block_time INTEGER') - db_version += 1 - elif current_version == 8: - session.execute(''' - CREATE TABLE wallets ( - record_id INTEGER NOT NULL, - coin_id INTEGER, - wallet_name VARCHAR, - balance_type INTEGER, - amount BIGINT, - updated_at BIGINT, - 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 - elif current_version == 10: - session.execute('ALTER TABLE smsgaddresses ADD COLUMN active_ind INTEGER') - session.execute('ALTER TABLE smsgaddresses ADD COLUMN created_at INTEGER') - session.execute('ALTER TABLE smsgaddresses ADD COLUMN note VARCHAR') - session.execute('ALTER TABLE smsgaddresses ADD COLUMN pubkey VARCHAR') - session.execute('UPDATE smsgaddresses SET active_ind = 1, created_at = 1') - - session.execute('ALTER TABLE offers ADD COLUMN addr_to VARCHAR') - session.execute(f'UPDATE offers SET addr_to = "{self.network_addr}"') - db_version += 1 - elif current_version == 11: - session.execute('ALTER TABLE bids ADD COLUMN chain_a_height_start INTEGER') - session.execute('ALTER TABLE bids ADD COLUMN chain_b_height_start INTEGER') - session.execute('ALTER TABLE bids ADD COLUMN protocol_version INTEGER') - session.execute('ALTER TABLE offers ADD COLUMN protocol_version INTEGER') - session.execute('ALTER TABLE transactions ADD COLUMN tx_data BLOB') - db_version += 1 - elif current_version == 12: - session.execute(''' - CREATE TABLE knownidentities ( - record_id INTEGER NOT NULL, - address VARCHAR, - label VARCHAR, - publickey BLOB, - num_sent_bids_successful INTEGER, - num_recv_bids_successful INTEGER, - num_sent_bids_rejected INTEGER, - num_recv_bids_rejected INTEGER, - num_sent_bids_failed INTEGER, - num_recv_bids_failed INTEGER, - note VARCHAR, - updated_at BIGINT, - created_at BIGINT, - PRIMARY KEY (record_id))''') - session.execute('ALTER TABLE bids ADD COLUMN reject_code INTEGER') - session.execute('ALTER TABLE bids ADD COLUMN rate INTEGER') - session.execute('ALTER TABLE offers ADD COLUMN amount_negotiable INTEGER') - session.execute('ALTER TABLE offers ADD COLUMN rate_negotiable INTEGER') - db_version += 1 - - if current_version != db_version: - self.db_version = db_version - self.setIntKVInSession('db_version', db_version, session) - session.commit() - session.close() - session.remove() - self.log.info('Upgraded database to version {}'.format(self.db_version)) - continue - break - - if db_version != CURRENT_DB_VERSION: - raise ValueError('Unable to upgrade database.') - def waitForDaemonRPC(self, coin_type): for i in range(self.startup_tries): if not self.is_running: @@ -1154,7 +1076,6 @@ class BasicSwap(BaseApp): created_at=offer_created_at, expire_at=offer_created_at + msg_buf.time_valid, was_sent=True, - auto_accept_bids=auto_accept_bids, security_token=security_token) offer.setState(OfferStates.OFFER_SENT) @@ -1162,6 +1083,18 @@ class BasicSwap(BaseApp): xmr_offer.offer_id = offer_id session.add(xmr_offer) + if auto_accept_bids: + # Use default strategy + auto_link = AutomationLink( + active_ind=1, + linked_type=TableTypes.OFFER, + linked_id=offer_id, + strategy_id=1, + created_at=offer_created_at, + repeat_limit=1, + repeat_count=0) + session.add(auto_link) + session.add(offer) session.add(SentOffer(offer_id=offer_id)) session.commit() @@ -3833,6 +3766,54 @@ class BasicSwap(BaseApp): session.remove() self.mxDB.release() + def shouldAutoAcceptBid(self, offer, bid, session=None): + use_session = None + try: + if session: + use_session = session + else: + self.mxDB.acquire() + use_session = scoped_session(self.session_factory) + + link = session.query(AutomationLink).filter_by(active_ind=1, linked_type=TableTypes.OFFER, linked_id=offer.offer_id).first() + + if not link: + return False + + strategy = session.query(AutomationStrategy).filter_by(active_ind=1, record_id=link.strategy_id).first() + opts = json.loads(strategy.data.decode('utf-8')) + + self.log.debug('Evaluating against strategy {}'.format(strategy.record_id)) + + if opts.get('full_amount_only', False) is True: + if bid.amount != offer.amount_from: + self.log.info('Not auto accepting bid %s, want exact amount match', bid.bid_id.hex()) + return False + + max_bids = opts.get('max_bids', 1) + # Auto accept bid if set and no other non-abandoned bid for this order exists + if self.countAcceptedBids(offer.offer_id) >= max_bids: + self.log.info('Not auto accepting bid %s, already have', bid.bid_id.hex()) + return False + + if strategy.only_known_identities: + identity_stats = session.query(KnownIdentity).filter_by(address=bid.bid_addr).first() + if not identity_stats: + return False + + # TODO: More options + if identity_stats.num_recv_bids_successful < 1: + return False + if identity_stats.num_recv_bids_successful <= identity_stats.num_recv_bids_failed: + return False + + return True + finally: + if session is None: + use_session.close() + use_session.remove() + self.mxDB.release() + def processBid(self, msg): self.log.debug('Processing bid msg %s', msg['msgid']) now = int(time.time()) @@ -3920,16 +3901,10 @@ class BasicSwap(BaseApp): self.log.info('Received valid bid %s for offer %s', bid_id.hex(), bid_data.offer_msg_id.hex()) self.saveBid(bid_id, bid) - # Auto accept bid if set and no other non-abandoned bid for this order exists - if offer.auto_accept_bids: - if self.countAcceptedBids(offer_id) > 0: - self.log.info('Not auto accepting bid %s, already have', bid_id.hex()) - elif bid_data.amount != offer.amount_from: - self.log.info('Not auto accepting bid %s, want exact amount match', bid_id.hex()) - else: - delay = random.randrange(self.min_delay_event, self.max_delay_event) - self.log.info('Auto accepting bid %s in %d seconds', bid_id.hex(), delay) - self.createEvent(delay, EventTypes.ACCEPT_BID, bid_id) + if self.shouldAutoAcceptBid(offer, bid): + delay = random.randrange(self.min_delay_event, self.max_delay_event) + self.log.info('Auto accepting bid %s in %d seconds', bid_id.hex(), delay) + self.createEvent(delay, EventTypes.ACCEPT_BID, bid_id) def processBidAccept(self, msg): self.log.debug('Processing bid accepted msg %s', msg['msgid']) @@ -4046,17 +4021,11 @@ class BasicSwap(BaseApp): bid.setState(BidStates.BID_RECEIVED) - # Auto accept bid if set and no other non-abandoned bid for this order exists - if offer.auto_accept_bids: - if self.countAcceptedBids(bid.offer_id) > 0: - self.log.info('Not auto accepting bid %s, already have', bid.bid_id.hex()) - elif bid.amount != offer.amount_from: - self.log.info('Not auto accepting bid %s, want exact amount match', bid.bid_id.hex()) - else: - delay = random.randrange(self.min_delay_event, self.max_delay_event) - self.log.info('Auto accepting xmr bid %s in %d seconds', bid.bid_id.hex(), delay) - self.createEventInSession(delay, EventTypes.ACCEPT_XMR_BID, bid.bid_id, session) - bid.setState(BidStates.SWAP_DELAYING) + if self.shouldAutoAcceptBid(offer, bid, session): + delay = random.randrange(self.min_delay_event, self.max_delay_event) + self.log.info('Auto accepting xmr bid %s in %d seconds', bid.bid_id.hex(), delay) + self.createEventInSession(delay, EventTypes.ACCEPT_XMR_BID, bid.bid_id, session) + bid.setState(BidStates.SWAP_DELAYING) self.saveBidInSession(bid.bid_id, bid, session, xmr_swap) @@ -5492,6 +5461,46 @@ class BasicSwap(BaseApp): session.remove() self.mxDB.release() + def listAutomationStrategies(self, filters={}): + self.mxDB.acquire() + try: + rv = [] + session = scoped_session(self.session_factory) + + query_str = 'SELECT strats.record_id, strats.label, strats.type_ind FROM automationstrategies AS strats' + query_str += ' WHERE strats.active_ind = 1 ' + + sort_dir = filters.get('sort_dir', 'DESC').upper() + sort_by = filters.get('sort_by', 'created_at') + query_str += f' ORDER BY strats.{sort_by} {sort_dir}' + + limit = filters.get('limit', None) + if limit is not None: + query_str += f' LIMIT {limit}' + offset = filters.get('offset', None) + if offset is not None: + query_str += f' OFFSET {offset}' + + q = session.execute(query_str) + for row in q: + rv.append(row) + return rv + finally: + session.close() + session.remove() + self.mxDB.release() + + def getAutomationStrategy(self, strategy_id): + self.mxDB.acquire() + try: + rv = [] + session = scoped_session(self.session_factory) + return session.query(AutomationStrategy).filter_by(record_id=strategy_id).first() + finally: + session.close() + session.remove() + self.mxDB.release() + def newSMSGAddress(self, use_type=AddressTypes.RECV_OFFER, addressnote=None, session=None): now = int(time.time()) use_session = None diff --git a/basicswap/db.py b/basicswap/db.py index c104ffb..14d946a 100644 --- a/basicswap/db.py +++ b/basicswap/db.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2019-2021 tecnovert +# Copyright (c) 2019-2022 tecnovert # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. @@ -12,7 +12,8 @@ from enum import IntEnum, auto from sqlalchemy.ext.declarative import declarative_base -CURRENT_DB_VERSION = 13 +CURRENT_DB_VERSION = 14 +CURRENT_DB_DATA_VERSION = 1 Base = declarative_base() @@ -21,6 +22,14 @@ class TableTypes(IntEnum): BID = auto() +def strTableTypes(state): + if state == TableTypes.OFFER: + return 'Offer' + if state == TableTypes.BID: + return 'Bid' + return 'Unknown' + + class DBKVInt(Base): __tablename__ = 'kv_int' @@ -372,6 +381,7 @@ class Wallets(Base): __tablename__ = 'wallets' record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) + active_ind = sa.Column(sa.Integer) coin_id = sa.Column(sa.Integer) wallet_name = sa.Column(sa.String) wallet_data = sa.Column(sa.String) @@ -385,6 +395,7 @@ class KnownIdentity(Base): __tablename__ = 'knownidentities' record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) + active_ind = sa.Column(sa.Integer) address = sa.Column(sa.String) label = sa.Column(sa.String) publickey = sa.Column(sa.LargeBinary) @@ -397,3 +408,51 @@ class KnownIdentity(Base): note = sa.Column(sa.String) updated_at = sa.Column(sa.BigInteger) created_at = sa.Column(sa.BigInteger) + + +class AutomationStrategy(Base): + __tablename__ = 'automationstrategies' + + record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) + active_ind = sa.Column(sa.Integer) + + label = sa.Column(sa.String) + type_ind = sa.Column(sa.Integer) + only_known_identities = sa.Column(sa.Integer) + num_concurrent = sa.Column(sa.Integer) + data = sa.Column(sa.LargeBinary) + + note = sa.Column(sa.String) + created_at = sa.Column(sa.BigInteger) + + +class AutomationLink(Base): + __tablename__ = 'automationlinks' + # Contains per order/bid options + + record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) + active_ind = sa.Column(sa.Integer) + + linked_type = sa.Column(sa.Integer) + linked_id = sa.Column(sa.LargeBinary) + strategy_id = sa.Column(sa.Integer) + + data = sa.Column(sa.LargeBinary) + repeat_limit = sa.Column(sa.Integer) + repeat_count = sa.Column(sa.Integer) + + note = sa.Column(sa.String) + created_at = sa.Column(sa.BigInteger) + + __table_args__ = (sa.Index('linked_index', 'linked_type', 'linked_id'), ) + + +class History(Base): + __tablename__ = 'history' + + record_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) + concept_type = sa.Column(sa.Integer) + concept_id = sa.Column(sa.Integer) + + changed_data = sa.Column(sa.LargeBinary) + created_at = sa.Column(sa.BigInteger) diff --git a/basicswap/db_upgrades.py b/basicswap/db_upgrades.py new file mode 100644 index 0000000..3909bde --- /dev/null +++ b/basicswap/db_upgrades.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2022 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +import json + +from sqlalchemy.orm import scoped_session +from .db import ( + TableTypes, + AutomationStrategy, + CURRENT_DB_VERSION, + CURRENT_DB_DATA_VERSION) + + +def upgradeDatabaseData(self, data_version): + if data_version >= CURRENT_DB_DATA_VERSION: + return + + self.log.info('Upgrading database records from version %d to %d.', data_version, CURRENT_DB_DATA_VERSION) + with self.mxDB: + try: + session = scoped_session(self.session_factory) + + if data_version < 1: + session.add(AutomationStrategy( + active_ind=1, + label='Accept All', + type_ind=TableTypes.BID, + data=json.dumps({'full_amount_only': True, + 'max_bids': 1}).encode('utf-8'), + only_known_identities=False)) + session.add(AutomationStrategy( + active_ind=1, + label='Accept Known', + type_ind=TableTypes.BID, + data=json.dumps({'full_amount_only': True, + 'max_bids': 1}).encode('utf-8'), + only_known_identities=True, + note='Accept bids from identities with previously successful swaps only')) + + self.db_data_version = CURRENT_DB_DATA_VERSION + self.setIntKVInSession('db_data_version', self.db_data_version, session) + session.commit() + self.log.info('Upgraded database records to version {}'.format(self.db_data_version)) + finally: + session.close() + session.remove() + + +def upgradeDatabase(self, db_version): + if db_version >= CURRENT_DB_VERSION: + return + + self.log.info('Upgrading database from version %d to %d.', db_version, CURRENT_DB_VERSION) + + while True: + session = scoped_session(self.session_factory) + + current_version = db_version + if current_version == 6: + session.execute('ALTER TABLE bids ADD COLUMN security_token BLOB') + session.execute('ALTER TABLE offers ADD COLUMN security_token BLOB') + db_version += 1 + elif current_version == 7: + session.execute('ALTER TABLE transactions ADD COLUMN block_hash BLOB') + session.execute('ALTER TABLE transactions ADD COLUMN block_height INTEGER') + session.execute('ALTER TABLE transactions ADD COLUMN block_time INTEGER') + db_version += 1 + elif current_version == 8: + session.execute(''' + CREATE TABLE wallets ( + record_id INTEGER NOT NULL, + coin_id INTEGER, + wallet_name VARCHAR, + balance_type INTEGER, + amount BIGINT, + updated_at BIGINT, + 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 + elif current_version == 10: + session.execute('ALTER TABLE smsgaddresses ADD COLUMN active_ind INTEGER') + session.execute('ALTER TABLE smsgaddresses ADD COLUMN created_at INTEGER') + session.execute('ALTER TABLE smsgaddresses ADD COLUMN note VARCHAR') + session.execute('ALTER TABLE smsgaddresses ADD COLUMN pubkey VARCHAR') + session.execute('UPDATE smsgaddresses SET active_ind = 1, created_at = 1') + + session.execute('ALTER TABLE offers ADD COLUMN addr_to VARCHAR') + session.execute(f'UPDATE offers SET addr_to = "{self.network_addr}"') + db_version += 1 + elif current_version == 11: + session.execute('ALTER TABLE bids ADD COLUMN chain_a_height_start INTEGER') + session.execute('ALTER TABLE bids ADD COLUMN chain_b_height_start INTEGER') + session.execute('ALTER TABLE bids ADD COLUMN protocol_version INTEGER') + session.execute('ALTER TABLE offers ADD COLUMN protocol_version INTEGER') + session.execute('ALTER TABLE transactions ADD COLUMN tx_data BLOB') + db_version += 1 + elif current_version == 12: + session.execute(''' + CREATE TABLE knownidentities ( + record_id INTEGER NOT NULL, + address VARCHAR, + label VARCHAR, + publickey BLOB, + num_sent_bids_successful INTEGER, + num_recv_bids_successful INTEGER, + num_sent_bids_rejected INTEGER, + num_recv_bids_rejected INTEGER, + num_sent_bids_failed INTEGER, + num_recv_bids_failed INTEGER, + note VARCHAR, + updated_at BIGINT, + created_at BIGINT, + PRIMARY KEY (record_id))''') + session.execute('ALTER TABLE bids ADD COLUMN reject_code INTEGER') + session.execute('ALTER TABLE bids ADD COLUMN rate INTEGER') + session.execute('ALTER TABLE offers ADD COLUMN amount_negotiable INTEGER') + session.execute('ALTER TABLE offers ADD COLUMN rate_negotiable INTEGER') + db_version += 1 + elif current_version == 13: + db_version += 1 + session.execute(''' + CREATE TABLE automationstrategies ( + record_id INTEGER NOT NULL, + active_ind INTEGER, + label VARCHAR, + type_ind INTEGER, + only_known_identities INTEGER, + num_concurrent INTEGER, + data BLOB, + + note VARCHAR, + created_at BIGINT, + PRIMARY KEY (record_id))''') + + session.execute(''' + CREATE TABLE automationlinks ( + record_id INTEGER NOT NULL, + active_ind INTEGER, + + linked_type INTEGER, + linked_id BLOB, + strategy_id INTEGER, + + data BLOB, + repeat_limit INTEGER, + repeat_count INTEGER, + + note VARCHAR, + created_at BIGINT, + PRIMARY KEY (record_id))''') + + session.execute(''' + CREATE TABLE history ( + record_id INTEGER NOT NULL, + concept_type INTEGER, + concept_id INTEGER, + changed_data BLOB, + + note VARCHAR, + created_at BIGINT, + PRIMARY KEY (record_id))''') + + session.execute('ALTER TABLE wallets ADD COLUMN active_ind INTEGER') + session.execute('ALTER TABLE knownidentities ADD COLUMN active_ind INTEGER') + + if current_version != db_version: + self.db_version = db_version + self.setIntKVInSession('db_version', db_version, session) + session.commit() + session.close() + session.remove() + self.log.info('Upgraded database to version {}'.format(self.db_version)) + continue + break + + if db_version != CURRENT_DB_VERSION: + raise ValueError('Unable to upgrade database.') diff --git a/basicswap/http_server.py b/basicswap/http_server.py index 9ddcbf7..b8e129a 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -59,9 +59,15 @@ from .ui.util import ( have_data_entry, get_data_entry_or, listAvailableCoins, + set_pagination_filters, ) from .ui.page_tor import page_tor from .ui.page_offers import page_offers +from .ui.page_automation import ( + page_automation_strategies, + page_automation_strategy, + page_automation_strategy_new +) env = Environment(loader=PackageLoader('basicswap', 'templates')) @@ -1155,15 +1161,7 @@ class HttpHandler(BaseHTTPRequestHandler): ensure(sort_dir in ['asc', 'desc'], 'Invalid sort dir') filters['sort_dir'] = sort_dir - if form_data and have_data_entry(form_data, 'pageback'): - filters['page_no'] = int(form_data[b'pageno'][0]) - 1 - if filters['page_no'] < 1: - filters['page_no'] = 1 - elif form_data and have_data_entry(form_data, 'pageforwards'): - filters['page_no'] = int(form_data[b'pageno'][0]) + 1 - - if filters['page_no'] > 1: - filters['offset'] = (filters['page_no'] - 1) * PAGE_LIMIT + set_pagination_filters(form_data, filters) bids = swap_client.listBids(sent=sent, filters=filters) @@ -1437,6 +1435,12 @@ class HttpHandler(BaseHTTPRequestHandler): return self.page_identity(url_split, post_string) if url_split[1] == 'tor': return page_tor(self, url_split, post_string) + if url_split[1] == 'automation': + return page_automation_strategies(self, url_split, post_string) + if url_split[1] == 'automationstrategy': + return page_automation_strategy(self, url_split, post_string) + if url_split[1] == 'newautomationstrategy': + return page_automation_strategy_new(self, url_split, post_string) if url_split[1] == 'shutdown': return self.page_shutdown(url_split, post_string) return self.page_index(url_split) diff --git a/basicswap/templates/automation_strategies.html b/basicswap/templates/automation_strategies.html new file mode 100644 index 0000000..3412c17 --- /dev/null +++ b/basicswap/templates/automation_strategies.html @@ -0,0 +1,38 @@ +{% include 'header.html' %} + +

Automation Strategies

+{% for m in messages %} +

{{ m }}

+{% endfor %} + +
+ + + + + + +
Sort By + + +
Page: {{ filters.page_no }}
+ + +
+ +

Create New Strategy

+ + + +{% for s in strategies %} + +{% endfor %} +
NameType
{{ s[1] }}{{ s[2] }}
+ +

home

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

Automation Strategy {{ strategy_id }}

+ +{% for m in messages %} +

{{ m }}

+{% endfor %} + + + + + + + +
Label{{ strategy.label }}
Type{{ strategy.type }}
Only known identities{{ strategy.only_known_identities }}
Data + +
Notes + +
+ + +

home

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

New Automation Strategy

+{% for m in messages %} +

{{ m }}

+{% endfor %} + +

TODO

+ +

home

+ diff --git a/basicswap/templates/index.html b/basicswap/templates/index.html index 722ccab..74db5c1 100644 --- a/basicswap/templates/index.html +++ b/basicswap/templates/index.html @@ -19,6 +19,7 @@ Version: {{ version }} Received Bids: {{ summary.num_recv_bids }}
Sent Bids: {{ summary.num_sent_bids }}
Watched Outputs: {{ summary.num_watched_outputs }}
+Automation Strategies
{% if use_tor_proxy %} TOR Information
{% endif %}

diff --git a/basicswap/ui/page_automation.py b/basicswap/ui/page_automation.py new file mode 100644 index 0000000..5a0bb94 --- /dev/null +++ b/basicswap/ui/page_automation.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2022 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +import os + +from .util import ( + PAGE_LIMIT, + get_data_entry, + have_data_entry, + set_pagination_filters, +) +from basicswap.util import ( + ensure, +) +from basicswap.db import ( + strTableTypes, +) + + +def page_automation_strategies(self, url_split, post_string): + server = self.server + swap_client = server.swap_client + + filters = { + 'page_no': 1, + 'limit': PAGE_LIMIT, + 'sort_by': 'created_at', + 'sort_dir': 'desc', + } + + messages = [] + form_data = self.checkForm(post_string, 'automationstrategies', messages) + + if form_data and have_data_entry(form_data, 'applyfilters'): + if have_data_entry(form_data, 'sort_by'): + sort_by = get_data_entry(form_data, 'sort_by') + ensure(sort_by in ['created_at', 'rate'], 'Invalid sort by') + filters['sort_by'] = sort_by + if have_data_entry(form_data, 'sort_dir'): + sort_dir = get_data_entry(form_data, 'sort_dir') + ensure(sort_dir in ['asc', 'desc'], 'Invalid sort dir') + filters['sort_dir'] = sort_dir + + set_pagination_filters(form_data, filters) + + formatted_strategies = [] + for s in swap_client.listAutomationStrategies(filters): + formatted_strategies.append((s[0], s[1], strTableTypes(s[2]))) + + template = server.env.get_template('automation_strategies.html') + return bytes(template.render( + title=server.title, + h2=server.title, + messages=messages, + filters=filters, + strategies=formatted_strategies, + form_id=os.urandom(8).hex(), + ), 'UTF-8') + + +def page_automation_strategy_new(self, url_split, post_string): + server = self.server + swap_client = self.server.swap_client + + messages = [] + form_data = self.checkForm(post_string, 'automationstrategynew', messages) + + template = server.env.get_template('automation_strategy_new.html') + return bytes(template.render( + title=server.title, + h2=server.title, + messages=messages, + form_id=os.urandom(8).hex(), + ), 'UTF-8') + + +def page_automation_strategy(self, url_split, post_string): + ensure(len(url_split) > 2, 'Strategy ID not specified') + try: + strategy_id = int(url_split[2]) + except Exception: + raise ValueError('Bad strategy ID') + + server = self.server + swap_client = self.server.swap_client + + messages = [] + + strategy = swap_client.getAutomationStrategy(strategy_id) + + formatted_strategy = { + 'label': strategy.label, + 'type': strTableTypes(strategy.type_ind), + 'only_known_identities': 'True' if strategy.only_known_identities is True else 'False', + 'data': strategy.data, + 'note': strategy.note, + 'created_at': strategy.created_at, + } + + template = server.env.get_template('automation_strategy.html') + return bytes(template.render( + title=server.title, + h2=server.title, + messages=messages, + strategy=formatted_strategy, + form_id=os.urandom(8).hex(), + ), 'UTF-8') diff --git a/basicswap/ui/page_offers.py b/basicswap/ui/page_offers.py index cafdf97..552f9c5 100644 --- a/basicswap/ui/page_offers.py +++ b/basicswap/ui/page_offers.py @@ -12,6 +12,7 @@ from .util import ( get_data_entry, have_data_entry, listAvailableCoins, + set_pagination_filters, ) from basicswap.util import ( ensure, @@ -23,7 +24,8 @@ from basicswap.chainparams import ( def page_offers(self, url_split, post_string, sent=False): - swap_client = self.server.swap_client + server = self.server + swap_client = server.swap_client filters = { 'coin_from': -1, @@ -53,15 +55,7 @@ def page_offers(self, url_split, post_string, sent=False): ensure(sent_from in ['any', 'only'], 'Invalid sent filter') filters['sent_from'] = sent_from - if form_data and have_data_entry(form_data, 'pageback'): - filters['page_no'] = int(form_data[b'pageno'][0]) - 1 - if filters['page_no'] < 1: - filters['page_no'] = 1 - elif form_data and have_data_entry(form_data, 'pageforwards'): - filters['page_no'] = int(form_data[b'pageno'][0]) + 1 - - if filters['page_no'] > 1: - filters['offset'] = (filters['page_no'] - 1) * PAGE_LIMIT + set_pagination_filters(form_data, filters) if filters['sent_from'] == 'only': sent = True @@ -84,10 +78,10 @@ def page_offers(self, url_split, post_string, sent=False): o.addr_from, o.was_sent)) - template = self.server.env.get_template('offers.html') + template = server.env.get_template('offers.html') return bytes(template.render( - title=self.server.title, - h2=self.server.title, + title=server.title, + h2=server.title, coins=listAvailableCoins(swap_client), messages=messages, filters=filters, diff --git a/basicswap/ui/util.py b/basicswap/ui/util.py index 55deeb7..6e10591 100644 --- a/basicswap/ui/util.py +++ b/basicswap/ui/util.py @@ -96,6 +96,17 @@ def setCoinFilter(form_data, field_name): raise ValueError('Unknown Coin Type {}'.format(str(field_name))) +def set_pagination_filters(form_data, filters): + if form_data and have_data_entry(form_data, 'pageback'): + filters['page_no'] = int(form_data[b'pageno'][0]) - 1 + if filters['page_no'] < 1: + filters['page_no'] = 1 + elif form_data and have_data_entry(form_data, 'pageforwards'): + filters['page_no'] = int(form_data[b'pageno'][0]) + 1 + if filters['page_no'] > 1: + filters['offset'] = (filters['page_no'] - 1) * PAGE_LIMIT + + def getTxIdHex(bid, tx_type, suffix): if tx_type == TxTypes.ITX: obj = bid.initiate_tx diff --git a/doc/install.md b/doc/install.md index 8682479..9cd05f1 100644 --- a/doc/install.md +++ b/doc/install.md @@ -13,6 +13,11 @@ Docker must be installed and started: Should return a line containing `Docker version`... +It's recommended to setup docker to work without sudo: + + https://docs.docker.com/engine/install/linux-postinstall/ + + #### Create the images: $ cd basicswap/docker diff --git a/tests/basicswap/extended/test_xmr_persistent.py b/tests/basicswap/extended/test_xmr_persistent.py index 3889e86..0461304 100644 --- a/tests/basicswap/extended/test_xmr_persistent.py +++ b/tests/basicswap/extended/test_xmr_persistent.py @@ -9,7 +9,7 @@ export RESET_TEST=true export TEST_PATH=/tmp/test_persistent mkdir -p ${TEST_PATH}/bin/{particl,monero,bitcoin} -cp ~/tmp/particl-0.21.2.8-x86_64-linux-gnu.tar.gz ${TEST_PATH}/bin/particl +cp ~/tmp/particl-0.21.2.9-x86_64-linux-gnu.tar.gz ${TEST_PATH}/bin/particl cp ~/tmp/bitcoin-0.21.1-x86_64-linux-gnu.tar.gz ${TEST_PATH}/bin/bitcoin cp ~/tmp/monero-linux-x64-v0.17.3.0.tar.bz2 ${TEST_PATH}/bin/monero/monero-0.17.3.0-x86_64-linux-gnu.tar.bz2 export PYTHONPATH=$(pwd)