From d155774dbc47dee84b3253a098a8e3fb2d87983e Mon Sep 17 00:00:00 2001 From: tecnovert Date: Tue, 23 Jul 2019 19:19:31 +0200 Subject: [PATCH] Added an rudimentary address pool. --- basicswap/basicswap.py | 82 +++++++++++++++++++++++++++++++++++++--- basicswap/http_server.py | 42 +++++++++++++++++--- bin/basicswap_prepare.py | 2 +- 3 files changed, 114 insertions(+), 12 deletions(-) diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index d01eba9..9e1bae6 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -116,6 +116,13 @@ class OpCodes(IntEnum): OP_CHECKSEQUENCEVERIFY = 0xb2, +class TxTypes(IntEnum): # For PooledAddress + ITX_REDEEM = auto() + ITX_REFUND = auto() + PTX_REDEEM = auto() + PTX_REFUND = auto() + + SEQUENCE_LOCK_BLOCKS = 1 SEQUENCE_LOCK_TIME = 2 SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds @@ -405,6 +412,8 @@ class PooledAddress(Base): addr_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) coin_type = sa.Column(sa.Integer) addr = sa.Column(sa.String) + bid_id = sa.Column(sa.LargeBinary) + tx_type = sa.Column(sa.Integer) """ @@ -816,6 +825,46 @@ class BasicSwap(): return hashlib.sha256(bytes(self.callcoinrpc(Coins.PART, 'extkey', ['info', evkey, path])['key_info']['result'], 'utf-8')).digest() + def getReceiveAddressFromPool(self, coin_type, bid_id, tx_type): + self.log.debug('Get address from pool bid_id {}, type {}, coin {}'.format(bid_id.hex(), tx_type, coin_type)) + self.mxDB.acquire() + try: + session = scoped_session(self.session_factory) + try: + record = session.query(PooledAddress).filter(PooledAddress.coin_type == int(coin_type) and PooledAddress.bid_id == None).one() + except Exception: + address = self.getReceiveAddressForCoin(coin_type) + record = PooledAddress( + addr=address, + coin_type=int(coin_type)) + record.bid_id = bid_id + record.tx_type = tx_type + addr = record.addr + session.add(record) + session.commit() + session.close() + session.remove() + finally: + self.mxDB.release() + return addr + + def returnAddressToPool(self, bid_id, tx_type): + self.log.debug('Return address to pool bid_id {}, type {}'.format(bid_id.hex(), tx_type)) + self.mxDB.acquire() + try: + session = scoped_session(self.session_factory) + try: + record = session.query(PooledAddress).filter(PooledAddress.bid_id == bid_id and PooledAddress.tx_type == tx_type).one() + self.log.debug('Returning address to pool addr {}'.format(record.addr)) + record.bid_id = None + session.commit() + except Exception: + pass + session.close() + session.remove() + finally: + self.mxDB.release() + def getReceiveAddressForCoin(self, coin_type): if coin_type == Coins.PART: new_addr = self.callcoinrpc(Coins.PART, 'getnewaddress') @@ -1157,6 +1206,16 @@ class BasicSwap(): # Remove any watched outputs self.removeWatchedOutput(Coins(offer.coin_from), bid_id, None) self.removeWatchedOutput(Coins(offer.coin_to), bid_id, None) + + # Return unused addrs to pool + if bid.initiate_txn_state != TxStates.TX_REDEEMED: + self.returnAddressToPool(bid_id, TxTypes.ITX_REDEEM) + if bid.initiate_txn_state != TxStates.TX_REFUNDED: + self.returnAddressToPool(bid_id, TxTypes.ITX_REFUND) + if bid.participate_txn_state != TxStates.TX_REDEEMED: + self.returnAddressToPool(bid_id, TxTypes.PTX_REDEEM) + if bid.participate_txn_state != TxStates.TX_REFUNDED: + self.returnAddressToPool(bid_id, TxTypes.PTX_REFUND) finally: session.close() session.remove() @@ -1228,7 +1287,7 @@ class BasicSwap(): txn_signed = self.callcoinrpc(coin_to, 'signrawtransactionwithwallet', [txn_funded])['hex'] - refund_txn = self.createRefundTxn(coin_to, txn_signed, bid, bid.participate_script) + refund_txn = self.createRefundTxn(coin_to, txn_signed, bid, bid.participate_script, tx_type=TxTypes.PTX_REFUND) bid.participate_txn_refund = bytes.fromhex(refund_txn) chain_height = self.callcoinrpc(coin_to, 'getblockchaininfo')['blocks'] @@ -1307,7 +1366,7 @@ class BasicSwap(): assert(amount_out > 0), 'Amount out <= 0' if addr_redeem_out is None: - addr_redeem_out = self.getReceiveAddressForCoin(coin_type) + addr_redeem_out = self.getReceiveAddressFromPool(coin_type, bid.bid_id, TxTypes.PTX_REDEEM if for_txn_type == 'participate' else TxTypes.ITX_REDEEM) assert(addr_redeem_out is not None) if self.coin_clients[coin_type]['use_segwit']: @@ -1359,7 +1418,7 @@ class BasicSwap(): return redeem_txn - def createRefundTxn(self, coin_type, txn, bid, txn_script, addr_refund_out=None, fee_rate=None): + def createRefundTxn(self, coin_type, txn, bid, txn_script, addr_refund_out=None, fee_rate=None, tx_type=TxTypes.ITX_REFUND): self.log.debug('createRefundTxn') if self.coin_clients[coin_type]['connection_type'] != 'rpc': return None @@ -1400,7 +1459,7 @@ class BasicSwap(): assert(amount_out > 0), 'Amount out <= 0' if addr_refund_out is None: - addr_refund_out = self.getReceiveAddressForCoin(coin_type) + addr_refund_out = self.getReceiveAddressFromPool(coin_type, bid.bid_id, tx_type) assert(addr_refund_out is not None), 'addr_refund_out is null' if self.coin_clients[coin_type]['use_segwit']: # Change to btc hrp @@ -1659,6 +1718,16 @@ class BasicSwap(): if (bid.initiate_txn_state is None or bid.initiate_txn_state >= TxStates.TX_REDEEMED) \ and (bid.participate_txn_state is None or bid.participate_txn_state >= TxStates.TX_REDEEMED): self.log.info('Swap completed for bid %s', bid_id.hex()) + + if bid.initiate_txn_state == TxStates.TX_REDEEMED: + self.returnAddressToPool(bid_id, TxTypes.ITX_REFUND) + else: + self.returnAddressToPool(bid_id, TxTypes.ITX_REDEEM) + if bid.participate_txn_state == TxStates.TX_REDEEMED: + self.returnAddressToPool(bid_id, TxTypes.PTX_REFUND) + else: + self.returnAddressToPool(bid_id, TxTypes.PTX_REDEEM) + bid.setState(BidStates.SWAP_COMPLETED) self.saveBid(bid_id, bid) return True # Mark bid for archiving @@ -2218,9 +2287,10 @@ class BasicSwap(): if offer_id is not None: q = session.query(Bid).filter(Bid.offer_id == offer_id) elif sent: - q = session.query(Bid).filter(Bid.was_sent == True).order_by(Bid.created_at.desc()) # noqa E712 + q = session.query(Bid).filter(Bid.was_sent == True) else: - q = session.query(Bid).filter(Bid.was_received == True).order_by(Bid.created_at.desc()) # noqa E712 + q = session.query(Bid).filter(Bid.was_received == True) + q = q.order_by(Bid.created_at.desc()) # noqa E712 for row in q: rv.append(row) return rv diff --git a/basicswap/http_server.py b/basicswap/http_server.py index 022a74d..a849bd8 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -23,6 +23,8 @@ from .chainparams import ( ) from .basicswap import ( SwapTypes, + BidStates, + TxStates, getOfferState, getBidState, getTxState, @@ -34,10 +36,11 @@ def getCoinName(c): return chainparams[c]['name'].capitalize() -def html_content_start(title, h2=None): +def html_content_start(title, h2=None, refresh=None): content = '\n' \ + '' \ - + '' + title + '' \ + + ('' if not refresh else ''.format(refresh)) \ + + '' + title + '\n' \ + '' if h2 is not None: content += '

' + h2 + '

' @@ -294,8 +297,9 @@ class HttpHandler(BaseHTTPRequestHandler): raise ValueError('Bad bid ID') swap_client = self.server.swap_client - content = html_content_start(self.server.title, self.server.title) \ - + '

Bid: ' + bid_id.hex() + '

' + content = html_content_start(self.server.title, self.server.title, 30) \ + + '

Bid: ' + bid_id.hex() + '

' \ + + '

Page Refresh: 30 seconds

' show_txns = False if post_string != '': @@ -328,11 +332,38 @@ class HttpHandler(BaseHTTPRequestHandler): ticker_from = swap_client.getTicker(coin_from) ticker_to = swap_client.getTicker(coin_to) + if bid.state == BidStates.BID_SENT: + state_description = 'Waiting for seller to accept.' + elif bid.state == BidStates.BID_RECEIVED: + state_description = 'Waiting for seller to accept.' + elif bid.state == BidStates.BID_ACCEPTED: + if not bid.initiate_txid: + state_description = 'Waiting for seller to send initiate tx.' + else: + state_description = 'Waiting for initiate tx to confirm.' + elif bid.state == BidStates.SWAP_INITIATED: + state_description = 'Waiting for participate txn to be confirmed in {} chain'.format(ticker_to) + elif bid.state == BidStates.SWAP_PARTICIPATING: + state_description = 'Waiting for initiate txn to be spent in {} chain'.format(ticker_from) + elif bid.state == BidStates.SWAP_COMPLETED: + state_description = 'Swap completed' + if bid.initiate_txn_state == TxStates.TX_REDEEMED and bid.participate_txn_state == TxStates.TX_REDEEMED: + state_description += ' successfully' + else: + state_description += ', ITX ' + getTxState(bid.initiate_txn_state + ', PTX ' + getTxState(bid.participate_txn_state)) + elif bid.state == BidStates.SWAP_TIMEDOUT: + state_description = 'Timed out waiting for initiate txn' + elif bid.state == BidStates.BID_ABANDONED: + state_description = 'Bid abandoned' + else: + state_description = '' + tr = '{}{}' content += '' content += tr.format('Swap', format8(bid.amount) + ' ' + ticker_from + ' for ' + format8((bid.amount * offer.rate) // COIN) + ' ' + ticker_to) content += tr.format('Bid State', getBidState(bid.state)) + content += tr.format('State Description', state_description) content += tr.format('ITX State', getTxState(bid.initiate_txn_state)) content += tr.format('PTX State', getTxState(bid.participate_txn_state)) content += tr.format('Offer', '' + bid.offer_id.hex() + '') @@ -419,9 +450,10 @@ class HttpHandler(BaseHTTPRequestHandler): swap_client = self.server.swap_client summary = swap_client.getSummary() - content = html_content_start(self.server.title, self.server.title) \ + content = html_content_start(self.server.title, self.server.title, 30) \ + '

View Wallets

' \ + '

' \ + + 'Page Refresh: 30 seconds
' \ + 'Network: ' + str(summary['network']) + '
' \ + 'Swaps in progress: ' + str(summary['num_swapping']) + '
' \ + 'Network Offers: ' + str(summary['num_network_offers']) + '
' \ diff --git a/bin/basicswap_prepare.py b/bin/basicswap_prepare.py index 25cdae8..3a7913c 100644 --- a/bin/basicswap_prepare.py +++ b/bin/basicswap_prepare.py @@ -264,7 +264,7 @@ def main(): sys.stderr.write('Error: {} exists, exiting.\n'.format(config_path)) exit(1) - port_offset = 300 if chain == testnet else 0 + port_offset = 300 if chain == 'testnet' else 0 settings = { 'debug': True, 'zmqhost': 'tcp://127.0.0.1',