diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 9d9b3ef..6cdebcf 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -51,6 +51,7 @@ from .db import ( SwapTx, PooledAddress, SentOffer, + SmsgAddress, ) import basicswap.config as cfg import basicswap.segwit_addr as segwit_addr @@ -467,6 +468,8 @@ class BasicSwap(): self.log.info('Upgrading Database from version %d to %d.', db_version, CURRENT_DB_VERSION) + raise ValueError('Scripted database upgrade not found.') + def waitForDaemonRPC(self, coin_type): for i in range(21): if not self.is_running: @@ -583,8 +586,7 @@ class BasicSwap(): raise ValueError('Unknown locktype') def postOffer(self, coin_from, coin_to, amount, rate, min_bid_amount, swap_type, - lock_type=SEQUENCE_LOCK_TIME, lock_value=48 * 60 * 60, auto_accept_bids=False): - + lock_type=SEQUENCE_LOCK_TIME, lock_value=48 * 60 * 60, auto_accept_bids=False, addr_send_from=None): # Offer to send offer.amount_from of coin_from in exchange for offer.amount_from * offer.rate of coin_to assert(coin_from != coin_to), 'coin_from == coin_to' @@ -617,8 +619,10 @@ class BasicSwap(): offer_bytes = msg_buf.SerializeToString() payload_hex = str.format('{:02x}', MessageTypes.OFFER) + offer_bytes.hex() - # TODO: reuse address? - offer_addr = self.callrpc('getnewaddress') + if addr_send_from is None: + offer_addr = self.callrpc('getnewaddress') + else: + offer_addr = addr_send_from self.callrpc('smsgaddlocaladdress', [offer_addr]) # Enable receiving smsg ro = self.callrpc('smsgsend', [offer_addr, self.network_addr, payload_hex, False, 1, False, False, True]) msg_id = ro['msgid'] @@ -626,7 +630,6 @@ class BasicSwap(): offer_id = bytes.fromhex(msg_id) session = scoped_session(self.session_factory) - offer = Offer( offer_id=offer_id, @@ -646,9 +649,11 @@ class BasicSwap(): was_sent=True, auto_accept_bids=auto_accept_bids,) offer.setState(OfferStates.OFFER_SENT) - session.add(offer) + session.add(offer) session.add(SentOffer(offer_id=offer_id)) + if addr_send_from is None: + session.add(SmsgAddress(addr=offer_addr, use_type=MessageTypes.OFFER)) session.commit() session.close() session.remove() @@ -874,79 +879,91 @@ class BasicSwap(): return (sign_for_addr, signature) + def saveBidInSession(self, bid_id, bid, session): + session.add(bid) + if bid.initiate_tx: + session.add(bid.initiate_tx) + if bid.participate_tx: + session.add(bid.participate_tx) + def saveBid(self, bid_id, bid): self.mxDB.acquire() try: session = scoped_session(self.session_factory) - session.add(bid) - if bid.initiate_tx: - session.add(bid.initiate_tx) - if bid.participate_tx: - session.add(bid.participate_tx) + self.saveBidInSession(bid_id, bid, session) session.commit() session.close() session.remove() finally: self.mxDB.release() - def postBid(self, offer_id, amount): - self.log.debug('postBid %s %s', offer_id.hex(), format8(amount)) - + def postBid(self, offer_id, amount, addr_send_from=None): # Bid to send bid.amount * offer.rate of coin_to in exchange for bid.amount of coin_from + self.log.debug('postBid %s %s', offer_id.hex(), format8(amount)) + self.mxDB.acquire() + try: + offer = self.getOffer(offer_id) + assert(offer), 'Offer not found: {}.'.format(offer_id.hex()) + assert(offer.expire_at > int(time.time())), 'Offer has expired' - offer = self.getOffer(offer_id) - assert(offer), 'Offer not found: {}.'.format(offer_id.hex()) + msg_buf = BidMessage() + msg_buf.offer_msg_id = offer_id + msg_buf.time_valid = 60 * 10 + msg_buf.amount = int(amount) # amount of coin_from - assert(offer.expire_at > int(time.time())), 'Offer has expired' + coin_from = Coins(offer.coin_from) + coin_to = Coins(offer.coin_to) - msg_buf = BidMessage() - msg_buf.offer_msg_id = offer_id - msg_buf.time_valid = 60 * 10 - msg_buf.amount = int(amount) # amount of coin_from + contract_count = self.getNewContractId() - coin_from = Coins(offer.coin_from) - coin_to = Coins(offer.coin_to) + now = int(time.time()) + if offer.swap_type == SwapTypes.SELLER_FIRST: + msg_buf.pkhash_buyer = getKeyID(self.getContractPubkey(dt.datetime.fromtimestamp(now).date(), contract_count)) - contract_count = self.getNewContractId() + proof_addr, proof_sig = self.getProofOfFunds(coin_to, msg_buf.amount) + msg_buf.proof_address = proof_addr + msg_buf.proof_signature = proof_sig - now = int(time.time()) - if offer.swap_type == SwapTypes.SELLER_FIRST: - msg_buf.pkhash_buyer = getKeyID(self.getContractPubkey(dt.datetime.fromtimestamp(now).date(), contract_count)) + bid_bytes = msg_buf.SerializeToString() + payload_hex = str.format('{:02x}', MessageTypes.BID) + bid_bytes.hex() - proof_addr, proof_sig = self.getProofOfFunds(coin_to, msg_buf.amount) - msg_buf.proof_address = proof_addr - msg_buf.proof_signature = proof_sig + if addr_send_from is None: + bid_addr = self.callrpc('getnewaddress') + else: + bid_addr = addr_send_from + self.callrpc('smsgaddlocaladdress', [bid_addr]) # Enable receiving smsg + ro = self.callrpc('smsgsend', [bid_addr, offer.addr_from, payload_hex, False, 1, False, False, True]) + msg_id = ro['msgid'] - bid_bytes = msg_buf.SerializeToString() - payload_hex = str.format('{:02x}', MessageTypes.BID) + bid_bytes.hex() + bid_id = bytes.fromhex(msg_id) + bid = Bid( + bid_id=bid_id, + offer_id=offer_id, + amount=msg_buf.amount, + pkhash_buyer=msg_buf.pkhash_buyer, + proof_address=msg_buf.proof_address, - # TODO: reuse address? - bid_addr = self.callrpc('getnewaddress') - self.callrpc('smsgaddlocaladdress', [bid_addr]) # Enable receiving smsg - ro = self.callrpc('smsgsend', [bid_addr, offer.addr_from, payload_hex, False, 1, False, False, True]) - msg_id = ro['msgid'] + created_at=now, + contract_count=contract_count, + amount_to=(msg_buf.amount * offer.rate) // COIN, + expire_at=now + msg_buf.time_valid, + bid_addr=bid_addr, + was_sent=True, + ) + bid.setState(BidStates.BID_SENT) - bid_id = bytes.fromhex(msg_id) - bid = Bid( - bid_id=bid_id, - offer_id=offer_id, - amount=msg_buf.amount, - pkhash_buyer=msg_buf.pkhash_buyer, - proof_address=msg_buf.proof_address, + session = scoped_session(self.session_factory) + self.saveBidInSession(bid_id, bid, session) + if addr_send_from is None: + session.add(SmsgAddress(addr=bid_addr, use_type=MessageTypes.BID)) + session.commit() + session.close() + session.remove() - created_at=now, - contract_count=contract_count, - amount_to=(msg_buf.amount * offer.rate) // COIN, - expire_at=now + msg_buf.time_valid, - bid_addr=bid_addr, - was_sent=True, - ) - bid.setState(BidStates.BID_SENT) - - self.saveBid(bid_id, bid) - - self.log.info('Sent BID %s', bid_id.hex()) - return bid_id + self.log.info('Sent BID %s', bid_id.hex()) + return bid_id + finally: + self.mxDB.release() def getOffer(self, offer_id, sent=False): self.mxDB.acquire() @@ -2274,13 +2291,19 @@ class BasicSwap(): rv = [] now = int(time.time()) session = scoped_session(self.session_factory) + + query_str = 'SELECT bids.created_at, bids.bid_id, bids.offer_id, bids.amount, bids.state, bids.was_received, tx1.state, tx2.state FROM bids ' + \ + 'LEFT JOIN transactions AS tx1 ON tx1.bid_id = bids.bid_id AND tx1.tx_type = {} '.format(TxTypes.ITX) + \ + 'LEFT JOIN transactions AS tx2 ON tx2.bid_id = bids.bid_id AND tx2.tx_type = {} '.format(TxTypes.PTX) + if offer_id is not None: - q = session.query(Bid).filter(Bid.offer_id == offer_id) + query_str += 'WHERE bids.offer_id = x\'{}\' '.format(offer_id.hex()) elif sent: - q = session.query(Bid).filter(Bid.was_sent == True) # noqa E712 + query_str += 'WHERE bids.was_sent = 1 ' else: - q = session.query(Bid).filter(Bid.was_received == True) # noqa E712 - q = q.order_by(Bid.created_at.desc()) + query_str += 'WHERE bids.was_received = 1 ' + query_str += 'ORDER BY bids.created_at DESC' + q = self.engine.execute(query_str) for row in q: rv.append(row) return rv @@ -2313,6 +2336,21 @@ class BasicSwap(): finally: self.mxDB.release() + def listSmsgAddresses(self, use_type_str): + use_type = MessageTypes.OFFER if use_type_str == 'offer' else MessageTypes.BID + self.mxDB.acquire() + try: + session = scoped_session(self.session_factory) + rv = [] + q = self.engine.execute('SELECT addr FROM smsgaddresses WHERE use_type = {} ORDER BY addr_id DESC'.format(use_type)) + for row in q: + rv.append(row[0]) + return rv + finally: + session.close() + session.remove() + self.mxDB.release() + def callrpc(self, method, params=[], wallet=None): return callrpc(self.coin_clients[Coins.PART]['rpcport'], self.coin_clients[Coins.PART]['rpcauth'], method, params, wallet) diff --git a/basicswap/db.py b/basicswap/db.py index 811de34..a5d7d0c 100644 --- a/basicswap/db.py +++ b/basicswap/db.py @@ -9,7 +9,7 @@ import time import sqlalchemy as sa from sqlalchemy.ext.declarative import declarative_base -CURRENT_DB_VERSION = 1 +CURRENT_DB_VERSION = 2 Base = declarative_base() @@ -173,3 +173,10 @@ class SentOffer(Base): __tablename__ = 'sentoffers' offer_id = sa.Column(sa.LargeBinary, primary_key=True) + + +class SmsgAddress(Base): + __tablename__ = 'smsgaddresses' + addr_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) + addr = sa.Column(sa.String) + use_type = sa.Column(sa.Integer) diff --git a/basicswap/http_server.py b/basicswap/http_server.py index 7c836df..849008e 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -118,6 +118,7 @@ class HttpHandler(BaseHTTPRequestHandler): bid_id = bytes.fromhex(url_split[3]) assert(len(bid_id) == 28) return bytes(json.dumps(self.server.swap_client.viewBid(bid_id)), 'UTF-8') + assert(False), 'TODO' return bytes(json.dumps(self.server.swap_client.listBids()), 'UTF-8') def js_sentbids(self, url_split): @@ -245,6 +246,10 @@ class HttpHandler(BaseHTTPRequestHandler): else: self.server.last_form_id['newoffer'] = form_id + addr_from = form_data[b'addr_from'][0].decode('utf-8') + if addr_from == '-1': + addr_from = None + try: coin_from = Coins(int(form_data[b'coin_from'][0])) except Exception: @@ -268,7 +273,7 @@ class HttpHandler(BaseHTTPRequestHandler): else: lock_type = ABS_LOCK_TIME - offer_id = swap_client.postOffer(coin_from, coin_to, value_from, rate, min_bid, SwapTypes.SELLER_FIRST, auto_accept_bids=autoaccept, lock_type=lock_type, lock_value=lock_seconds) + offer_id = swap_client.postOffer(coin_from, coin_to, value_from, rate, min_bid, SwapTypes.SELLER_FIRST, lock_type=lock_type, lock_value=lock_seconds, auto_accept_bids=autoaccept, addr_send_from=addr_from) messages.append('Sent Offer ' + offer_id.hex() + '
Rate: ' + format8(rate)) coins = [] @@ -282,6 +287,7 @@ class HttpHandler(BaseHTTPRequestHandler): h2=self.server.title, messages=messages, coins=coins, + addrs=swap_client.listSmsgAddresses('offer'), form_id=os.urandom(8).hex(), ), 'UTF-8') @@ -298,6 +304,7 @@ class HttpHandler(BaseHTTPRequestHandler): messages = [] sent_bid_id = None + show_bid_form = None if post_string != '': form_data = urllib.parse.parse_qs(post_string) form_id = form_data[b'formid'][0].decode('utf-8') @@ -305,7 +312,15 @@ class HttpHandler(BaseHTTPRequestHandler): messages.append('Prevented double submit for form {}.'.format(form_id)) else: self.server.last_form_id['offer'] = form_id - sent_bid_id = swap_client.postBid(offer_id, offer.amount_from).hex() + + if b'newbid' in form_data: + show_bid_form = True + else: + addr_from = form_data[b'addr_from'][0].decode('utf-8') + if addr_from == '-1': + addr_from = None + + sent_bid_id = swap_client.postBid(offer_id, offer.amount_from).hex() coin_from = Coins(offer.coin_from) coin_to = Coins(offer.coin_to) @@ -325,7 +340,8 @@ class HttpHandler(BaseHTTPRequestHandler): 'addr_from': offer.addr_from, 'created_at': offer.created_at, 'expired_at': offer.expire_at, - 'sent': 'True' if offer.was_sent else 'False' + 'sent': 'True' if offer.was_sent else 'False', + 'show_bid_form': show_bid_form, } if offer.was_sent: @@ -341,7 +357,8 @@ class HttpHandler(BaseHTTPRequestHandler): sent_bid_id=sent_bid_id, messages=messages, data=data, - bids=[(b.bid_id.hex(), format8(b.amount), strBidState(b.state), strTxState(b.getITxState()), strTxState(b.getPTxState())) for b in bids], + bids=[(b[1].hex(), format8(b[3]), strBidState(b[4]), strTxState(b[6]), strTxState(b[7])) for b in bids], + addrs=None if show_bid_form is None else swap_client.listSmsgAddresses('bid'), form_id=os.urandom(8).hex(), ), 'UTF-8') @@ -510,8 +527,8 @@ class HttpHandler(BaseHTTPRequestHandler): title=self.server.title, h2=self.server.title, page_type='Sent' if sent else 'Received', - bids=[(time.strftime('%Y-%m-%d %H:%M', time.localtime(b.created_at)), - b.bid_id.hex(), b.offer_id.hex(), strBidState(b.state), strTxState(b.getITxState()), strTxState(b.getPTxState())) for b in bids], + bids=[(time.strftime('%Y-%m-%d %H:%M', time.localtime(b[0])), + b[1].hex(), b[2].hex(), strBidState(b[4]), strTxState(b[6]), strTxState(b[7])) for b in bids], ), 'UTF-8') def page_watched(self, url_split, post_string): diff --git a/basicswap/templates/offer.html b/basicswap/templates/offer.html index 7e27891..7f32b31 100644 --- a/basicswap/templates/offer.html +++ b/basicswap/templates/offer.html @@ -31,6 +31,28 @@ {% endif %} +
+{% if data.show_bid_form %} +

New Bid

+ + + + +
Send From Address + +
+{% else %} + +{% endif %} + +
+ +

Bids

@@ -39,10 +61,5 @@ {% endfor %}
Bid IDBid AmountBid StatusITX StatusPTX Status
-
- - -
-

home

diff --git a/basicswap/templates/offer_new.html b/basicswap/templates/offer_new.html index 503ec43..ef98ccd 100644 --- a/basicswap/templates/offer_new.html +++ b/basicswap/templates/offer_new.html @@ -8,6 +8,13 @@
+ +
Send From Address
Coin From