From 8c9105ce01d5beee3ad9ef3e90695aa36908f125 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Wed, 2 Dec 2020 13:24:52 +0200 Subject: [PATCH] Raise versions, add release notes. Add event log table. Offers can be revoked. Added separate range for retry delays. --- basicswap/__init__.py | 2 +- basicswap/basicswap.py | 183 +++++++++++++++++++++++++++--------- basicswap/db.py | 27 +++++- basicswap/messages.proto | 5 + basicswap/messages_pb2.py | 73 +++++++++++--- basicswap/network.py | 7 ++ doc/release-notes.md | 5 + tests/basicswap/test_run.py | 4 +- tests/basicswap/test_xmr.py | 45 ++++++++- 9 files changed, 286 insertions(+), 65 deletions(-) create mode 100644 doc/release-notes.md diff --git a/basicswap/__init__.py b/basicswap/__init__.py index 84acba5..49710cf 100644 --- a/basicswap/__init__.py +++ b/basicswap/__init__.py @@ -1,3 +1,3 @@ name = "basicswap" -__version__ = "0.0.5" +__version__ = "0.0.6" diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 05afbf0..84aeb21 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -9,6 +9,7 @@ import re import zmq import json import time +import base64 import shutil import random import logging @@ -59,9 +60,11 @@ from .messages_pb2 import ( XmrBidLockTxSigsMessage, XmrBidLockSpendTxMessage, XmrBidSecretMessage, + OfferRevokeMessage, ) from .db import ( CURRENT_DB_VERSION, + TableTypes, Base, DBKVInt, DBKVString, @@ -72,6 +75,7 @@ from .db import ( SentOffer, SmsgAddress, EventQueue, + EventLog, XmrOffer, XmrSwap, XmrSplitData, @@ -100,6 +104,7 @@ class MessageTypes(IntEnum): XMR_BID_TXN_SIGS_FL = auto() XMR_BID_LOCK_SPEND_TX_LF = auto() XMR_BID_SECRET_LF = auto() + OFFER_REVOKE = auto() class SwapTypes(IntEnum): @@ -176,6 +181,10 @@ class EventTypes(IntEnum): RECOVER_XMR_SWAP_LOCK_TX_B = auto() +class EventLogTypes(IntEnum): + FAILED_TX_B_PUBLISH = auto() + + class XmrSplitMsgTypes(IntEnum): BID = auto() BID_ACCEPT = auto() @@ -404,8 +413,11 @@ class BasicSwap(BaseApp): self._last_checked_xmr_swaps = 0 # TODO: Adjust ranges - self.min_delay_auto_accept = self.settings.get('min_delay_auto_accept', 10) - self.max_delay_auto_accept = self.settings.get('max_delay_auto_accept', 60) + self.min_delay_event = self.settings.get('min_delay_event', 10) + self.max_delay_event = self.settings.get('max_delay_event', 60) + + self.min_delay_retry = self.settings.get('min_delay_retry', 20) + self.max_delay_retry = self.settings.get('max_delay_retry', 120) self.swaps_in_progress = dict() @@ -922,6 +934,35 @@ class BasicSwap(BaseApp): self.log.info('Sent OFFER %s', offer_id.hex()) return offer_id + def revokeOffer(self, offer_id): + self.log.info('Revoking offer %s', offer_id.hex()) + + session = None + self.mxDB.acquire() + try: + session = scoped_session(self.session_factory) + + offer = session.query(Offer).filter_by(offer_id=offer_id).first() + + msg_buf = OfferRevokeMessage() + msg_buf.offer_msg_id = offer_id + + signature_enc = self.callcoinrpc(Coins.PART, 'signmessage', [offer.addr_from, offer_id.hex() + '_revoke']) + + msg_buf.signature = base64.b64decode(signature_enc) + + msg_bytes = msg_buf.SerializeToString() + payload_hex = str.format('{:02x}', MessageTypes.OFFER_REVOKE) + msg_bytes.hex() + + options = {'decodehex': True, 'ttl_is_seconds': True} + ro = self.callrpc('smsgsend', [offer.addr_from, self.network_addr, payload_hex, False, offer.time_valid, False, options]) + msg_id = ro['msgid'] + finally: + if session: + session.close() + session.remove() + self.mxDB.release() + def getPathKey(self, coin_from, coin_to, offer_created_at, contract_count, key_no, for_xmr=False): account = self.callcoinrpc(Coins.PART, 'extkey', ['account']) evkey = self.callcoinrpc(Coins.PART, 'extkey', ['account', 'default', 'true'])['evkey'] @@ -1141,7 +1182,7 @@ class BasicSwap(BaseApp): return (sign_for_addr, signature) - def saveBidInSession(self, bid_id, bid, session, xmr_swap=None): + def saveBidInSession(self, bid_id, bid, session, xmr_swap=None, save_in_progress=None): session.add(bid) if bid.initiate_tx: session.add(bid.initiate_tx) @@ -1158,6 +1199,11 @@ class BasicSwap(BaseApp): if xmr_swap is not None: session.add(xmr_swap) + if save_in_progress is not None: + if not isinstance(save_in_progress, Offer): + raise ValueError('Must specify offer for save_in_progress') + self.swaps_in_progress[bid_id] = (bid, save_in_progress) # (bid, offer) + def saveBid(self, bid_id, bid, xmr_swap=None): self.mxDB.acquire() try: @@ -1182,12 +1228,13 @@ class BasicSwap(BaseApp): def createEventInSession(self, delay, event_type, linked_id, session): self.log.debug('createEvent %d %s', event_type, linked_id.hex()) - event = EventQueue() - event.active_ind = 1 - event.created_at = int(time.time()) - event.trigger_at = event.created_at + delay - event.event_type = event_type - event.linked_id = linked_id + now = int(time.time()) + event = EventQueue( + active_ind=1, + created_at=now, + trigger_at=now + delay, + event_type=event_type, + linked_id=linked_id) session.add(event) def createEvent(self, delay, event_type, linked_id): @@ -1202,6 +1249,21 @@ class BasicSwap(BaseApp): finally: self.mxDB.release() + def logBidEvent(self, bid, event_type, event_msg, session): + self.log.debug('logBidEvent %s %s', bid.bid_id.hex(), event_type) + entry = EventLog( + active_ind=1, + created_at=int(time.time()), + linked_type=TableTypes.BID, + linked_id=bid.bid_id, + event_type=int(event_type), + event_msg=event_msg) + session.add(entry) + + def countBidEvents(self, bid, event_type, session): + q = session.execute('SELECT COUNT(*) FROM eventlog WHERE linked_type = {} AND linked_id = x\'{}\' AND event_type = {}'.format(int(TableTypes.BID), bid.bid_id.hex(), int(event_type))).first() + return q[0] + 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)) @@ -2370,7 +2432,7 @@ class BasicSwap(BaseApp): self.saveBidInSession(bid_id, bid, session, xmr_swap) if bid.was_sent: - delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) + delay = random.randrange(self.min_delay_event, self.max_delay_event) self.log.info('Sending xmr swap chain B lock tx for bid %s in %d seconds', bid_id.hex(), delay) self.createEventInSession(delay, EventTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, session) # bid.setState(BidStates.SWAP_DELAYING) @@ -2398,7 +2460,7 @@ class BasicSwap(BaseApp): self.saveBidInSession(bid_id, bid, session, xmr_swap) if bid.was_received: - delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) + delay = random.randrange(self.min_delay_event, self.max_delay_event) self.log.info('Releasing xmr swap secret for bid %s in %d seconds', bid_id.hex(), delay) self.createEventInSession(delay, EventTypes.SEND_XMR_SECRET, bid_id, session) @@ -2697,7 +2759,7 @@ class BasicSwap(BaseApp): if not bid.was_received: bid.setState(BidStates.SWAP_COMPLETED) if bid.was_received: - delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) + delay = random.randrange(self.min_delay_event, self.max_delay_event) self.log.info('Redeeming coin b lock tx for bid %s in %d seconds', bid_id.hex(), delay) self.createEventInSession(delay, EventTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, session) else: @@ -2710,9 +2772,7 @@ class BasicSwap(BaseApp): else: self.setBidError(bid.bid_id, bid, 'Unexpected txn spent coin a lock tx: {}'.format(spend_txid_hex), save_bid=False) - self.saveBidInSession(bid_id, bid, session, xmr_swap) - # Update copy of bid in swaps_in_progress - self.swaps_in_progress[bid_id] = (bid, offer) + self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) session.commit() except Exception as ex: self.log.error('process_XMR_SWAP_A_LOCK_tx_spend %s', str(ex)) @@ -2753,7 +2813,7 @@ class BasicSwap(BaseApp): txid=xmr_swap.a_lock_refund_spend_tx_id, ) if bid.xmr_b_lock_tx is not None: - delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) + delay = random.randrange(self.min_delay_event, self.max_delay_event) self.log.info('Recovering xmr swap chain B lock tx for bid %s in %d seconds', bid_id.hex(), delay) self.createEventInSession(delay, EventTypes.RECOVER_XMR_SWAP_LOCK_TX_B, bid_id, session) else: @@ -2767,9 +2827,7 @@ class BasicSwap(BaseApp): self.log.info('Coin a lock refund spent by unknown tx, bid {}'.format(bid_id.hex())) bid.setState(BidStates.XMR_SWAP_FAILED_SWIPED) - self.saveBidInSession(bid_id, bid, session, xmr_swap) - # Update copy of bid in swaps_in_progress - self.swaps_in_progress[bid_id] = (bid, offer) + self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) session.commit() except Exception as ex: self.log.error('process_XMR_SWAP_A_LOCK_REFUND_tx_spend %s', str(ex)) @@ -2990,6 +3048,7 @@ class BasicSwap(BaseApp): if existing_offer is None: offer = Offer( offer_id=offer_id, + active_ind=1, coin_from=offer_data.coin_from, coin_to=offer_data.coin_to, @@ -3028,6 +3087,44 @@ class BasicSwap(BaseApp): session.close() session.remove() + def processOfferRevoke(self, msg): + assert(msg['to'] == self.network_addr), 'Message received on wrong address' + + msg_bytes = bytes.fromhex(msg['hex'][2:-2]) + msg_data = OfferRevokeMessage() + msg_data.ParseFromString(msg_bytes) + + now = int(time.time()) + self.mxDB.acquire() + session = None + try: + session = scoped_session(self.session_factory) + + offer = session.query(Offer).filter_by(offer_id=msg_data.offer_msg_id).first() + if offer is None: + raise ValueError('Offer not found: {}'.format(msg_data.offer_msg_id.hex())) + + if offer.expire_at <= now: + raise ValueError('Offer already expired: {}'.format(msg_data.offer_msg_id.hex())) + + if len(msg_data.signature) != 65: + raise ValueError('Invalid signature length') + + signature_enc = base64.b64encode(msg_data.signature).decode('utf-8') + + passed = self.callcoinrpc(Coins.PART, 'verifymessage', [offer.addr_from, signature_enc, msg_data.offer_msg_id.hex() + '_revoke']) + assert(passed is True), 'Proof of funds signature invalid' + + offer.active_ind = 2 + # TODO: Remove message, or wait for expire + + session.add(offer) + session.commit() + finally: + if session: + session.close() + session.remove() + self.mxDB.release() def processBid(self, msg): self.log.debug('Processing bid msg %s', msg['msgid']) now = int(time.time()) @@ -3110,7 +3207,7 @@ class BasicSwap(BaseApp): if self.countAcceptedBids(offer_id) > 0: self.log.info('Not auto accepting bid %s, already have', bid_id.hex()) else: - delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) + 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) @@ -3275,7 +3372,7 @@ class BasicSwap(BaseApp): bid.setState(BidStates.SWAP_DELAYING) self.saveBidInSession(bid.bid_id, bid, session, xmr_swap) - delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) + delay = random.randrange(self.min_delay_event, self.max_delay_event) self.log.info('Responding to xmr bid accept %s in %d seconds', bid.bid_id.hex(), delay) self.createEventInSession(delay, EventTypes.SIGN_XMR_SWAP_LOCK_TX_A, bid.bid_id, session) @@ -3573,15 +3670,22 @@ class BasicSwap(BaseApp): try: b_lock_tx_id = ci_to.publishBLockTx(xmr_swap.pkbv, xmr_swap.pkbs, bid.amount_to, xmr_offer.b_fee_rate) except Exception as ex: - self.log.error('publishBLockTx failed for bid {} with error {}'.format(bid_id.hex(), str(ex))) + error_msg = 'publishBLockTx failed for bid {} with error {}'.format(bid_id.hex(), str(ex)) + num_retries = self.countBidEvents(bid, EventLogTypes.FAILED_TX_B_PUBLISH, session) + if num_retries > 0: + error_msg += ', retry no. {}'.format(num_retries) + self.log.error(error_msg) - if 'not enough unlocked money' in str(ex): - delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) # TODO: New delay range + str_error = str(ex) + if num_retries < 5 and 'not enough unlocked money' in str_error or 'transaction was rejected by daemon' in str_error: + delay = random.randrange(self.min_delay_retry, self.max_delay_retry) self.log.info('Retrying sending xmr swap chain B lock tx for bid %s in %d seconds', bid_id.hex(), delay) self.createEventInSession(delay, EventTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, session) else: self.setBidError(bid_id, bid, 'publishBLockTx failed: ' + str(ex), save_bid=False) - self.saveBidInSession(bid_id, bid, session, xmr_swap) + self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) + + self.logBidEvent(bid, EventLogTypes.FAILED_TX_B_PUBLISH, str_error, session) return self.log.debug('Submitted lock txn %s to %s chain for bid %s', b_lock_tx_id.hex(), chainparams[coin_to]['name'], bid_id.hex()) @@ -3592,10 +3696,7 @@ class BasicSwap(BaseApp): ) bid.xmr_b_lock_tx.setState(TxStates.TX_NONE) - self.saveBidInSession(bid_id, bid, session, xmr_swap) - - # Update copy of bid in swaps_in_progress - self.swaps_in_progress[bid_id] = (bid, offer) + self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) def sendXmrBidSecret(self, bid_id, session): # Leader sending lock tx a release secret (MSG5F) @@ -3627,9 +3728,7 @@ class BasicSwap(BaseApp): xmr_swap.coin_a_lock_refund_spend_tx_msg_id = bytes.fromhex(ro['msgid']) bid.setState(BidStates.XMR_SWAP_SECRET_SHARED) - self.saveBidInSession(bid_id, bid, session, xmr_swap) - # Update copy of bid in swaps_in_progress - self.swaps_in_progress[bid_id] = (bid, offer) + self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) def redeemXmrBidCoinALockTx(self, bid_id, session): # Follower redeeming A lock tx @@ -3678,9 +3777,7 @@ class BasicSwap(BaseApp): ) bid.xmr_a_lock_spend_tx.setState(TxStates.TX_NONE) - self.saveBidInSession(bid_id, bid, session, xmr_swap) - # Update copy of bid in swaps_in_progress - self.swaps_in_progress[bid_id] = (bid, offer) + self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) def redeemXmrBidCoinBLockTx(self, bid_id, session): # Leader redeeming B lock tx @@ -3714,9 +3811,7 @@ class BasicSwap(BaseApp): bid.xmr_b_lock_tx.spend_txid = txid bid.setState(BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED) # TODO: Why does using bid.txns error here? - self.saveBidInSession(bid_id, bid, session, xmr_swap) - # Update copy of bid in swaps_in_progress - self.swaps_in_progress[bid_id] = (bid, offer) + self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) def recoverXmrBidCoinBLockTx(self, bid_id, session): # Follower recovering B lock tx @@ -3750,9 +3845,7 @@ class BasicSwap(BaseApp): bid.xmr_b_lock_tx.spend_txid = txid bid.setState(BidStates.XMR_SWAP_NOSCRIPT_TX_RECOVERED) - self.saveBidInSession(bid_id, bid, session, xmr_swap) - # Update copy of bid in swaps_in_progress - self.swaps_in_progress[bid_id] = (bid, offer) + self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer) def processXmrBidCoinALockSigs(self, msg): # Leader processing MSG3L @@ -3803,7 +3896,7 @@ class BasicSwap(BaseApp): assert(v), 'Invalid signature for lock refund spend txn' self.addLockRefundSigs(xmr_swap, ci_from) - delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) + delay = random.randrange(self.min_delay_event, self.max_delay_event) self.log.info('Sending coin A lock tx for xmr bid %s in %d seconds', bid_id.hex(), delay) self.createEvent(delay, EventTypes.SEND_XMR_SWAP_LOCK_TX_A, bid_id) @@ -3902,7 +3995,7 @@ class BasicSwap(BaseApp): xmr_swap.sv = msg_data.secret_value - delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) + delay = random.randrange(self.min_delay_event, self.max_delay_event) self.log.info('Redeeming coin A lock tx for xmr bid %s in %d seconds', bid_id.hex(), delay) self.createEvent(delay, EventTypes.REDEEM_XMR_SWAP_LOCK_TX_A, bid_id) @@ -3934,6 +4027,8 @@ class BasicSwap(BaseApp): self.processXmrSplitMessage(msg) elif msg_type == MessageTypes.XMR_BID_SECRET_LF: self.processXmrSecretMessage(msg) + if msg_type == MessageTypes.OFFER_REVOKE: + self.processOfferRevoke(msg) except Exception as ex: self.log.error('processMsg %s', str(ex)) @@ -4143,7 +4238,7 @@ class BasicSwap(BaseApp): if sent: q = session.query(Offer).filter(Offer.was_sent == True) # noqa E712 else: - q = session.query(Offer).filter(Offer.expire_at > now) + q = session.query(Offer).filter(sa.and_(Offer.expire_at > now, Offer.active_ind == 1)) filter_offer_id = filters.get('offer_id', None) if filter_offer_id is not None: diff --git a/basicswap/db.py b/basicswap/db.py index fa020da..5f18413 100644 --- a/basicswap/db.py +++ b/basicswap/db.py @@ -1,16 +1,18 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2019 tecnovert +# Copyright (c) 2019-2020 tecnovert # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. import struct import time import sqlalchemy as sa + from sqlalchemy.ext.declarative import declarative_base +from enum import IntEnum, auto -CURRENT_DB_VERSION = 2 +CURRENT_DB_VERSION = 3 Base = declarative_base() @@ -32,6 +34,7 @@ class Offer(Base): __tablename__ = 'offers' offer_id = sa.Column(sa.LargeBinary, primary_key=True) + active_ind = sa.Column(sa.Integer) coin_from = sa.Column(sa.Integer) coin_to = sa.Column(sa.Integer) @@ -75,6 +78,7 @@ class Bid(Base): bid_id = sa.Column(sa.LargeBinary, primary_key=True) offer_id = sa.Column(sa.LargeBinary, sa.ForeignKey('offers.offer_id')) + active_ind = sa.Column(sa.Integer) was_sent = sa.Column(sa.Boolean) was_received = sa.Column(sa.Boolean) @@ -212,6 +216,20 @@ class EventQueue(Base): event_data = sa.Column(sa.LargeBinary) +class EventLog(Base): + __tablename__ = 'eventlog' + + event_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) + active_ind = sa.Column(sa.Integer) + created_at = sa.Column(sa.BigInteger) + linked_type = sa.Column(sa.Integer) + linked_id = sa.Column(sa.LargeBinary) + event_type = sa.Column(sa.Integer) + event_msg = sa.Column(sa.String) + + __table_args__ = (sa.Index('main_index', 'linked_type', 'linked_id'), ) + + class XmrOffer(Base): __tablename__ = 'xmr_offers' @@ -310,3 +328,8 @@ class XmrSplitData(Base): msg_sequence = sa.Column(sa.Integer) dleag = sa.Column(sa.LargeBinary) created_at = sa.Column(sa.BigInteger) + + +class TableTypes(IntEnum): + OFFER = auto() + BID = auto() diff --git a/basicswap/messages.proto b/basicswap/messages.proto index d345799..162fbb1 100644 --- a/basicswap/messages.proto +++ b/basicswap/messages.proto @@ -50,6 +50,11 @@ message BidAcceptMessage { bytes contract_script = 3; } +message OfferRevokeMessage { + bytes offer_msg_id = 1; + bytes signature = 2; +} + message XmrBidMessage { /* MSG1L, F -> L */ diff --git a/basicswap/messages_pb2.py b/basicswap/messages_pb2.py index d2cf8d4..2d0d116 100644 --- a/basicswap/messages_pb2.py +++ b/basicswap/messages_pb2.py @@ -19,7 +19,7 @@ DESCRIPTOR = _descriptor.FileDescriptor( syntax='proto3', serialized_options=None, create_key=_descriptor._internal_create_key, - serialized_pb=b'\n\x0emessages.proto\x12\tbasicswap\"\xd8\x03\n\x0cOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x33\n\tlock_type\x18\x07 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0e \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x0f \x01(\x04\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\x8c\x01\n\nBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x04 \x01(\x0c\x12\x15\n\rproof_address\x18\x05 \x01(\t\x12\x17\n\x0fproof_signature\x18\x06 \x01(\t\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\"\x99\x01\n\rXmrBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04pkaf\x18\x04 \x01(\x0c\x12\r\n\x05pkarf\x18\x05 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x06 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x07 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x08 \x01(\x0c\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x9b\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\n\n\x02sh\x18\x02 \x01(\x0c\x12\x0c\n\x04pkal\x18\x03 \x01(\x0c\x12\r\n\x05pkarl\x18\x04 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x05 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x06 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x07 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x08 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\t \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\n \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\x0b \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\x0c \x01(\x0c\"r\n\x17XmrBidLockTxSigsMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12$\n\x1c\x61\x66_lock_refund_spend_tx_esig\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61\x66_lock_refund_tx_sig\x18\x03 \x01(\x0c\"f\n\x18XmrBidLockSpendTxMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x17\n\x0f\x61_lock_spend_tx\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x03 \x01(\x0c\"?\n\x13XmrBidSecretMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x14\n\x0csecret_value\x18\x02 \x01(\x0c\x62\x06proto3' + serialized_pb=b'\n\x0emessages.proto\x12\tbasicswap\"\xd8\x03\n\x0cOfferMessage\x12\x11\n\tcoin_from\x18\x01 \x01(\r\x12\x0f\n\x07\x63oin_to\x18\x02 \x01(\r\x12\x13\n\x0b\x61mount_from\x18\x03 \x01(\x04\x12\x0c\n\x04rate\x18\x04 \x01(\x04\x12\x16\n\x0emin_bid_amount\x18\x05 \x01(\x04\x12\x12\n\ntime_valid\x18\x06 \x01(\x04\x12\x33\n\tlock_type\x18\x07 \x01(\x0e\x32 .basicswap.OfferMessage.LockType\x12\x12\n\nlock_value\x18\x08 \x01(\r\x12\x11\n\tswap_type\x18\t \x01(\r\x12\x15\n\rproof_address\x18\n \x01(\t\x12\x17\n\x0fproof_signature\x18\x0b \x01(\t\x12\x15\n\rpkhash_seller\x18\x0c \x01(\x0c\x12\x13\n\x0bsecret_hash\x18\r \x01(\x0c\x12\x15\n\rfee_rate_from\x18\x0e \x01(\x04\x12\x13\n\x0b\x66\x65\x65_rate_to\x18\x0f \x01(\x04\"q\n\x08LockType\x12\x0b\n\x07NOT_SET\x10\x00\x12\x18\n\x14SEQUENCE_LOCK_BLOCKS\x10\x01\x12\x16\n\x12SEQUENCE_LOCK_TIME\x10\x02\x12\x13\n\x0f\x41\x42S_LOCK_BLOCKS\x10\x03\x12\x11\n\rABS_LOCK_TIME\x10\x04\"\x8c\x01\n\nBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x14\n\x0cpkhash_buyer\x18\x04 \x01(\x0c\x12\x15\n\rproof_address\x18\x05 \x01(\t\x12\x17\n\x0fproof_signature\x18\x06 \x01(\t\"V\n\x10\x42idAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x15\n\rinitiate_txid\x18\x02 \x01(\x0c\x12\x17\n\x0f\x63ontract_script\x18\x03 \x01(\x0c\"=\n\x12OfferRevokeMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\"\x99\x01\n\rXmrBidMessage\x12\x14\n\x0coffer_msg_id\x18\x01 \x01(\x0c\x12\x12\n\ntime_valid\x18\x02 \x01(\x04\x12\x0e\n\x06\x61mount\x18\x03 \x01(\x04\x12\x0c\n\x04pkaf\x18\x04 \x01(\x0c\x12\r\n\x05pkarf\x18\x05 \x01(\x0c\x12\x0c\n\x04kbvf\x18\x06 \x01(\x0c\x12\x12\n\nkbsf_dleag\x18\x07 \x01(\x0c\x12\x0f\n\x07\x64\x65st_af\x18\x08 \x01(\x0c\"T\n\x0fXmrSplitMessage\x12\x0e\n\x06msg_id\x18\x01 \x01(\x0c\x12\x10\n\x08msg_type\x18\x02 \x01(\r\x12\x10\n\x08sequence\x18\x03 \x01(\r\x12\r\n\x05\x64leag\x18\x04 \x01(\x0c\"\x9b\x02\n\x13XmrBidAcceptMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\n\n\x02sh\x18\x02 \x01(\x0c\x12\x0c\n\x04pkal\x18\x03 \x01(\x0c\x12\r\n\x05pkarl\x18\x04 \x01(\x0c\x12\x0c\n\x04kbvl\x18\x05 \x01(\x0c\x12\x12\n\nkbsl_dleag\x18\x06 \x01(\x0c\x12\x11\n\ta_lock_tx\x18\x07 \x01(\x0c\x12\x18\n\x10\x61_lock_tx_script\x18\x08 \x01(\x0c\x12\x18\n\x10\x61_lock_refund_tx\x18\t \x01(\x0c\x12\x1f\n\x17\x61_lock_refund_tx_script\x18\n \x01(\x0c\x12\x1e\n\x16\x61_lock_refund_spend_tx\x18\x0b \x01(\x0c\x12\x1d\n\x15\x61l_lock_refund_tx_sig\x18\x0c \x01(\x0c\"r\n\x17XmrBidLockTxSigsMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12$\n\x1c\x61\x66_lock_refund_spend_tx_esig\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61\x66_lock_refund_tx_sig\x18\x03 \x01(\x0c\"f\n\x18XmrBidLockSpendTxMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x17\n\x0f\x61_lock_spend_tx\x18\x02 \x01(\x0c\x12\x1d\n\x15\x61l_lock_spend_tx_esig\x18\x03 \x01(\x0c\"?\n\x13XmrBidSecretMessage\x12\x12\n\nbid_msg_id\x18\x01 \x01(\x0c\x12\x14\n\x0csecret_value\x18\x02 \x01(\x0c\x62\x06proto3' ) @@ -309,6 +309,45 @@ _BIDACCEPTMESSAGE = _descriptor.Descriptor( ) +_OFFERREVOKEMESSAGE = _descriptor.Descriptor( + name='OfferRevokeMessage', + full_name='basicswap.OfferRevokeMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + create_key=_descriptor._internal_create_key, + fields=[ + _descriptor.FieldDescriptor( + name='offer_msg_id', full_name='basicswap.OfferRevokeMessage.offer_msg_id', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + _descriptor.FieldDescriptor( + name='signature', full_name='basicswap.OfferRevokeMessage.signature', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=b"", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=735, + serialized_end=796, +) + + _XMRBIDMESSAGE = _descriptor.Descriptor( name='XmrBidMessage', full_name='basicswap.XmrBidMessage', @@ -385,8 +424,8 @@ _XMRBIDMESSAGE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=736, - serialized_end=889, + serialized_start=799, + serialized_end=952, ) @@ -438,8 +477,8 @@ _XMRSPLITMESSAGE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=891, - serialized_end=975, + serialized_start=954, + serialized_end=1038, ) @@ -547,8 +586,8 @@ _XMRBIDACCEPTMESSAGE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=978, - serialized_end=1261, + serialized_start=1041, + serialized_end=1324, ) @@ -593,8 +632,8 @@ _XMRBIDLOCKTXSIGSMESSAGE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1263, - serialized_end=1377, + serialized_start=1326, + serialized_end=1440, ) @@ -639,8 +678,8 @@ _XMRBIDLOCKSPENDTXMESSAGE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1379, - serialized_end=1481, + serialized_start=1442, + serialized_end=1544, ) @@ -678,8 +717,8 @@ _XMRBIDSECRETMESSAGE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1483, - serialized_end=1546, + serialized_start=1546, + serialized_end=1609, ) _OFFERMESSAGE.fields_by_name['lock_type'].enum_type = _OFFERMESSAGE_LOCKTYPE @@ -687,6 +726,7 @@ _OFFERMESSAGE_LOCKTYPE.containing_type = _OFFERMESSAGE DESCRIPTOR.message_types_by_name['OfferMessage'] = _OFFERMESSAGE DESCRIPTOR.message_types_by_name['BidMessage'] = _BIDMESSAGE DESCRIPTOR.message_types_by_name['BidAcceptMessage'] = _BIDACCEPTMESSAGE +DESCRIPTOR.message_types_by_name['OfferRevokeMessage'] = _OFFERREVOKEMESSAGE DESCRIPTOR.message_types_by_name['XmrBidMessage'] = _XMRBIDMESSAGE DESCRIPTOR.message_types_by_name['XmrSplitMessage'] = _XMRSPLITMESSAGE DESCRIPTOR.message_types_by_name['XmrBidAcceptMessage'] = _XMRBIDACCEPTMESSAGE @@ -716,6 +756,13 @@ BidAcceptMessage = _reflection.GeneratedProtocolMessageType('BidAcceptMessage', }) _sym_db.RegisterMessage(BidAcceptMessage) +OfferRevokeMessage = _reflection.GeneratedProtocolMessageType('OfferRevokeMessage', (_message.Message,), { + 'DESCRIPTOR' : _OFFERREVOKEMESSAGE, + '__module__' : 'messages_pb2' + # @@protoc_insertion_point(class_scope:basicswap.OfferRevokeMessage) + }) +_sym_db.RegisterMessage(OfferRevokeMessage) + XmrBidMessage = _reflection.GeneratedProtocolMessageType('XmrBidMessage', (_message.Message,), { 'DESCRIPTOR' : _XMRBIDMESSAGE, '__module__' : 'messages_pb2' diff --git a/basicswap/network.py b/basicswap/network.py index 43370ca..388165b 100644 --- a/basicswap/network.py +++ b/basicswap/network.py @@ -12,3 +12,10 @@ TODO: class Peer: pass + + +class Network: + def __init__(self, network_port, network_key): + self._network_port = network_port + self._network_key = network_key + self._peers = [] diff --git a/doc/release-notes.md b/doc/release-notes.md new file mode 100644 index 0000000..8c8bf57 --- /dev/null +++ b/doc/release-notes.md @@ -0,0 +1,5 @@ + +0.0.6 +============== +- Experimental support for XMR swaps. + - Single direction only, scriptless -> XMR diff --git a/tests/basicswap/test_run.py b/tests/basicswap/test_run.py index bd37abe..37edfee 100644 --- a/tests/basicswap/test_run.py +++ b/tests/basicswap/test_run.py @@ -176,8 +176,8 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey): 'check_watched_seconds': 4, 'check_expired_seconds': 60, 'check_events_seconds': 1, - 'min_delay_auto_accept': 1, - 'max_delay_auto_accept': 5 + 'min_delay_event': 1, + 'max_delay_event': 5 } with open(settings_path, 'w') as fp: json.dump(settings, fp, indent=4) diff --git a/tests/basicswap/test_xmr.py b/tests/basicswap/test_xmr.py index 75d3863..48fcc3b 100644 --- a/tests/basicswap/test_xmr.py +++ b/tests/basicswap/test_xmr.py @@ -235,8 +235,10 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey): 'check_expired_seconds': 60, 'check_events_seconds': 1, 'check_xmr_swaps_seconds': 1, - 'min_delay_auto_accept': 1, - 'max_delay_auto_accept': 5 + 'min_delay_event': 1, + 'max_delay_event': 5, + 'min_delay_retry': 2, + 'max_delay_retry': 10 } with open(settings_path, 'w') as fp: @@ -521,6 +523,22 @@ class Test(unittest.TestCase): return raise ValueError('wait_for_offer timed out.') + def wait_for_no_offer(self, swap_client, offer_id, wait_for=20): + logging.info('wait_for_no_offer %s', offer_id.hex()) + for i in range(wait_for): + if stop_test: + raise ValueError('Test stopped.') + time.sleep(1) + offers = swap_client.listOffers() + found_offer = False + for offer in offers: + if offer.offer_id == offer_id: + found_offer = True + break + if not found_offer: + return True + raise ValueError('wait_for_offer timed out.') + def wait_for_bid(self, swap_client, bid_id, state=None, sent=False, wait_for=20): logging.info('wait_for_bid %s', bid_id.hex()) for i in range(wait_for): @@ -688,18 +706,39 @@ class Test(unittest.TestCase): bid1_id = swap_clients[1].postXmrBid(offer1_id, offer1.amount_from) bid2_id = swap_clients[1].postXmrBid(offer2_id, offer2.amount_from) + offer3_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 11 * COIN, 0.15 * XMR_COIN, 11 * COIN, SwapTypes.XMR_SWAP) + self.wait_for_bid(swap_clients[0], bid1_id, BidStates.BID_RECEIVED) swap_clients[0].acceptXmrBid(bid1_id) + self.wait_for_offer(swap_clients[1], offer3_id) + offer3 = swap_clients[1].getOffer(offer3_id) + bid3_id = swap_clients[1].postXmrBid(offer3_id, offer3.amount_from) + self.wait_for_bid(swap_clients[0], bid2_id, BidStates.BID_RECEIVED) swap_clients[0].acceptXmrBid(bid2_id) + self.wait_for_bid(swap_clients[0], bid3_id, BidStates.BID_RECEIVED) + swap_clients[0].acceptXmrBid(bid3_id) + self.wait_for_bid(swap_clients[0], bid1_id, BidStates.SWAP_COMPLETED, wait_for=180) self.wait_for_bid(swap_clients[1], bid1_id, BidStates.SWAP_COMPLETED, sent=True) - self.wait_for_bid(swap_clients[0], bid2_id, BidStates.SWAP_COMPLETED, wait_for=180) + self.wait_for_bid(swap_clients[0], bid2_id, BidStates.SWAP_COMPLETED, wait_for=60) self.wait_for_bid(swap_clients[1], bid2_id, BidStates.SWAP_COMPLETED, sent=True) + self.wait_for_bid(swap_clients[0], bid3_id, BidStates.SWAP_COMPLETED, wait_for=60) + self.wait_for_bid(swap_clients[1], bid3_id, BidStates.SWAP_COMPLETED, sent=True) + + def test_07_revoke_offer(self): + logging.info('---------- Test offer revocaction') + swap_clients = self.swap_clients + offer_id = swap_clients[0].postOffer(Coins.BTC, Coins.XMR, 10 * COIN, 100 * XMR_COIN, 10 * COIN, SwapTypes.XMR_SWAP) + self.wait_for_offer(swap_clients[1], offer_id) + + swap_clients[0].revokeOffer(offer_id) + + self.wait_for_no_offer(swap_clients[1], offer_id) if __name__ == '__main__': unittest.main()