Debug mode is disabled by default.
This commit is contained in:
tecnovert 2020-11-15 19:02:46 +02:00
parent 009729aa96
commit 18a5322f10
No known key found for this signature in database
GPG key ID: 8ED6D8750C4E3F93
14 changed files with 503 additions and 91 deletions

View file

@ -36,7 +36,7 @@ class BaseApp:
self.settings = settings self.settings = settings
self.coin_clients = {} self.coin_clients = {}
self.mxDB = threading.RLock() self.mxDB = threading.RLock()
self.debug = self.settings.get('debug', cfg.DEBUG) self.debug = self.settings.get('debug', False)
self.prepareLogging() self.prepareLogging()
self.log.info('Network: {}'.format(self.chain)) self.log.info('Network: {}'.format(self.chain))

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2019 tecnovert # Copyright (c) 2019-2020 tecnovert
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@ -57,6 +57,8 @@ from .messages_pb2 import (
XmrBidMessage, XmrBidMessage,
XmrBidAcceptMessage, XmrBidAcceptMessage,
XmrSplitMessage, XmrSplitMessage,
XmrBidLockTxSigsMessage,
XmrBidLockSpendTxMessage,
) )
from .db import ( from .db import (
CURRENT_DB_VERSION, CURRENT_DB_VERSION,
@ -95,6 +97,7 @@ class MessageTypes(IntEnum):
XMR_BID = auto() XMR_BID = auto()
XMR_BID_SPLIT = auto() XMR_BID_SPLIT = auto()
XMR_BID_ACCEPT = auto() XMR_BID_ACCEPT = auto()
XMR_BID_TXN_SIGS_FL = auto()
class SwapTypes(IntEnum): class SwapTypes(IntEnum):
@ -120,6 +123,8 @@ class BidStates(IntEnum):
SWAP_INITIATED = auto() # Initiate txn validated SWAP_INITIATED = auto() # Initiate txn validated
SWAP_PARTICIPATING = auto() # Participate txn validated SWAP_PARTICIPATING = auto() # Participate txn validated
SWAP_COMPLETED = auto() # All swap txns spent SWAP_COMPLETED = auto() # All swap txns spent
XMR_SWAP_SCRIPT_COIN_LOCKED = auto()
SWAP_DELAYING = auto()
SWAP_TIMEDOUT = auto() SWAP_TIMEDOUT = auto()
BID_ABANDONED = auto() # Bid will no longer be processed BID_ABANDONED = auto() # Bid will no longer be processed
BID_ERROR = auto() # An error occurred BID_ERROR = auto() # An error occurred
@ -141,9 +146,15 @@ class TxTypes(IntEnum):
PTX_REDEEM = auto() PTX_REDEEM = auto()
PTX_REFUND = auto() PTX_REFUND = auto()
XMR_SWAP_A_LOCK = auto()
XMR_SWAP_A_LOCK_REFUND = auto()
XMR_SWAP_A_LOCK_REFUND_SPEND = auto()
class EventTypes(IntEnum): class EventTypes(IntEnum):
ACCEPT_BID = auto() ACCEPT_BID = auto()
SIGN_XMR_SWAP_LOCK_TX_A = auto()
SEND_XMR_SWAP_LOCK_TX_A = auto()
class XmrSplitMsgTypes(IntEnum): class XmrSplitMsgTypes(IntEnum):
@ -467,15 +478,15 @@ class BasicSwap(BaseApp):
def createInterface(self, coin): def createInterface(self, coin):
if coin == Coins.PART: if coin == Coins.PART:
return PARTInterface(self.coin_clients[coin]) return PARTInterface(self.coin_clients[coin], self.chain)
elif coin == Coins.BTC: elif coin == Coins.BTC:
return BTCInterface(self.coin_clients[coin]) return BTCInterface(self.coin_clients[coin], self.chain)
elif coin == Coins.LTC: elif coin == Coins.LTC:
return LTCInterface(self.coin_clients[coin]) return LTCInterface(self.coin_clients[coin], self.chain)
elif coin == Coins.NMC: elif coin == Coins.NMC:
return NMCInterface(self.coin_clients[coin]) return NMCInterface(self.coin_clients[coin], self.chain)
elif coin == Coins.XMR: elif coin == Coins.XMR:
return XMRInterface(self.coin_clients[coin]) return XMRInterface(self.coin_clients[coin], self.chain)
else: else:
raise ValueError('Unknown coin type') raise ValueError('Unknown coin type')
@ -1087,11 +1098,8 @@ class BasicSwap(BaseApp):
finally: finally:
self.mxDB.release() self.mxDB.release()
def createEvent(self, delay, event_type, linked_id): def createEventInSession(self, delay, event_type, linked_id, session):
self.log.debug('createEvent %d %s', event_type, linked_id.hex()) self.log.debug('createEvent %d %s', event_type, linked_id.hex())
self.mxDB.acquire()
try:
session = scoped_session(self.session_factory)
event = EventQueue() event = EventQueue()
event.active_ind = 1 event.active_ind = 1
event.created_at = int(time.time()) event.created_at = int(time.time())
@ -1099,6 +1107,13 @@ class BasicSwap(BaseApp):
event.event_type = event_type event.event_type = event_type
event.linked_id = linked_id event.linked_id = linked_id
session.add(event) session.add(event)
def createEvent(self, delay, event_type, linked_id):
#self.log.debug('createEvent %d %s', event_type, linked_id.hex())
self.mxDB.acquire()
try:
session = scoped_session(self.session_factory)
self.createEventInSession(delay, event_type, linked_id, session)
session.commit() session.commit()
session.close() session.close()
session.remove() session.remove()
@ -1351,11 +1366,6 @@ class BasicSwap(BaseApp):
assert(xmr_offer), 'XMR offer not found: {}.'.format(offer_id.hex()) assert(xmr_offer), 'XMR offer not found: {}.'.format(offer_id.hex())
assert(offer.expire_at > int(time.time())), 'Offer has expired' assert(offer.expire_at > int(time.time())), 'Offer has expired'
msg_buf = XmrBidMessage()
msg_buf.offer_msg_id = offer_id
msg_buf.time_valid = 60 * 10
msg_buf.amount = int(amount) # amount of coin_from
coin_from = Coins(offer.coin_from) coin_from = Coins(offer.coin_from)
coin_to = Coins(offer.coin_to) coin_to = Coins(offer.coin_to)
ci_from = self.ci(coin_from) ci_from = self.ci(coin_from)
@ -1363,6 +1373,14 @@ class BasicSwap(BaseApp):
self.checkSynced(coin_from, coin_to) self.checkSynced(coin_from, coin_to)
msg_buf = XmrBidMessage()
msg_buf.offer_msg_id = offer_id
msg_buf.time_valid = 60 * 10
msg_buf.amount = int(amount) # amount of coin_from
address_out = self.getReceiveAddressFromPool(coin_from, offer_id, TxTypes.XMR_SWAP_A_LOCK)
msg_buf.dest_af = ci_from.decodeAddress(address_out)
bid_created_at = int(time.time()) bid_created_at = int(time.time())
if offer.swap_type != SwapTypes.XMR_SWAP: if offer.swap_type != SwapTypes.XMR_SWAP:
raise ValueError('TODO') raise ValueError('TODO')
@ -1370,6 +1388,7 @@ class BasicSwap(BaseApp):
# Follower to leader # Follower to leader
xmr_swap = XmrSwap() xmr_swap = XmrSwap()
xmr_swap.contract_count = self.getNewContractId() xmr_swap.contract_count = self.getNewContractId()
xmr_swap.dest_af = msg_buf.dest_af
xmr_swap.b_restore_height = self.ci(coin_to).getChainHeight() xmr_swap.b_restore_height = self.ci(coin_to).getChainHeight()
kbvf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 1, for_xmr=True) kbvf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 1, for_xmr=True)
kbsf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 2, for_xmr=True) kbsf = self.getPathKey(coin_from, coin_to, bid_created_at, xmr_swap.contract_count, 2, for_xmr=True)
@ -1580,7 +1599,8 @@ class BasicSwap(BaseApp):
session.close() session.close()
session.remove() session.remove()
self.swaps_in_progress[bid_id] = (bid, offer) #self.swaps_in_progress[bid_id] = (bid, offer)
# Add to swaps_in_progress only when waiting on txns
self.log.info('Sent XMR_BID_ACCEPT %s', bid_id.hex()) self.log.info('Sent XMR_BID_ACCEPT %s', bid_id.hex())
return bid_id return bid_id
finally: finally:
@ -1625,6 +1645,7 @@ class BasicSwap(BaseApp):
self.mxDB.release() self.mxDB.release()
def setBidError(self, bid_id, bid, error_str): def setBidError(self, bid_id, bid, error_str):
self.log.error('Bid %s - Error: %s', bid_id.hex(), error_str)
bid.setState(BidStates.BID_ERROR) bid.setState(BidStates.BID_ERROR)
bid.state_note = 'error msg: ' + error_str bid.state_note = 'error msg: ' + error_str
self.saveBid(bid_id, bid) self.saveBid(bid_id, bid)
@ -2435,6 +2456,10 @@ class BasicSwap(BaseApp):
for row in q: for row in q:
if row.event_type == EventTypes.ACCEPT_BID: if row.event_type == EventTypes.ACCEPT_BID:
self.acceptBid(row.linked_id) self.acceptBid(row.linked_id)
elif row.event_type == EventTypes.SIGN_XMR_SWAP_LOCK_TX_A:
self.sendXmrBidTxnSigsFtoL(row.linked_id, session)
elif row.event_type == EventTypes.SEND_XMR_SWAP_LOCK_TX_A:
self.sendXmrBidCoinALockTx(row.linked_id, session)
else: else:
self.log.warning('Unknown event type: %d', row.event_type) self.log.warning('Unknown event type: %d', row.event_type)
@ -2459,9 +2484,9 @@ class BasicSwap(BaseApp):
if num_segments > 1: if num_segments > 1:
try: try:
self.receiveXmrBid(bid, session) self.receiveXmrBid(bid, session)
except Exception as e: except Exception as ex:
self.log.info('Verify xmr bid {} failed: {}'.format(bid.bid_id.hex(), str(e))) self.log.info('Verify xmr bid {} failed: {}'.format(bid.bid_id.hex(), str(ex)))
bid.setState(BidStates.BID_ERROR, 'Failed validation: ' + str(e)) bid.setState(BidStates.BID_ERROR, 'Failed validation: ' + str(ex))
session.add(bid) session.add(bid)
continue continue
if bid.created_at + ttl_xmr_split_messages < now: if bid.created_at + ttl_xmr_split_messages < now:
@ -2476,9 +2501,9 @@ class BasicSwap(BaseApp):
if num_segments > 1: if num_segments > 1:
try: try:
self.receiveXmrBidAccept(bid, session) self.receiveXmrBidAccept(bid, session)
except Exception as e: except Exception as ex:
self.log.info('Verify xmr bid accept {} failed: {}'.format(bid.bid_id.hex(), str(e))) self.log.info('Verify xmr bid accept {} failed: {}'.format(bid.bid_id.hex(), str(ex)))
bid.setState(BidStates.BID_ERROR, 'Failed accept validation: ' + str(e)) bid.setState(BidStates.BID_ERROR, 'Failed accept validation: ' + str(ex))
session.add(bid) session.add(bid)
continue continue
if bid.created_at + ttl_xmr_split_messages < now: if bid.created_at + ttl_xmr_split_messages < now:
@ -2489,6 +2514,7 @@ class BasicSwap(BaseApp):
# Expire old records # Expire old records
q = session.query(XmrSplitData).filter(XmrSplitData.created_at + ttl_xmr_split_messages < now) q = session.query(XmrSplitData).filter(XmrSplitData.created_at + ttl_xmr_split_messages < now)
q.delete(synchronize_session=False) q.delete(synchronize_session=False)
session.commit() session.commit()
session.close() session.close()
session.remove() session.remove()
@ -2659,7 +2685,6 @@ class BasicSwap(BaseApp):
else: else:
delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept) delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept)
self.log.info('Auto accepting bid %s in %d seconds', bid_id.hex(), delay) self.log.info('Auto accepting bid %s in %d seconds', bid_id.hex(), delay)
self.createEvent(delay, EventTypes.ACCEPT_BID, bid_id) self.createEvent(delay, EventTypes.ACCEPT_BID, bid_id)
def processBidAccept(self, msg): def processBidAccept(self, msg):
@ -2760,6 +2785,14 @@ class BasicSwap(BaseApp):
if not ci_to.verifyDLEAG(xmr_swap.kbsf_dleag): if not ci_to.verifyDLEAG(xmr_swap.kbsf_dleag):
raise ValueError('Invalid DLEAG proof.') raise ValueError('Invalid DLEAG proof.')
# Extract pubkeys from MSG1L DLEAG
xmr_swap.pkasf = xmr_swap.kbsf_dleag[0: 33]
if not ci_from.verifyPubkey(xmr_swap.pkasf):
raise ValueError('Invalid coin a pubkey.')
xmr_swap.pkbsf = xmr_swap.kbsf_dleag[33: 33 + 32]
if not ci_to.verifyPubkey(xmr_swap.pkbsf):
raise ValueError('Invalid coin b pubkey.')
if not ci_from.verifyPubkey(xmr_swap.pkaf): if not ci_from.verifyPubkey(xmr_swap.pkaf):
raise ValueError('Invalid pubkey.') raise ValueError('Invalid pubkey.')
if not ci_from.verifyPubkey(xmr_swap.pkarf): if not ci_from.verifyPubkey(xmr_swap.pkarf):
@ -2794,14 +2827,27 @@ class BasicSwap(BaseApp):
if not ci_to.verifyDLEAG(xmr_swap.kbsl_dleag): if not ci_to.verifyDLEAG(xmr_swap.kbsl_dleag):
raise ValueError('Invalid DLEAG proof.') raise ValueError('Invalid DLEAG proof.')
# Extract pubkeys from MSG1F DLEAG
xmr_swap.pkasl = xmr_swap.kbsl_dleag[0: 33]
if not ci_from.verifyPubkey(xmr_swap.pkasl):
raise ValueError('Invalid coin a pubkey.')
xmr_swap.pkbsl = xmr_swap.kbsl_dleag[33: 33 + 32]
if not ci_to.verifyPubkey(xmr_swap.pkbsl):
raise ValueError('Invalid coin b pubkey.')
if not ci_from.verifyPubkey(xmr_swap.pkal): if not ci_from.verifyPubkey(xmr_swap.pkal):
raise ValueError('Invalid pubkey.') raise ValueError('Invalid pubkey.')
if not ci_from.verifyPubkey(xmr_swap.pkarl): if not ci_from.verifyPubkey(xmr_swap.pkarl):
raise ValueError('Invalid pubkey.') raise ValueError('Invalid pubkey.')
bid.setState(BidStates.BID_ACCEPTED) #bid.setState(BidStates.BID_ACCEPTED)
bid.setState(BidStates.SWAP_DELAYING)
self.saveBidInSession(bid.bid_id, bid, session, xmr_swap) self.saveBidInSession(bid.bid_id, bid, session, xmr_swap)
delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept)
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)
def processXmrBid(self, msg): def processXmrBid(self, msg):
self.log.debug('Processing xmr bid msg %s', msg['msgid']) self.log.debug('Processing xmr bid msg %s', msg['msgid'])
now = int(time.time()) now = int(time.time())
@ -2841,6 +2887,7 @@ class BasicSwap(BaseApp):
xmr_swap = XmrSwap( xmr_swap = XmrSwap(
bid_id=bid_id, bid_id=bid_id,
dest_af=bid_data.dest_af,
pkaf=bid_data.pkaf, pkaf=bid_data.pkaf,
pkarf=bid_data.pkarf, pkarf=bid_data.pkarf,
vkbvf=bid_data.kbvf, vkbvf=bid_data.kbvf,
@ -2858,7 +2905,7 @@ class BasicSwap(BaseApp):
self.saveBid(bid_id, bid, xmr_swap=xmr_swap) self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
def processXmrBidAccept(self, msg): def processXmrBidAccept(self, msg):
# F receiving MSG1F # F receiving MSG1F and MSG2F
self.log.debug('Processing xmr bid accept msg %s', msg['msgid']) self.log.debug('Processing xmr bid accept msg %s', msg['msgid'])
now = int(time.time()) now = int(time.time())
msg_bytes = bytes.fromhex(msg['hex'][2:-2]) msg_bytes = bytes.fromhex(msg['hex'][2:-2])
@ -2869,13 +2916,18 @@ class BasicSwap(BaseApp):
self.log.debug('for bid %s', msg_data.bid_msg_id.hex()) self.log.debug('for bid %s', msg_data.bid_msg_id.hex())
bid, xmr_swap = self.getXmrBid(msg_data.bid_msg_id) bid, xmr_swap = self.getXmrBid(msg_data.bid_msg_id)
assert(bid), 'Bid not found: {}.'.format(msg_data.bid_id.hex())
assert(xmr_swap), 'XMR swap not found: {}.'.format(msg_data.bid_id.hex())
offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=True) offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=True)
assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex()) assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex())
assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex()) assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex())
coin_from = Coins(offer.coin_from) coin_from = Coins(offer.coin_from)
coin_to = Coins(offer.coin_to) coin_to = Coins(offer.coin_to)
ci_from = self.ci(coin_from)
ci_to = self.ci(coin_to)
try:
assert(len(msg_data.sh) == 32), 'Bad secret hash length' assert(len(msg_data.sh) == 32), 'Bad secret hash length'
xmr_swap.sh = msg_data.sh xmr_swap.sh = msg_data.sh
@ -2884,8 +2936,172 @@ class BasicSwap(BaseApp):
xmr_swap.vkbvl = msg_data.kbvl xmr_swap.vkbvl = msg_data.kbvl
xmr_swap.kbsl_dleag = msg_data.kbsl_dleag xmr_swap.kbsl_dleag = msg_data.kbsl_dleag
xmr_swap.a_lock_tx = msg_data.a_lock_tx
xmr_swap.a_lock_tx_script = msg_data.a_lock_tx_script
xmr_swap.a_lock_refund_tx = msg_data.a_lock_refund_tx
xmr_swap.a_lock_refund_tx_script = msg_data.a_lock_refund_tx_script
xmr_swap.a_lock_refund_spend_tx = msg_data.a_lock_refund_spend_tx
xmr_swap.al_lock_refund_tx_sig = msg_data.al_lock_refund_tx_sig
check_a_lock_tx_inputs = True
xmr_swap.a_lock_tx_id, lock_tx_vout = ci_from.verifyLockTx(
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
bid.amount,
xmr_swap.sh,
xmr_swap.pkal, xmr_swap.pkaf,
xmr_offer.lock_time_1, xmr_offer.a_fee_rate,
xmr_swap.pkarl, xmr_swap.pkarf,
check_a_lock_tx_inputs
)
xmr_swap.a_lock_tx_dest = ci_from.getScriptDest(xmr_swap.a_lock_tx_script)
lock_refund_tx_id, xmr_swap.a_swap_refund_value = ci_from.verifyLockRefundTx(
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
xmr_swap.a_lock_tx_id, lock_tx_vout, xmr_offer.lock_time_1, xmr_swap.a_lock_tx_script,
xmr_swap.pkarl, xmr_swap.pkarf,
xmr_offer.lock_time_2,
xmr_swap.pkaf,
bid.amount, xmr_offer.a_fee_rate
)
ci_from.verifyLockRefundSpendTx(
xmr_swap.a_lock_refund_spend_tx,
lock_refund_tx_id, xmr_swap.a_lock_refund_tx_script,
xmr_swap.pkal,
xmr_swap.a_swap_refund_value, xmr_offer.a_fee_rate
)
logging.info('Checking leader\'s lock refund tx signature')
v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_tx, xmr_swap.al_lock_refund_tx_sig, xmr_swap.pkarl, 0, xmr_swap.a_lock_tx_script, bid.amount)
bid.setState(BidStates.BID_RECEIVING_ACC) bid.setState(BidStates.BID_RECEIVING_ACC)
self.saveBid(msg_data.bid_msg_id, bid, xmr_swap=xmr_swap) self.saveBid(bid.bid_id, bid, xmr_swap=xmr_swap)
except Exception as ex:
if self.debug:
traceback.print_exc()
self.setBidError(bid.bid_id, bid, str(ex))
def sendXmrBidTxnSigsFtoL(self, bid_id, session):
# F -> L: Sending MSG3L
self.log.debug('Signing xmr bid lock txns %s', bid_id.hex())
bid, xmr_swap = self.getXmrBid(bid_id)
assert(bid), 'Bid not found: {}.'.format(bid_id.hex())
assert(xmr_swap), 'XMR swap not found: {}.'.format(bid_id.hex())
offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False)
assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex())
assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex())
coin_from = Coins(offer.coin_from)
coin_to = Coins(offer.coin_to)
ci_from = self.ci(coin_from)
ci_to = self.ci(coin_to)
try:
karf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 4)
print('[rm] xmr_swap.a_swap_refund_value', xmr_swap.a_swap_refund_value)
xmr_swap.af_lock_refund_spend_tx_esig = ci_from.signTxOtVES(karf, xmr_swap.pkasl, xmr_swap.a_lock_refund_spend_tx, 0, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value)
xmr_swap.af_lock_refund_tx_sig = ci_from.signTx(karf, xmr_swap.a_lock_refund_tx, 0, xmr_swap.a_lock_tx_script, bid.amount)
msg_buf = XmrBidLockTxSigsMessage(
bid_msg_id=bid_id,
af_lock_refund_spend_tx_esig=xmr_swap.af_lock_refund_spend_tx_esig,
af_lock_refund_tx_sig=xmr_swap.af_lock_refund_tx_sig
)
mag_bytes = msg_buf.SerializeToString()
payload_hex = str.format('{:02x}', MessageTypes.XMR_BID_TXN_SIGS_FL) + mag_bytes.hex()
options = {'decodehex': True, 'ttl_is_seconds': True}
# TODO: set msg_valid based on bid / offer parameters
msg_valid = self.SMSG_SECONDS_IN_HOUR * 48
ro = self.callrpc('smsgsend', [bid.bid_addr, offer.addr_from, payload_hex, False, msg_valid, False, options])
xmr_swap.coin_a_lock_tx_sigs_l_id = bytes.fromhex(ro['msgid'])
self.log.info('Sent XMR_BID_TXN_SIGS_FL %s', xmr_swap.coin_a_lock_tx_sigs_l_id.hex())
bid.setState(BidStates.BID_ACCEPTED)
session.add(bid)
session.add(xmr_swap)
self.swaps_in_progress[bid_id] = (bid, offer)
except Exception as ex:
if self.debug:
traceback.print_exc()
def sendXmrBidCoinALockTx(self, bid_id, session):
# Send coin A lock tx and MSG4F L -> F
self.log.debug('Sending coin A lock tx for xmr bid %s', bid_id.hex())
bid, xmr_swap = self.getXmrBid(bid_id)
assert(bid), 'Bid not found: {}.'.format(bid_id.hex())
assert(xmr_swap), 'XMR swap not found: {}.'.format(bid_id.hex())
offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False)
assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex())
assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex())
coin_from = Coins(offer.coin_from)
coin_to = Coins(offer.coin_to)
ci_from = self.ci(coin_from)
ci_to = self.ci(coin_to)
kal = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 4)
xmr_swap.a_lock_spend_tx = ci_from.createScriptLockSpendTx(
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
xmr_swap.dest_af,
xmr_offer.a_fee_rate)
xmr_swap.al_lock_spend_tx_esig = ci_from.signTxOtVES(kal, xmr_swap.pkasf, xmr_swap.a_lock_spend_tx, 0, xmr_swap.a_lock_tx_script, bid.amount) # self.a_swap_value
# TODO: Separate MSG4F and txn sending
def processXmrBidCoinALockSigs(self, msg):
# Follower processing MSG3L
self.log.debug('Processing xmr coin a follower lock sigs msg %s', msg['msgid'])
now = int(time.time())
msg_bytes = bytes.fromhex(msg['hex'][2:-2])
msg_data = XmrBidLockTxSigsMessage()
msg_data.ParseFromString(msg_bytes)
assert(len(msg_data.bid_msg_id) == 28), 'Bad bid_msg_id length'
bid_id = msg_data.bid_msg_id
bid, xmr_swap = self.getXmrBid(bid_id)
assert(bid), 'Bid not found: {}.'.format(bid_id.hex())
assert(xmr_swap), 'XMR swap not found: {}.'.format(bid_id.hex())
offer, xmr_offer = self.getXmrOffer(bid.offer_id, sent=False)
assert(offer), 'Offer not found: {}.'.format(bid.offer_id.hex())
assert(xmr_offer), 'XMR offer not found: {}.'.format(bid.offer_id.hex())
coin_from = Coins(offer.coin_from)
coin_to = Coins(offer.coin_to)
ci_from = self.ci(coin_from)
ci_to = self.ci(coin_to)
try:
xmr_swap.af_lock_refund_spend_tx_esig = msg_data.af_lock_refund_spend_tx_esig
xmr_swap.af_lock_refund_tx_sig = msg_data.af_lock_refund_tx_sig
kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3, for_xmr=True)
xmr_swap.af_lock_refund_spend_tx_sig = ci_from.decryptOtVES(kbsl, xmr_swap.af_lock_refund_spend_tx_esig)
print('[rm] xmr_swap.a_swap_refund_value', xmr_swap.a_swap_refund_value)
v = ci_from.verifyTxSig(xmr_swap.a_lock_refund_spend_tx, xmr_swap.af_lock_refund_spend_tx_sig, xmr_swap.pkarf, 0, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value)
assert(v), 'Invalid signature for lock refund spend txn'
delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept)
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)
bid.setState(BidStates.SWAP_DELAYING)
self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
except Exception as ex:
if self.debug:
traceback.print_exc()
self.setBidError(bid_id, bid, str(ex))
def processXmrSplitMessage(self, msg): def processXmrSplitMessage(self, msg):
self.log.debug('Processing xmr split msg %s', msg['msgid']) self.log.debug('Processing xmr split msg %s', msg['msgid'])
@ -2895,7 +3111,6 @@ class BasicSwap(BaseApp):
msg_data.ParseFromString(msg_bytes) msg_data.ParseFromString(msg_bytes)
# Validate data # Validate data
print('[rm] msg_data.msg_id', msg_data.msg_id.hex())
assert(len(msg_data.msg_id) == 28), 'Bad msg_id length' assert(len(msg_data.msg_id) == 28), 'Bad msg_id length'
if msg_data.msg_type == XmrSplitMsgTypes.BID or msg_data.msg_type == XmrSplitMsgTypes.BID_ACCEPT: if msg_data.msg_type == XmrSplitMsgTypes.BID or msg_data.msg_type == XmrSplitMsgTypes.BID_ACCEPT:
@ -2923,11 +3138,14 @@ class BasicSwap(BaseApp):
self.processXmrBid(msg) self.processXmrBid(msg)
elif msg_type == MessageTypes.XMR_BID_ACCEPT: elif msg_type == MessageTypes.XMR_BID_ACCEPT:
self.processXmrBidAccept(msg) self.processXmrBidAccept(msg)
elif msg_type == MessageTypes.XMR_BID_TXN_SIGS_FL:
self.processXmrBidCoinALockSigs(msg)
elif msg_type == MessageTypes.XMR_BID_SPLIT: elif msg_type == MessageTypes.XMR_BID_SPLIT:
self.processXmrSplitMessage(msg) self.processXmrSplitMessage(msg)
except Exception as ex: except Exception as ex:
self.log.error('processMsg %s', str(ex)) self.log.error('processMsg %s', str(ex))
if self.debug:
traceback.print_exc() traceback.print_exc()
finally: finally:
self.mxDB.release() self.mxDB.release()
@ -2954,6 +3172,7 @@ class BasicSwap(BaseApp):
pass pass
except Exception as ex: except Exception as ex:
self.log.error('smsg zmq %s', str(ex)) self.log.error('smsg zmq %s', str(ex))
if self.debug:
traceback.print_exc() traceback.print_exc()
self.mxDB.acquire() self.mxDB.acquire()
@ -2968,6 +3187,7 @@ class BasicSwap(BaseApp):
to_remove.append(bid_id) to_remove.append(bid_id)
except Exception as ex: except Exception as ex:
self.log.error('checkBidState %s %s', bid_id.hex(), str(ex)) self.log.error('checkBidState %s %s', bid_id.hex(), str(ex))
if self.debug:
traceback.print_exc() traceback.print_exc()
self.setBidError(bid_id, v[0], str(ex)) self.setBidError(bid_id, v[0], str(ex))
@ -2996,6 +3216,7 @@ class BasicSwap(BaseApp):
except Exception as ex: except Exception as ex:
self.log.error('update %s', str(ex)) self.log.error('update %s', str(ex))
if self.debug:
traceback.print_exc() traceback.print_exc()
finally: finally:
self.mxDB.release() self.mxDB.release()

View file

@ -6,8 +6,6 @@
import os import os
DEBUG = True
CONFIG_FILENAME = 'basicswap.json' CONFIG_FILENAME = 'basicswap.json'
DEFAULT_DATADIR = '~/.basicswap' DEFAULT_DATADIR = '~/.basicswap'
TEST_DATADIRS = os.path.expanduser(os.getenv('DATADIRS', '/tmp/basicswap')) TEST_DATADIRS = os.path.expanduser(os.getenv('DATADIRS', '/tmp/basicswap'))

View file

@ -228,15 +228,21 @@ class XmrSwap(Base):
bid_accept_msg_id2 = sa.Column(sa.LargeBinary) bid_accept_msg_id2 = sa.Column(sa.LargeBinary)
bid_accept_msg_id3 = sa.Column(sa.LargeBinary) bid_accept_msg_id3 = sa.Column(sa.LargeBinary)
coin_a_lock_tx_sigs_l_id = sa.Column(sa.LargeBinary) # MSG3L F -> L
contract_count = sa.Column(sa.Integer) contract_count = sa.Column(sa.Integer)
sh = sa.Column(sa.LargeBinary) # Secret hash sh = sa.Column(sa.LargeBinary) # Secret hash
dest_af = sa.Column(sa.LargeBinary) # Destination for coin A amount to follower when swap completes successfully
pkal = sa.Column(sa.LargeBinary) pkal = sa.Column(sa.LargeBinary)
pkarl = sa.Column(sa.LargeBinary) pkarl = sa.Column(sa.LargeBinary)
pkasl = sa.Column(sa.LargeBinary)
pkaf = sa.Column(sa.LargeBinary) pkaf = sa.Column(sa.LargeBinary)
pkarf = sa.Column(sa.LargeBinary) pkarf = sa.Column(sa.LargeBinary)
pkasf = sa.Column(sa.LargeBinary)
vkbvl = sa.Column(sa.LargeBinary) vkbvl = sa.Column(sa.LargeBinary)
vkbsl = sa.Column(sa.LargeBinary) vkbsl = sa.Column(sa.LargeBinary)
@ -260,6 +266,13 @@ class XmrSwap(Base):
a_lock_refund_spend_tx = sa.Column(sa.LargeBinary) a_lock_refund_spend_tx = sa.Column(sa.LargeBinary)
af_lock_refund_spend_tx_esig = sa.Column(sa.LargeBinary)
af_lock_refund_spend_tx_sig = sa.Column(sa.LargeBinary)
af_lock_refund_tx_sig = sa.Column(sa.LargeBinary)
a_lock_spend_tx = sa.Column(sa.LargeBinary)
al_lock_spend_tx_esig = sa.Column(sa.LargeBinary)
b_restore_height = sa.Column(sa.Integer) # Height of xmr chain before the swap b_restore_height = sa.Column(sa.Integer) # Height of xmr chain before the swap

View file

@ -9,18 +9,25 @@ import time
import hashlib import hashlib
import logging import logging
from io import BytesIO from io import BytesIO
from basicswap.contrib.test_framework import segwit_addr
from .util import ( from .util import (
decodeScriptNum, decodeScriptNum,
getCompactSizeLen, getCompactSizeLen,
dumpj, dumpj,
format_amount, format_amount,
make_int make_int,
) decodeAddress)
from coincurve.keys import ( from coincurve.keys import (
PublicKey) PublicKey)
from coincurve.dleag import ( from coincurve.dleag import (
verify_secp256k1_point) verify_secp256k1_point)
from coincurve.ecdsaotves import (
ecdsaotves_enc_sign,
ecdsaotves_enc_verify,
ecdsaotves_dec_sig,
ecdsaotves_rec_enc_key)
from .ecc_util import ( from .ecc_util import (
G, ep, G, ep,
@ -59,7 +66,7 @@ from .contrib.test_framework.script import (
from .contrib.test_framework.key import ECKey, ECPubKey from .contrib.test_framework.key import ECKey, ECPubKey
from .chainparams import CoinInterface from .chainparams import CoinInterface, Coins, chainparams
from .rpc import make_rpc_func from .rpc import make_rpc_func
from .util import assert_cond from .util import assert_cond
@ -72,6 +79,10 @@ def findOutput(tx, script_pk):
class BTCInterface(CoinInterface): class BTCInterface(CoinInterface):
@staticmethod
def coin_type():
return Coins.BTC
@staticmethod @staticmethod
def exp(): def exp():
return 8 return 8
@ -102,9 +113,10 @@ class BTCInterface(CoinInterface):
def compareFeeRates(self, a, b): def compareFeeRates(self, a, b):
return abs(a - b) < 20 return abs(a - b) < 20
def __init__(self, coin_settings): def __init__(self, coin_settings, network):
self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth']) self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth'])
self.txoType = CTxOut self.txoType = CTxOut
self._network = network
def testDaemonRPC(self): def testDaemonRPC(self):
self.rpc_callback('getwalletinfo', []) self.rpc_callback('getwalletinfo', [])
@ -124,6 +136,13 @@ class BTCInterface(CoinInterface):
args.append('bech32') args.append('bech32')
return self.rpc_callback('getnewaddress', args) return self.rpc_callback('getnewaddress', args)
def decodeAddress(self, address):
bech32_prefix = chainparams[self.coin_type()][self._network]['hrp']
if address.startswith(bech32_prefix):
ignr, pkhash = segwit_addr.decode(bech32_prefix, address)
return pkhash
return decodeAddress(address)[1:]
def getNewSecretKey(self): def getNewSecretKey(self):
return getSecretInt() return getSecretInt()
@ -379,8 +398,8 @@ class BTCInterface(CoinInterface):
return tx.serialize() return tx.serialize()
def createScriptLockSpendTx(self, tx_lock, script_lock, pkh_dest, tx_fee_rate): def createScriptLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate):
tx_lock = self.loadTx(tx_lock_bytes)
output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()]) output_script = CScript([OP_0, hashlib.sha256(script_lock).digest()])
locked_n = findOutput(tx_lock, output_script) locked_n = findOutput(tx_lock, output_script)
assert_cond(locked_n is not None, 'Output not found in tx') assert_cond(locked_n is not None, 'Output not found in tx')
@ -411,7 +430,7 @@ class BTCInterface(CoinInterface):
return tx.serialize() return tx.serialize()
def verifyLockTx(self, tx, script_out, def verifyLockTx(self, tx_bytes, script_out,
swap_value, swap_value,
sh, sh,
Kal, Kaf, Kal, Kaf,
@ -425,6 +444,7 @@ class BTCInterface(CoinInterface):
# However by checking early we can avoid wasting time processing unmineable txns # However by checking early we can avoid wasting time processing unmineable txns
# Check fee is reasonable # Check fee is reasonable
tx = self.loadTx(tx_bytes)
tx_hash = self.getTxHash(tx) tx_hash = self.getTxHash(tx)
logging.info('Verifying lock tx: {}.'.format(b2h(tx_hash))) logging.info('Verifying lock tx: {}.'.format(b2h(tx_hash)))
@ -441,11 +461,11 @@ class BTCInterface(CoinInterface):
# Check script and values # Check script and values
shv, A, B, csv_val, C, D = self.extractScriptLockScriptValues(script_out) shv, A, B, csv_val, C, D = self.extractScriptLockScriptValues(script_out)
assert_cond(shv == sh, 'Bad hash lock') assert_cond(shv == sh, 'Bad hash lock')
assert_cond(A == self.encodePubkey(Kal), 'Bad script pubkey') assert_cond(A == Kal, 'Bad script pubkey')
assert_cond(B == self.encodePubkey(Kaf), 'Bad script pubkey') assert_cond(B == Kaf, 'Bad script pubkey')
assert_cond(csv_val == lock_value, 'Bad script csv value') assert_cond(csv_val == lock_value, 'Bad script csv value')
assert_cond(C == self.encodePubkey(Karl), 'Bad script pubkey') assert_cond(C == Karl, 'Bad script pubkey')
assert_cond(D == self.encodePubkey(Karf), 'Bad script pubkey') assert_cond(D == Karf, 'Bad script pubkey')
if check_lock_tx_inputs: if check_lock_tx_inputs:
# Check that inputs are unspent and verify fee rate # Check that inputs are unspent and verify fee rate
@ -454,7 +474,6 @@ class BTCInterface(CoinInterface):
add_witness_bytes = getCompactSizeLen(len(tx.vin)) add_witness_bytes = getCompactSizeLen(len(tx.vin))
for pi in tx.vin: for pi in tx.vin:
ptx = self.rpc_callback('getrawtransaction', [i2h(pi.prevout.hash), True]) ptx = self.rpc_callback('getrawtransaction', [i2h(pi.prevout.hash), True])
print('ptx', dumpj(ptx))
prevout = ptx['vout'][pi.prevout.n] prevout = ptx['vout'][pi.prevout.n]
inputs_value += make_int(prevout['value']) inputs_value += make_int(prevout['value'])
@ -483,7 +502,7 @@ class BTCInterface(CoinInterface):
return tx_hash, locked_n return tx_hash, locked_n
def verifyLockRefundTx(self, tx, script_out, def verifyLockRefundTx(self, tx_bytes, script_out,
prevout_id, prevout_n, prevout_seq, prevout_script, prevout_id, prevout_n, prevout_seq, prevout_script,
Karl, Karf, csv_val_expect, Kaf, swap_value, feerate): Karl, Karf, csv_val_expect, Kaf, swap_value, feerate):
# Verify: # Verify:
@ -491,6 +510,7 @@ class BTCInterface(CoinInterface):
# Must have only one output to the p2wsh of the lock refund script # Must have only one output to the p2wsh of the lock refund script
# Output value must be locked_coin - lock tx fee # Output value must be locked_coin - lock tx fee
tx = self.loadTx(tx_bytes)
tx_hash = self.getTxHash(tx) tx_hash = self.getTxHash(tx)
logging.info('Verifying lock refund tx: {}.'.format(b2h(tx_hash))) logging.info('Verifying lock refund tx: {}.'.format(b2h(tx_hash)))
@ -511,10 +531,10 @@ class BTCInterface(CoinInterface):
# Check script and values # Check script and values
A, B, csv_val, C = self.extractScriptLockRefundScriptValues(script_out) A, B, csv_val, C = self.extractScriptLockRefundScriptValues(script_out)
assert_cond(A == self.encodePubkey(Karl), 'Bad script pubkey') assert_cond(A == Karl, 'Bad script pubkey')
assert_cond(B == self.encodePubkey(Karf), 'Bad script pubkey') assert_cond(B == Karf, 'Bad script pubkey')
assert_cond(csv_val == csv_val_expect, 'Bad script csv value') assert_cond(csv_val == csv_val_expect, 'Bad script csv value')
assert_cond(C == self.encodePubkey(Kaf), 'Bad script pubkey') assert_cond(C == Kaf, 'Bad script pubkey')
fee_paid = swap_value - locked_coin fee_paid = swap_value - locked_coin
assert(fee_paid > 0) assert(fee_paid > 0)
@ -533,13 +553,14 @@ class BTCInterface(CoinInterface):
return tx_hash, locked_coin return tx_hash, locked_coin
def verifyLockRefundSpendTx(self, tx, def verifyLockRefundSpendTx(self, tx_bytes,
lock_refund_tx_id, prevout_script, lock_refund_tx_id, prevout_script,
Kal, Kal,
prevout_value, feerate): prevout_value, feerate):
# Verify: # Verify:
# Must have only one input with correct prevout (n is always 0) and sequence # Must have only one input with correct prevout (n is always 0) and sequence
# Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr # Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr
tx = self.loadTx(tx_bytes)
tx_hash = self.getTxHash(tx) tx_hash = self.getTxHash(tx)
logging.info('Verifying lock refund spend tx: {}.'.format(b2h(tx_hash))) logging.info('Verifying lock refund spend tx: {}.'.format(b2h(tx_hash)))
@ -553,7 +574,7 @@ class BTCInterface(CoinInterface):
assert_cond(len(tx.vout) == 1, 'tx doesn\'t have one output') assert_cond(len(tx.vout) == 1, 'tx doesn\'t have one output')
p2wpkh = CScript([OP_0, hash160(self.encodePubkey(Kal))]) p2wpkh = CScript([OP_0, hash160(Kal)])
locked_n = findOutput(tx, p2wpkh) locked_n = findOutput(tx, p2wpkh)
assert_cond(locked_n is not None, 'Output not found in lock refund spend tx') assert_cond(locked_n is not None, 'Output not found in lock refund spend tx')
tx_value = tx.vout[locked_n].nValue tx_value = tx.vout[locked_n].nValue
@ -632,16 +653,22 @@ class BTCInterface(CoinInterface):
return eck.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL return eck.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL
def signTxOtVES(self, key_sign, key_encrypt, tx, prevout_n, prevout_script, prevout_value): def signTxOtVES(self, key_sign, pubkey_encrypt, tx_bytes, prevout_n, prevout_script, prevout_value):
tx = self.loadTx(tx_bytes)
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value) sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
return otves.EncSign(key_sign, key_encrypt, sig_hash)
def verifyTxOtVES(self, tx, sig, Ks, Ke, prevout_n, prevout_script, prevout_value): return ecdsaotves_enc_sign(key_sign, pubkey_encrypt, sig_hash)
#return otves.EncSign(key_sign, key_encrypt, sig_hash)
def verifyTxOtVES(self, tx_bytes, sig, Ks, Ke, prevout_n, prevout_script, prevout_value):
tx = self.loadTx(tx_bytes)
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value) sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
return otves.EncVrfy(Ks, Ke, sig_hash, sig) return ecdsaotves_enc_verify(Ks, Ke, sig_hash, sig)
#return otves.EncVrfy(Ks, Ke, sig_hash, sig)
def decryptOtVES(self, k, esig): def decryptOtVES(self, k, esig):
return otves.DecSig(k, esig) + b'\x01' # 0x1 is SIGHASH_ALL return ecdsaotves_dec_sig(k, esig) + b'\x01' # 0x1 is SIGHASH_ALL
#return otves.DecSig(k, esig) + b'\x01' # 0x1 is SIGHASH_ALL
def verifyTxSig(self, tx_bytes, sig, K, prevout_n, prevout_script, prevout_value): def verifyTxSig(self, tx_bytes, sig, K, prevout_n, prevout_script, prevout_value):
tx = self.loadTx(tx_bytes) tx = self.loadTx(tx_bytes)
@ -675,9 +702,9 @@ class BTCInterface(CoinInterface):
tx.deserialize(BytesIO(tx_bytes)) tx.deserialize(BytesIO(tx_bytes))
return tx return tx
def getTxHash(self, tx_bytes): def getTxHash(self, tx):
tx = CTransaction() if isinstance(tx, bytes):
tx = FromHex(tx, tx_bytes.hex()) tx = self.loadTx(tx)
tx.rehash() tx.rehash()
return i2b(tx.sha256) return i2b(tx.sha256)

View file

@ -6,7 +6,9 @@
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from .interface_btc import BTCInterface from .interface_btc import BTCInterface
from .chainparams import Coins
class LTCInterface(BTCInterface): class LTCInterface(BTCInterface):
pass @staticmethod
def coin_type():
return Coins.LTC

View file

@ -6,7 +6,9 @@
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from .interface_btc import BTCInterface from .interface_btc import BTCInterface
from .chainparams import Coins
class NMCInterface(BTCInterface): class NMCInterface(BTCInterface):
pass @staticmethod
def coin_type():
return Coins.NMC

View file

@ -10,11 +10,15 @@ from .contrib.test_framework.messages import (
) )
from .interface_btc import BTCInterface from .interface_btc import BTCInterface
from .chainparams import CoinInterface from .chainparams import CoinInterface, Coins
from .rpc import make_rpc_func from .rpc import make_rpc_func
class PARTInterface(BTCInterface): class PARTInterface(BTCInterface):
@staticmethod
def coin_type():
return Coins.PART
@staticmethod @staticmethod
def witnessScaleFactor(): def witnessScaleFactor():
return 2 return 2
@ -23,9 +27,10 @@ class PARTInterface(BTCInterface):
def txVersion(): def txVersion():
return 0xa0 return 0xa0
def __init__(self, coin_settings): def __init__(self, coin_settings, network):
self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth']) self.rpc_callback = make_rpc_func(coin_settings['rpcport'], coin_settings['rpcauth'])
self.txoType = CTxOutPart self.txoType = CTxOutPart
self._network = network
def getNewAddress(self, use_segwit): def getNewAddress(self, use_segwit):
return self.rpc_callback('getnewaddress', ['swap_receive']) return self.rpc_callback('getnewaddress', ['swap_receive'])

View file

@ -25,12 +25,16 @@ from .rpc_xmr import (
make_xmr_wallet_rpc_func) make_xmr_wallet_rpc_func)
from .ecc_util import ( from .ecc_util import (
b2i) b2i)
from .chainparams import CoinInterface from .chainparams import CoinInterface, Coins
XMR_COIN = 10 ** 12 XMR_COIN = 10 ** 12
class XMRInterface(CoinInterface): class XMRInterface(CoinInterface):
@staticmethod
def coin_type():
return Coins.XMR
@staticmethod @staticmethod
def exp(): def exp():
return 12 return 12
@ -43,12 +47,13 @@ class XMRInterface(CoinInterface):
def nbK(): # No. of bytes requires to encode a public key def nbK(): # No. of bytes requires to encode a public key
return 32 return 32
def __init__(self, coin_settings): def __init__(self, coin_settings, network):
rpc_cb = make_xmr_rpc_func(coin_settings['rpcport']) rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'])
rpc_wallet_cb = make_xmr_wallet_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth']) rpc_wallet_cb = make_xmr_wallet_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'])
self.rpc_cb = rpc_cb self.rpc_cb = rpc_cb
self.rpc_wallet_cb = rpc_wallet_cb self.rpc_wallet_cb = rpc_wallet_cb
self._network = network
def testDaemonRPC(self): def testDaemonRPC(self):
self.rpc_wallet_cb('get_languages') self.rpc_wallet_cb('get_languages')

View file

@ -51,9 +51,8 @@ message BidAcceptMessage {
} }
/* Step 2, buyer -> seller */
message XmrBidMessage { message XmrBidMessage {
/* MSG1L, F -> L */
bytes offer_msg_id = 1; bytes offer_msg_id = 1;
uint64 time_valid = 2; /* seconds bid is valid for */ uint64 time_valid = 2; /* seconds bid is valid for */
uint64 amount = 3; /* amount of amount_from bid is for */ uint64 amount = 3; /* amount of amount_from bid is for */
@ -63,6 +62,8 @@ message XmrBidMessage {
bytes kbvf = 6; bytes kbvf = 6;
bytes kbsf_dleag = 7; bytes kbsf_dleag = 7;
bytes dest_af = 8;
} }
message XmrSplitMessage { message XmrSplitMessage {
@ -72,7 +73,6 @@ message XmrSplitMessage {
bytes dleag = 4; bytes dleag = 4;
} }
message XmrBidAcceptMessage { message XmrBidAcceptMessage {
bytes bid_msg_id = 1; bytes bid_msg_id = 1;
@ -92,3 +92,20 @@ message XmrBidAcceptMessage {
bytes al_lock_refund_tx_sig = 12; bytes al_lock_refund_tx_sig = 12;
} }
message XmrBidLockTxSigsMessage {
/* MSG3L */
bytes bid_msg_id = 1;
bytes af_lock_refund_spend_tx_esig = 2;
bytes af_lock_refund_tx_sig = 3;
}
message XmrBidLockSpendTxMessage {
/* MSG4F */
bytes bid_msg_id = 1;
bytes a_lock_spend_tx = 2;
bytes al_lock_spend_tx_esig = 3;
}

View file

@ -19,7 +19,7 @@ DESCRIPTOR = _descriptor.FileDescriptor(
syntax='proto3', syntax='proto3',
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, 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\"\x88\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\"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\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\"\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\x62\x06proto3'
) )
@ -366,6 +366,13 @@ _XMRBIDMESSAGE = _descriptor.Descriptor(
message_type=None, enum_type=None, containing_type=None, message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None, is_extension=False, extension_scope=None,
serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key), serialized_options=None, file=DESCRIPTOR, create_key=_descriptor._internal_create_key),
_descriptor.FieldDescriptor(
name='dest_af', full_name='basicswap.XmrBidMessage.dest_af', index=7,
number=8, 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=[ extensions=[
], ],
@ -379,7 +386,7 @@ _XMRBIDMESSAGE = _descriptor.Descriptor(
oneofs=[ oneofs=[
], ],
serialized_start=736, serialized_start=736,
serialized_end=872, serialized_end=889,
) )
@ -431,8 +438,8 @@ _XMRSPLITMESSAGE = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=874, serialized_start=891,
serialized_end=958, serialized_end=975,
) )
@ -540,8 +547,100 @@ _XMRBIDACCEPTMESSAGE = _descriptor.Descriptor(
extension_ranges=[], extension_ranges=[],
oneofs=[ oneofs=[
], ],
serialized_start=961, serialized_start=978,
serialized_end=1244, serialized_end=1261,
)
_XMRBIDLOCKTXSIGSMESSAGE = _descriptor.Descriptor(
name='XmrBidLockTxSigsMessage',
full_name='basicswap.XmrBidLockTxSigsMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='bid_msg_id', full_name='basicswap.XmrBidLockTxSigsMessage.bid_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='af_lock_refund_spend_tx_esig', full_name='basicswap.XmrBidLockTxSigsMessage.af_lock_refund_spend_tx_esig', 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),
_descriptor.FieldDescriptor(
name='af_lock_refund_tx_sig', full_name='basicswap.XmrBidLockTxSigsMessage.af_lock_refund_tx_sig', index=2,
number=3, 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=1263,
serialized_end=1377,
)
_XMRBIDLOCKSPENDTXMESSAGE = _descriptor.Descriptor(
name='XmrBidLockSpendTxMessage',
full_name='basicswap.XmrBidLockSpendTxMessage',
filename=None,
file=DESCRIPTOR,
containing_type=None,
create_key=_descriptor._internal_create_key,
fields=[
_descriptor.FieldDescriptor(
name='bid_msg_id', full_name='basicswap.XmrBidLockSpendTxMessage.bid_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='a_lock_spend_tx', full_name='basicswap.XmrBidLockSpendTxMessage.a_lock_spend_tx', 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),
_descriptor.FieldDescriptor(
name='al_lock_spend_tx_esig', full_name='basicswap.XmrBidLockSpendTxMessage.al_lock_spend_tx_esig', index=2,
number=3, 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=1379,
serialized_end=1481,
) )
_OFFERMESSAGE.fields_by_name['lock_type'].enum_type = _OFFERMESSAGE_LOCKTYPE _OFFERMESSAGE.fields_by_name['lock_type'].enum_type = _OFFERMESSAGE_LOCKTYPE
@ -552,6 +651,8 @@ DESCRIPTOR.message_types_by_name['BidAcceptMessage'] = _BIDACCEPTMESSAGE
DESCRIPTOR.message_types_by_name['XmrBidMessage'] = _XMRBIDMESSAGE DESCRIPTOR.message_types_by_name['XmrBidMessage'] = _XMRBIDMESSAGE
DESCRIPTOR.message_types_by_name['XmrSplitMessage'] = _XMRSPLITMESSAGE DESCRIPTOR.message_types_by_name['XmrSplitMessage'] = _XMRSPLITMESSAGE
DESCRIPTOR.message_types_by_name['XmrBidAcceptMessage'] = _XMRBIDACCEPTMESSAGE DESCRIPTOR.message_types_by_name['XmrBidAcceptMessage'] = _XMRBIDACCEPTMESSAGE
DESCRIPTOR.message_types_by_name['XmrBidLockTxSigsMessage'] = _XMRBIDLOCKTXSIGSMESSAGE
DESCRIPTOR.message_types_by_name['XmrBidLockSpendTxMessage'] = _XMRBIDLOCKSPENDTXMESSAGE
_sym_db.RegisterFileDescriptor(DESCRIPTOR) _sym_db.RegisterFileDescriptor(DESCRIPTOR)
OfferMessage = _reflection.GeneratedProtocolMessageType('OfferMessage', (_message.Message,), { OfferMessage = _reflection.GeneratedProtocolMessageType('OfferMessage', (_message.Message,), {
@ -596,5 +697,19 @@ XmrBidAcceptMessage = _reflection.GeneratedProtocolMessageType('XmrBidAcceptMess
}) })
_sym_db.RegisterMessage(XmrBidAcceptMessage) _sym_db.RegisterMessage(XmrBidAcceptMessage)
XmrBidLockTxSigsMessage = _reflection.GeneratedProtocolMessageType('XmrBidLockTxSigsMessage', (_message.Message,), {
'DESCRIPTOR' : _XMRBIDLOCKTXSIGSMESSAGE,
'__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.XmrBidLockTxSigsMessage)
})
_sym_db.RegisterMessage(XmrBidLockTxSigsMessage)
XmrBidLockSpendTxMessage = _reflection.GeneratedProtocolMessageType('XmrBidLockSpendTxMessage', (_message.Message,), {
'DESCRIPTOR' : _XMRBIDLOCKSPENDTXMESSAGE,
'__module__' : 'messages_pb2'
# @@protoc_insertion_point(class_scope:basicswap.XmrBidLockSpendTxMessage)
})
_sym_db.RegisterMessage(XmrBidLockSpendTxMessage)
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View file

@ -135,6 +135,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
btcdatadir = os.path.join(datadir, str(BTC_NODE)) btcdatadir = os.path.join(datadir, str(BTC_NODE))
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME) settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
settings = { settings = {
'debug': True,
'zmqhost': 'tcp://127.0.0.1', 'zmqhost': 'tcp://127.0.0.1',
'zmqport': BASE_ZMQ_PORT + nodeId, 'zmqport': BASE_ZMQ_PORT + nodeId,
'htmlhost': 'localhost', 'htmlhost': 'localhost',

View file

@ -139,6 +139,7 @@ def prepareDir(datadir, nodeId, network_key, network_pubkey):
btcdatadir = os.path.join(datadir, str(BTC_NODE)) btcdatadir = os.path.join(datadir, str(BTC_NODE))
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME) settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
settings = { settings = {
'debug': True,
'zmqhost': 'tcp://127.0.0.1', 'zmqhost': 'tcp://127.0.0.1',
'zmqport': BASE_ZMQ_PORT + nodeId, 'zmqport': BASE_ZMQ_PORT + nodeId,
'htmlhost': 'localhost', 'htmlhost': 'localhost',

View file

@ -198,6 +198,7 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey):
settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME) settings_path = os.path.join(basicswap_dir, cfg.CONFIG_FILENAME)
settings = { settings = {
'debug': True,
'zmqhost': 'tcp://127.0.0.1', 'zmqhost': 'tcp://127.0.0.1',
'zmqport': BASE_ZMQ_PORT + node_id, 'zmqport': BASE_ZMQ_PORT + node_id,
'htmlhost': 'localhost', 'htmlhost': 'localhost',
@ -513,9 +514,9 @@ class Test(unittest.TestCase):
return return
raise ValueError('wait_for_offer timed out.') raise ValueError('wait_for_offer timed out.')
def wait_for_bid(self, swap_client, bid_id, state=None, sent=False): 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()) logging.info('wait_for_bid %s', bid_id.hex())
for i in range(20): for i in range(wait_for):
time.sleep(1) time.sleep(1)
bids = swap_client.listBids(sent=sent) bids = swap_client.listBids(sent=sent)
for bid in bids: for bid in bids:
@ -551,7 +552,11 @@ class Test(unittest.TestCase):
swap_clients[0].acceptXmrBid(bid_id) swap_clients[0].acceptXmrBid(bid_id)
self.wait_for_bid(swap_clients[1], bid_id, BidStates.BID_ACCEPTED, sent=True) self.wait_for_bid(swap_clients[1], bid_id, BidStates.BID_ACCEPTED, sent=True, wait_for=40)
self.wait_for_bid(swap_clients[0], bid_id, BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED, sent=True)