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
' \ + '' \ - + '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 = '