PART -> XMR tests pass.
This commit is contained in:
parent
0e2011e085
commit
bc60527940
@ -134,6 +134,10 @@ class BidStates(IntEnum):
|
|||||||
XMR_SWAP_SECRET_SHARED = auto()
|
XMR_SWAP_SECRET_SHARED = auto()
|
||||||
XMR_SWAP_SCRIPT_TX_REDEEMED = auto()
|
XMR_SWAP_SCRIPT_TX_REDEEMED = auto()
|
||||||
XMR_SWAP_NOSCRIPT_TX_REDEEMED = auto()
|
XMR_SWAP_NOSCRIPT_TX_REDEEMED = auto()
|
||||||
|
XMR_SWAP_NOSCRIPT_TX_RECOVERED = auto()
|
||||||
|
XMR_SWAP_FAILED_REFUNDED = auto()
|
||||||
|
XMR_SWAP_FAILED_SWIPED = auto()
|
||||||
|
XMR_SWAP_FAILED = auto()
|
||||||
SWAP_DELAYING = 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
|
||||||
@ -160,6 +164,7 @@ class TxTypes(IntEnum):
|
|||||||
XMR_SWAP_A_LOCK_SPEND = auto()
|
XMR_SWAP_A_LOCK_SPEND = auto()
|
||||||
XMR_SWAP_A_LOCK_REFUND = auto()
|
XMR_SWAP_A_LOCK_REFUND = auto()
|
||||||
XMR_SWAP_A_LOCK_REFUND_SPEND = auto()
|
XMR_SWAP_A_LOCK_REFUND_SPEND = auto()
|
||||||
|
XMR_SWAP_A_LOCK_REFUND_SWIPE = auto()
|
||||||
XMR_SWAP_B_LOCK = auto()
|
XMR_SWAP_B_LOCK = auto()
|
||||||
|
|
||||||
|
|
||||||
@ -171,6 +176,7 @@ class EventTypes(IntEnum):
|
|||||||
SEND_XMR_SECRET = auto()
|
SEND_XMR_SECRET = auto()
|
||||||
REDEEM_XMR_SWAP_LOCK_TX_A = auto() # Follower
|
REDEEM_XMR_SWAP_LOCK_TX_A = auto() # Follower
|
||||||
REDEEM_XMR_SWAP_LOCK_TX_B = auto() # Leader
|
REDEEM_XMR_SWAP_LOCK_TX_B = auto() # Leader
|
||||||
|
RECOVER_XMR_SWAP_LOCK_TX_B = auto()
|
||||||
|
|
||||||
|
|
||||||
class XmrSplitMsgTypes(IntEnum):
|
class XmrSplitMsgTypes(IntEnum):
|
||||||
@ -178,6 +184,12 @@ class XmrSplitMsgTypes(IntEnum):
|
|||||||
BID_ACCEPT = auto()
|
BID_ACCEPT = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class DebugTypes(IntEnum):
|
||||||
|
BID_STOP_AFTER_COIN_A_LOCK = auto()
|
||||||
|
BID_DONT_SPEND_COIN_A_LOCK_REFUND = auto()
|
||||||
|
CREATE_INVALID_COIN_B_LOCK = auto()
|
||||||
|
|
||||||
|
|
||||||
SEQUENCE_LOCK_BLOCKS = 1
|
SEQUENCE_LOCK_BLOCKS = 1
|
||||||
SEQUENCE_LOCK_TIME = 2
|
SEQUENCE_LOCK_TIME = 2
|
||||||
ABS_LOCK_BLOCKS = 3
|
ABS_LOCK_BLOCKS = 3
|
||||||
@ -374,7 +386,7 @@ class BasicSwap(BaseApp):
|
|||||||
|
|
||||||
self.sqlite_file = os.path.join(self.data_dir, 'db{}.sqlite'.format('' if self.chain == 'mainnet' else ('_' + self.chain)))
|
self.sqlite_file = os.path.join(self.data_dir, 'db{}.sqlite'.format('' if self.chain == 'mainnet' else ('_' + self.chain)))
|
||||||
db_exists = os.path.exists(self.sqlite_file)
|
db_exists = os.path.exists(self.sqlite_file)
|
||||||
self.engine = sa.create_engine('sqlite:///' + self.sqlite_file)
|
self.engine = sa.create_engine('sqlite:///' + self.sqlite_file) # , echo=True
|
||||||
if not db_exists:
|
if not db_exists:
|
||||||
Base.metadata.create_all(self.engine)
|
Base.metadata.create_all(self.engine)
|
||||||
self.session_factory = sessionmaker(bind=self.engine, expire_on_commit=False)
|
self.session_factory = sessionmaker(bind=self.engine, expire_on_commit=False)
|
||||||
@ -803,8 +815,11 @@ class BasicSwap(BaseApp):
|
|||||||
if swap_type == SwapTypes.XMR_SWAP:
|
if swap_type == SwapTypes.XMR_SWAP:
|
||||||
xmr_offer = XmrOffer()
|
xmr_offer = XmrOffer()
|
||||||
|
|
||||||
xmr_offer.lock_time_1 = lock_value # Delay before the chain a lock refund tx can be mined
|
# Delay before the chain a lock refund tx can be mined
|
||||||
xmr_offer.lock_time_2 = lock_value # Delay before the follower can spend from the chain a lock refund tx
|
xmr_offer.lock_time_1 = getExpectedSequence(lock_type, lock_value, coin_from)
|
||||||
|
|
||||||
|
# Delay before the follower can spend from the chain a lock refund tx
|
||||||
|
xmr_offer.lock_time_2 = getExpectedSequence(lock_type, lock_value, coin_from)
|
||||||
|
|
||||||
# TODO: Dynamic fee selection
|
# TODO: Dynamic fee selection
|
||||||
xmr_offer.a_fee_rate = make_int(0.00032595, self.ci(coin_from).exp())
|
xmr_offer.a_fee_rate = make_int(0.00032595, self.ci(coin_from).exp())
|
||||||
@ -1094,10 +1109,12 @@ class BasicSwap(BaseApp):
|
|||||||
session.add(bid.participate_tx)
|
session.add(bid.participate_tx)
|
||||||
if bid.xmr_a_lock_tx:
|
if bid.xmr_a_lock_tx:
|
||||||
session.add(bid.xmr_a_lock_tx)
|
session.add(bid.xmr_a_lock_tx)
|
||||||
if bid.xmr_b_lock_tx:
|
|
||||||
session.add(bid.xmr_b_lock_tx)
|
|
||||||
if bid.xmr_a_lock_spend_tx:
|
if bid.xmr_a_lock_spend_tx:
|
||||||
session.add(bid.xmr_a_lock_spend_tx)
|
session.add(bid.xmr_a_lock_spend_tx)
|
||||||
|
if bid.xmr_b_lock_tx:
|
||||||
|
session.add(bid.xmr_b_lock_tx)
|
||||||
|
for tx_type, tx in bid.txns.items():
|
||||||
|
session.add(tx)
|
||||||
if xmr_swap is not None:
|
if xmr_swap is not None:
|
||||||
session.add(xmr_swap)
|
session.add(xmr_swap)
|
||||||
|
|
||||||
@ -1231,6 +1248,7 @@ class BasicSwap(BaseApp):
|
|||||||
self.mxDB.release()
|
self.mxDB.release()
|
||||||
|
|
||||||
def loadBidTxns(self, bid, session):
|
def loadBidTxns(self, bid, session):
|
||||||
|
bid.txns = {}
|
||||||
for stx in session.query(SwapTx).filter(sa.and_(SwapTx.bid_id == bid.bid_id)):
|
for stx in session.query(SwapTx).filter(sa.and_(SwapTx.bid_id == bid.bid_id)):
|
||||||
if stx.tx_type == TxTypes.ITX:
|
if stx.tx_type == TxTypes.ITX:
|
||||||
bid.initiate_tx = stx
|
bid.initiate_tx = stx
|
||||||
@ -1243,7 +1261,8 @@ class BasicSwap(BaseApp):
|
|||||||
elif stx.tx_type == TxTypes.XMR_SWAP_B_LOCK:
|
elif stx.tx_type == TxTypes.XMR_SWAP_B_LOCK:
|
||||||
bid.xmr_b_lock_tx = stx
|
bid.xmr_b_lock_tx = stx
|
||||||
else:
|
else:
|
||||||
self.log.warning('Unknown transaction type: {}'.format(stx.tx_type))
|
bid.txns[stx.tx_type] = stx
|
||||||
|
#self.log.warning('Unknown transaction type: {}'.format(stx.tx_type))
|
||||||
def getXmrBid(self, bid_id, sent=False):
|
def getXmrBid(self, bid_id, sent=False):
|
||||||
self.mxDB.acquire()
|
self.mxDB.acquire()
|
||||||
try:
|
try:
|
||||||
@ -1585,15 +1604,16 @@ class BasicSwap(BaseApp):
|
|||||||
)
|
)
|
||||||
|
|
||||||
xmr_swap.al_lock_refund_tx_sig = ci_from.signTx(karl, xmr_swap.a_lock_refund_tx, 0, xmr_swap.a_lock_tx_script, bid.amount)
|
xmr_swap.al_lock_refund_tx_sig = ci_from.signTx(karl, xmr_swap.a_lock_refund_tx, 0, xmr_swap.a_lock_tx_script, bid.amount)
|
||||||
|
|
||||||
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)
|
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)
|
||||||
assert(v)
|
assert(v)
|
||||||
|
|
||||||
|
pkh_refund_to = ci_from.decodeAddress(self.getReceiveAddressForCoin(coin_from))
|
||||||
xmr_swap.a_lock_refund_spend_tx = ci_from.createScriptLockRefundSpendTx(
|
xmr_swap.a_lock_refund_spend_tx = ci_from.createScriptLockRefundSpendTx(
|
||||||
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
|
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
|
||||||
xmr_swap.pkal,
|
pkh_refund_to,
|
||||||
xmr_offer.a_fee_rate
|
xmr_offer.a_fee_rate
|
||||||
)
|
)
|
||||||
|
xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxHash(xmr_swap.a_lock_refund_spend_tx)
|
||||||
|
|
||||||
msg_buf = XmrBidAcceptMessage()
|
msg_buf = XmrBidAcceptMessage()
|
||||||
msg_buf.bid_msg_id = bid_id
|
msg_buf.bid_msg_id = bid_id
|
||||||
@ -1699,6 +1719,7 @@ class BasicSwap(BaseApp):
|
|||||||
self.log.error('Bid %s - Error: %s', bid_id.hex(), 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
|
||||||
|
print('[rm] saveBid 5')
|
||||||
self.saveBid(bid_id, bid)
|
self.saveBid(bid_id, bid)
|
||||||
|
|
||||||
def createInitiateTxn(self, coin_type, bid_id, bid, initiate_script):
|
def createInitiateTxn(self, coin_type, bid_id, bid, initiate_script):
|
||||||
@ -2187,20 +2208,161 @@ class BasicSwap(BaseApp):
|
|||||||
|
|
||||||
def checkXmrBidState(self, bid_id, bid, offer):
|
def checkXmrBidState(self, bid_id, bid, offer):
|
||||||
rv = False
|
rv = False
|
||||||
state = BidStates(bid.state)
|
|
||||||
if state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX:
|
|
||||||
if bid.xmr_a_lock_tx is None:
|
|
||||||
return
|
|
||||||
ci_from = self.ci(Coins(offer.coin_from))
|
|
||||||
ci_to = self.ci(Coins(offer.coin_to))
|
|
||||||
|
|
||||||
try:
|
ci_from = self.ci(Coins(offer.coin_from))
|
||||||
self.mxDB.acquire()
|
ci_to = self.ci(Coins(offer.coin_to))
|
||||||
session = scoped_session(self.session_factory)
|
|
||||||
xmr_offer = session.query(XmrOffer).filter_by(offer_id=offer.offer_id).first()
|
session = None
|
||||||
assert(xmr_offer), 'XMR offer not found: {}.'.format(offer.offer_id.hex())
|
try:
|
||||||
xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first()
|
self.mxDB.acquire()
|
||||||
assert(xmr_swap), 'XMR swap not found: {}.'.format(bid.bid_id.hex())
|
session = scoped_session(self.session_factory)
|
||||||
|
xmr_offer = session.query(XmrOffer).filter_by(offer_id=offer.offer_id).first()
|
||||||
|
assert(xmr_offer), 'XMR offer not found: {}.'.format(offer.offer_id.hex())
|
||||||
|
xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first()
|
||||||
|
assert(xmr_swap), 'XMR swap not found: {}.'.format(bid.bid_id.hex())
|
||||||
|
|
||||||
|
if bid.was_sent and not TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND in bid.txns:
|
||||||
|
found_tx = ci_from.getTransaction(xmr_swap.a_lock_refund_spend_tx_id)
|
||||||
|
if found_tx is not None:
|
||||||
|
self.log.debug('Found coin a lock refund spend tx')
|
||||||
|
xmr_swap.a_lock_refund_spend_tx = found_tx # Replace with fully signed tx
|
||||||
|
|
||||||
|
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND] = SwapTx(
|
||||||
|
bid_id=bid_id,
|
||||||
|
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND,
|
||||||
|
txid=xmr_swap.a_lock_refund_spend_tx_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if bid.was_sent:
|
||||||
|
if bid.xmr_b_lock_tx is not None:
|
||||||
|
delay = random.randrange(self.min_delay_auto_accept, self.max_delay_auto_accept)
|
||||||
|
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:
|
||||||
|
rv = True # Remove from swaps_in_progress
|
||||||
|
bid.setState(BidStates.XMR_SWAP_FAILED_REFUNDED)
|
||||||
|
|
||||||
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
session.commit()
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
if TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE in bid.txns:
|
||||||
|
swipe_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE]
|
||||||
|
|
||||||
|
# TODO: explorer or getrawtransaction for each block after swap begins
|
||||||
|
found_tx = ci_from.getTransaction(swipe_tx.txid)
|
||||||
|
if found_tx is not None:
|
||||||
|
# TODO: Check depth
|
||||||
|
rv = True
|
||||||
|
bid.setState(BidStates.XMR_SWAP_FAILED_SWIPED)
|
||||||
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
session.commit()
|
||||||
|
return rv
|
||||||
|
|
||||||
|
if bid.was_received and TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND in bid.txns:
|
||||||
|
refund_spend_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND]
|
||||||
|
|
||||||
|
# TODO: explorer or getrawtransaction for each block after swap begins
|
||||||
|
found_tx = ci_from.getTransaction(refund_spend_tx.txid)
|
||||||
|
if found_tx is not None:
|
||||||
|
# TODO: Check depth
|
||||||
|
#if bid.was_received and TxTypes.XMR_SWAP_B_LOCK in bid.txns:
|
||||||
|
if bid.was_sent and bid.xmr_b_lock_tx is not None:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
rv = True
|
||||||
|
bid.setState(BidStates.XMR_SWAP_FAILED_REFUNDED)
|
||||||
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
session.commit()
|
||||||
|
return rv
|
||||||
|
|
||||||
|
if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns:
|
||||||
|
refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND]
|
||||||
|
if bid.was_received:
|
||||||
|
if bid.debug_ind == DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND:
|
||||||
|
self.log.debug('XMR bid %s: Abandoning bid for testing: %d.', bid_id.hex(), bid.debug_ind)
|
||||||
|
bid.setState(BidStates.BID_ABANDONED)
|
||||||
|
rv = True
|
||||||
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
session.commit()
|
||||||
|
return rv
|
||||||
|
try:
|
||||||
|
txid = ci_from.publishTx(xmr_swap.a_lock_refund_spend_tx)
|
||||||
|
|
||||||
|
self.log.info('Submitted coin a lock refund spend tx for bid {}'.format(bid_id.hex()))
|
||||||
|
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND] = SwapTx(
|
||||||
|
bid_id=bid_id,
|
||||||
|
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND,
|
||||||
|
txid=bytes.fromhex(txid),
|
||||||
|
)
|
||||||
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
session.commit()
|
||||||
|
except Exception as ex:
|
||||||
|
logging.debug('Trying to publish coin a lock refund spend tx: %s', str(ex))
|
||||||
|
|
||||||
|
|
||||||
|
if bid.was_sent:
|
||||||
|
if xmr_swap.a_lock_refund_swipe_tx is None:
|
||||||
|
self.createCoinALockRefundSwipeTx(ci_from, bid, offer, xmr_swap, xmr_offer)
|
||||||
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
try:
|
||||||
|
txid = ci_from.publishTx(xmr_swap.a_lock_refund_swipe_tx)
|
||||||
|
self.log.info('Submitted coin a lock refund swipe tx for bid {}'.format(bid_id.hex()))
|
||||||
|
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE] = SwapTx(
|
||||||
|
bid_id=bid_id,
|
||||||
|
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE,
|
||||||
|
txid=bytes.fromhex(txid),
|
||||||
|
)
|
||||||
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
session.commit()
|
||||||
|
except Exception as ex:
|
||||||
|
logging.debug('Trying to publish coin a lock refund swipe tx: %s', str(ex))
|
||||||
|
|
||||||
|
if BidStates(bid.state) == BidStates.XMR_SWAP_NOSCRIPT_TX_RECOVERED:
|
||||||
|
txid_hex = bid.xmr_b_lock_tx.spend_txid.hex()
|
||||||
|
|
||||||
|
found_tx = ci_to.findTxnByHash(txid_hex)
|
||||||
|
if found_tx is not None:
|
||||||
|
self.log.info('Found coin b lock recover tx bid %s', bid_id.hex())
|
||||||
|
rv = True # Remove from swaps_in_progress
|
||||||
|
bid.setState(BidStates.XMR_SWAP_FAILED_REFUNDED)
|
||||||
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
session.commit()
|
||||||
|
return rv
|
||||||
|
|
||||||
|
if len(xmr_swap.al_lock_refund_tx_sig) > 0 and len(xmr_swap.af_lock_refund_tx_sig) > 0:
|
||||||
|
try:
|
||||||
|
txid = ci_from.publishTx(xmr_swap.a_lock_refund_tx)
|
||||||
|
|
||||||
|
self.log.info('Submitted coin a lock refund tx for bid {}'.format(bid_id.hex()))
|
||||||
|
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx(
|
||||||
|
bid_id=bid_id,
|
||||||
|
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
|
||||||
|
txid=bytes.fromhex(txid),
|
||||||
|
)
|
||||||
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
session.commit()
|
||||||
|
return rv
|
||||||
|
except Exception as ex:
|
||||||
|
if 'Transaction already in block chain' in str(ex):
|
||||||
|
self.log.info('Found coin a lock refund tx for bid {}'.format(bid_id.hex()))
|
||||||
|
txid = ci_from.getTxHash(xmr_swap.a_lock_refund_tx)
|
||||||
|
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx(
|
||||||
|
bid_id=bid_id,
|
||||||
|
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
|
||||||
|
txid=txid,
|
||||||
|
)
|
||||||
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
session.commit()
|
||||||
|
return rv
|
||||||
|
|
||||||
|
state = BidStates(bid.state)
|
||||||
|
if state == BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX:
|
||||||
|
if bid.xmr_a_lock_tx is None:
|
||||||
|
return rv
|
||||||
|
|
||||||
# TODO: Timeout waiting for transactions
|
# TODO: Timeout waiting for transactions
|
||||||
|
|
||||||
@ -2208,7 +2370,7 @@ class BasicSwap(BaseApp):
|
|||||||
utxos = ci_from.getOutput(bid.xmr_a_lock_tx.txid, a_lock_tx_dest, bid.amount)
|
utxos = ci_from.getOutput(bid.xmr_a_lock_tx.txid, a_lock_tx_dest, bid.amount)
|
||||||
|
|
||||||
if len(utxos) < 1:
|
if len(utxos) < 1:
|
||||||
return
|
return rv
|
||||||
|
|
||||||
if len(utxos) > 1:
|
if len(utxos) > 1:
|
||||||
raise ValueError('Too many outputs for chain A lock tx')
|
raise ValueError('Too many outputs for chain A lock tx')
|
||||||
@ -2227,41 +2389,27 @@ class BasicSwap(BaseApp):
|
|||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
session.close()
|
elif state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED:
|
||||||
session.remove()
|
#if bid.was_sent and TxTypes.XMR_SWAP_B_LOCK not in bid.txns:
|
||||||
except Exception as ex:
|
if bid.was_sent and bid.xmr_b_lock_tx is None:
|
||||||
session.close()
|
return rv
|
||||||
session.remove()
|
|
||||||
raise ex
|
|
||||||
finally:
|
|
||||||
self.mxDB.release()
|
|
||||||
elif state == BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED:
|
|
||||||
if bid.was_sent and bid.xmr_b_lock_tx is None:
|
|
||||||
return
|
|
||||||
ci_from = self.ci(Coins(offer.coin_from))
|
|
||||||
ci_to = self.ci(Coins(offer.coin_to))
|
|
||||||
|
|
||||||
try:
|
found_tx = ci_to.findTxB(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, ci_to.blocks_confirmed, xmr_swap.b_restore_height)
|
||||||
self.mxDB.acquire()
|
if found_tx is not None:
|
||||||
session = scoped_session(self.session_factory)
|
|
||||||
xmr_offer = session.query(XmrOffer).filter_by(offer_id=offer.offer_id).first()
|
|
||||||
assert(xmr_offer), 'XMR offer not found: {}.'.format(offer.offer_id.hex())
|
|
||||||
xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first()
|
|
||||||
assert(xmr_swap), 'XMR swap not found: {}.'.format(bid.bid_id.hex())
|
|
||||||
|
|
||||||
rv = ci_to.findTxB(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, ci_to.blocks_confirmed, xmr_swap.b_restore_height)
|
|
||||||
|
|
||||||
if rv is not None:
|
|
||||||
|
|
||||||
|
#if TxTypes.XMR_SWAP_B_LOCK not in bid.txns:
|
||||||
if bid.xmr_b_lock_tx is None:
|
if bid.xmr_b_lock_tx is None:
|
||||||
b_lock_tx_id = bytes.fromhex(rv['txid'])
|
b_lock_tx_id = bytes.fromhex(found_tx['txid'])
|
||||||
|
#bid.txns[TxTypes.XMR_SWAP_B_LOCK] = SwapTx(
|
||||||
bid.xmr_b_lock_tx = SwapTx(
|
bid.xmr_b_lock_tx = SwapTx(
|
||||||
bid_id=bid_id,
|
bid_id=bid_id,
|
||||||
tx_type=TxTypes.XMR_SWAP_B_LOCK,
|
tx_type=TxTypes.XMR_SWAP_B_LOCK,
|
||||||
txid=b_lock_tx_id,
|
txid=b_lock_tx_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#bid.txns[TxTypes.XMR_SWAP_B_LOCK].setState(TxStates.TX_CONFIRMED)
|
||||||
bid.xmr_b_lock_tx.setState(TxStates.TX_CONFIRMED)
|
bid.xmr_b_lock_tx.setState(TxStates.TX_CONFIRMED)
|
||||||
|
|
||||||
bid.setState(BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED)
|
bid.setState(BidStates.XMR_SWAP_NOSCRIPT_COIN_LOCKED)
|
||||||
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
|
||||||
@ -2271,41 +2419,21 @@ class BasicSwap(BaseApp):
|
|||||||
self.createEventInSession(delay, EventTypes.SEND_XMR_SECRET, bid_id, session)
|
self.createEventInSession(delay, EventTypes.SEND_XMR_SECRET, bid_id, session)
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
|
# Waiting for initiate txn to be confirmed in 'from' chain
|
||||||
session.close()
|
#initiate_txnid_hex = bid.initiate_tx.txid.hex()
|
||||||
session.remove()
|
#p2sh = self.getScriptAddress(coin_from, bid.initiate_tx.script)
|
||||||
except Exception as ex:
|
elif state == BidStates.XMR_SWAP_SECRET_SHARED:
|
||||||
session.close()
|
# Wait for script spend tx to confirm
|
||||||
session.remove()
|
|
||||||
raise ex
|
|
||||||
finally:
|
|
||||||
self.mxDB.release()
|
|
||||||
# Waiting for initiate txn to be confirmed in 'from' chain
|
|
||||||
#initiate_txnid_hex = bid.initiate_tx.txid.hex()
|
|
||||||
#p2sh = self.getScriptAddress(coin_from, bid.initiate_tx.script)
|
|
||||||
elif state == BidStates.XMR_SWAP_SECRET_SHARED:
|
|
||||||
# Wait for script spend tx to confirm
|
|
||||||
ci_from = self.ci(Coins(offer.coin_from))
|
|
||||||
ci_to = self.ci(Coins(offer.coin_to))
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.mxDB.acquire()
|
|
||||||
session = scoped_session(self.session_factory)
|
|
||||||
xmr_offer = session.query(XmrOffer).filter_by(offer_id=offer.offer_id).first()
|
|
||||||
assert(xmr_offer), 'XMR offer not found: {}.'.format(offer.offer_id.hex())
|
|
||||||
xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first()
|
|
||||||
assert(xmr_swap), 'XMR swap not found: {}.'.format(bid.bid_id.hex())
|
|
||||||
|
|
||||||
# TODO: Use explorer to get tx / block hash for getrawtransaction
|
# TODO: Use explorer to get tx / block hash for getrawtransaction
|
||||||
rv = ci_from.getTransaction(xmr_swap.a_lock_spend_tx_id)
|
found_tx = ci_from.getTransaction(xmr_swap.a_lock_spend_tx_id)
|
||||||
if rv is not None:
|
if found_tx is not None:
|
||||||
xmr_swap.a_lock_spend_tx = rv
|
xmr_swap.a_lock_spend_tx = found_tx
|
||||||
|
|
||||||
#bid.xmr_a_lock_spend_tx.setState(TxStates.TX_CONFIRMED)
|
#bid.xmr_a_lock_spend_tx.setState(TxStates.TX_CONFIRMED)
|
||||||
bid.setState(BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED) # TODO: Wait for confirmation?
|
bid.setState(BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED) # TODO: Wait for confirmation?
|
||||||
|
|
||||||
if not bid.was_received:
|
if not bid.was_received:
|
||||||
rv = True # Remove from return False
|
rv = True # Remove from swaps_in_progress
|
||||||
bid.setState(BidStates.SWAP_COMPLETED)
|
bid.setState(BidStates.SWAP_COMPLETED)
|
||||||
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
if bid.was_received:
|
if bid.was_received:
|
||||||
@ -2314,25 +2442,41 @@ class BasicSwap(BaseApp):
|
|||||||
self.createEventInSession(delay, EventTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, session)
|
self.createEventInSession(delay, EventTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, session)
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
|
elif state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
|
||||||
|
#txid_hex = bid.txns[TxTypes.XMR_SWAP_B_LOCK].spend_txid.hex()
|
||||||
|
txid_hex = bid.xmr_b_lock_tx.spend_txid.hex()
|
||||||
|
|
||||||
|
found_tx = ci_to.findTxnByHash(txid_hex)
|
||||||
|
if found_tx is not None:
|
||||||
|
self.log.info('Found coin b lock spend tx bid %s', bid_id.hex())
|
||||||
|
rv = True # Remove from swaps_in_progress
|
||||||
|
bid.setState(BidStates.SWAP_COMPLETED)
|
||||||
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
session.commit()
|
||||||
|
'''
|
||||||
|
elif state == BidStates.XMR_SWAP_NOSCRIPT_TX_RECOVERED:
|
||||||
|
print('[rm] waiting for coin b lock tx recover tx to confirm')
|
||||||
|
|
||||||
|
txid_hex = bid.xmr_b_lock_tx.spend_txid.hex()
|
||||||
|
|
||||||
|
found_tx = ci_to.findTxnByHash(txid_hex)
|
||||||
|
if found_tx is not None:
|
||||||
|
self.log.info('Found coin b lock recover tx bid %s', bid_id.hex())
|
||||||
|
rv = True # Remove from swaps_in_progress
|
||||||
|
bid.setState(BidStates.XMR_SWAP_FAILED_REFUNDED)
|
||||||
|
print('[rm] saveBidInSession 9.1')
|
||||||
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
session.commit()
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
except Exception as ex:
|
||||||
|
raise ex
|
||||||
|
finally:
|
||||||
|
if session:
|
||||||
session.close()
|
session.close()
|
||||||
session.remove()
|
session.remove()
|
||||||
except Exception as ex:
|
self.mxDB.release()
|
||||||
session.close()
|
|
||||||
session.remove()
|
|
||||||
raise ex
|
|
||||||
finally:
|
|
||||||
self.mxDB.release()
|
|
||||||
elif state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
|
|
||||||
ci_to = self.ci(Coins(offer.coin_to))
|
|
||||||
txid_hex = bid.xmr_b_lock_tx.spend_txid.hex()
|
|
||||||
|
|
||||||
rv = ci_to.findTxnByHash(txid_hex)
|
|
||||||
if rv is not None:
|
|
||||||
rv = True # Remove from return False
|
|
||||||
bid.setState(BidStates.SWAP_COMPLETED)
|
|
||||||
self.saveBid(bid_id, bid)
|
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
@ -2648,6 +2792,7 @@ class BasicSwap(BaseApp):
|
|||||||
def checkEvents(self):
|
def checkEvents(self):
|
||||||
self.mxDB.acquire()
|
self.mxDB.acquire()
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
|
session = None
|
||||||
try:
|
try:
|
||||||
session = scoped_session(self.session_factory)
|
session = scoped_session(self.session_factory)
|
||||||
|
|
||||||
@ -2668,6 +2813,8 @@ class BasicSwap(BaseApp):
|
|||||||
self.redeemXmrBidCoinALockTx(row.linked_id, session)
|
self.redeemXmrBidCoinALockTx(row.linked_id, session)
|
||||||
elif row.event_type == EventTypes.REDEEM_XMR_SWAP_LOCK_TX_B:
|
elif row.event_type == EventTypes.REDEEM_XMR_SWAP_LOCK_TX_B:
|
||||||
self.redeemXmrBidCoinBLockTx(row.linked_id, session)
|
self.redeemXmrBidCoinBLockTx(row.linked_id, session)
|
||||||
|
elif row.event_type == EventTypes.RECOVER_XMR_SWAP_LOCK_TX_B:
|
||||||
|
self.recoverXmrBidCoinBLockTx(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)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
@ -2677,9 +2824,10 @@ class BasicSwap(BaseApp):
|
|||||||
session.delete(row)
|
session.delete(row)
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
session.close()
|
|
||||||
session.remove()
|
|
||||||
finally:
|
finally:
|
||||||
|
if session:
|
||||||
|
session.close()
|
||||||
|
session.remove()
|
||||||
self.mxDB.release()
|
self.mxDB.release()
|
||||||
|
|
||||||
def checkXmrSwaps(self):
|
def checkXmrSwaps(self):
|
||||||
@ -2796,8 +2944,8 @@ class BasicSwap(BaseApp):
|
|||||||
xmr_offer = XmrOffer()
|
xmr_offer = XmrOffer()
|
||||||
|
|
||||||
xmr_offer.offer_id = offer_id
|
xmr_offer.offer_id = offer_id
|
||||||
xmr_offer.lock_time_1 = offer_data.lock_value
|
xmr_offer.lock_time_1 = getExpectedSequence(offer_data.lock_type, offer_data.lock_value, coin_from)
|
||||||
xmr_offer.lock_time_2 = offer_data.lock_value
|
xmr_offer.lock_time_2 = getExpectedSequence(offer_data.lock_type, offer_data.lock_value, coin_from)
|
||||||
|
|
||||||
xmr_offer.a_fee_rate = offer_data.fee_rate_from
|
xmr_offer.a_fee_rate = offer_data.fee_rate_from
|
||||||
xmr_offer.b_fee_rate = offer_data.fee_rate_to
|
xmr_offer.b_fee_rate = offer_data.fee_rate_to
|
||||||
@ -3012,7 +3160,6 @@ class BasicSwap(BaseApp):
|
|||||||
bid.setState(BidStates.BID_RECEIVED)
|
bid.setState(BidStates.BID_RECEIVED)
|
||||||
self.saveBidInSession(bid.bid_id, bid, session, xmr_swap)
|
self.saveBidInSession(bid.bid_id, bid, session, xmr_swap)
|
||||||
|
|
||||||
|
|
||||||
def receiveXmrBidAccept(self, bid, session):
|
def receiveXmrBidAccept(self, bid, session):
|
||||||
# Follower receiving MSG1F and MSG2F
|
# Follower receiving MSG1F and MSG2F
|
||||||
self.log.debug('Receiving xmr bid accept %s', bid.bid_id.hex())
|
self.log.debug('Receiving xmr bid accept %s', bid.bid_id.hex())
|
||||||
@ -3163,6 +3310,7 @@ class BasicSwap(BaseApp):
|
|||||||
xmr_swap.a_lock_refund_tx = msg_data.a_lock_refund_tx
|
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_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.a_lock_refund_spend_tx = msg_data.a_lock_refund_spend_tx
|
||||||
|
xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxHash(xmr_swap.a_lock_refund_spend_tx)
|
||||||
xmr_swap.al_lock_refund_tx_sig = msg_data.al_lock_refund_tx_sig
|
xmr_swap.al_lock_refund_tx_sig = msg_data.al_lock_refund_tx_sig
|
||||||
|
|
||||||
check_a_lock_tx_inputs = True
|
check_a_lock_tx_inputs = True
|
||||||
@ -3225,6 +3373,8 @@ class BasicSwap(BaseApp):
|
|||||||
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_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)
|
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)
|
||||||
|
|
||||||
|
self.addLockRefundSigs(xmr_swap, ci_from)
|
||||||
|
|
||||||
msg_buf = XmrBidLockTxSigsMessage(
|
msg_buf = XmrBidLockTxSigsMessage(
|
||||||
bid_msg_id=bid_id,
|
bid_msg_id=bid_id,
|
||||||
af_lock_refund_spend_tx_esig=xmr_swap.af_lock_refund_spend_tx_esig,
|
af_lock_refund_spend_tx_esig=xmr_swap.af_lock_refund_spend_tx_esig,
|
||||||
@ -3332,16 +3482,27 @@ class BasicSwap(BaseApp):
|
|||||||
ci_from = self.ci(coin_from)
|
ci_from = self.ci(coin_from)
|
||||||
ci_to = self.ci(coin_to)
|
ci_to = self.ci(coin_to)
|
||||||
|
|
||||||
|
if bid.debug_ind == DebugTypes.BID_STOP_AFTER_COIN_A_LOCK:
|
||||||
|
self.log.debug('XMR bid %s: Abandoning bid for testing: %d.', bid_id.hex(), bid.debug_ind)
|
||||||
|
# bid.setState(BidStates.BID_ABANDONED) # TODO: Retry if state
|
||||||
|
return
|
||||||
|
|
||||||
|
if bid.debug_ind == DebugTypes.CREATE_INVALID_COIN_B_LOCK:
|
||||||
|
self.log.debug('XMR bid %s: Debug %d - Reducing lock b txn amount by 10%%.', bid_id.hex(), bid.debug_ind)
|
||||||
|
bid.amount_to -= int(bid.amount_to * 0.1)
|
||||||
b_lock_tx_id = ci_to.publishBLockTx(xmr_swap.pkbv, xmr_swap.pkbs, bid.amount_to, xmr_offer.b_fee_rate)
|
b_lock_tx_id = ci_to.publishBLockTx(xmr_swap.pkbv, xmr_swap.pkbs, bid.amount_to, xmr_offer.b_fee_rate)
|
||||||
|
|
||||||
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())
|
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())
|
||||||
bid.xmr_b_lock_tx = SwapTx(
|
bid.xmr_b_lock_tx = SwapTx(
|
||||||
|
#xmr_b_lock_tx = SwapTx(
|
||||||
bid_id=bid_id,
|
bid_id=bid_id,
|
||||||
tx_type=TxTypes.XMR_SWAP_B_LOCK,
|
tx_type=TxTypes.XMR_SWAP_B_LOCK,
|
||||||
txid=b_lock_tx_id,
|
txid=b_lock_tx_id,
|
||||||
)
|
)
|
||||||
bid.xmr_b_lock_tx.setState(TxStates.TX_NONE)
|
bid.xmr_b_lock_tx.setState(TxStates.TX_NONE)
|
||||||
|
#bid.txns[TxTypes.XMR_SWAP_B_LOCK] = xmr_b_lock_tx
|
||||||
|
|
||||||
|
print('[rm] saveBidInSession 23')
|
||||||
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
|
||||||
# Update copy of bid in swaps_in_progress
|
# Update copy of bid in swaps_in_progress
|
||||||
@ -3377,6 +3538,7 @@ class BasicSwap(BaseApp):
|
|||||||
xmr_swap.coin_a_lock_refund_spend_tx_msg_id = bytes.fromhex(ro['msgid'])
|
xmr_swap.coin_a_lock_refund_spend_tx_msg_id = bytes.fromhex(ro['msgid'])
|
||||||
|
|
||||||
bid.setState(BidStates.XMR_SWAP_SECRET_SHARED)
|
bid.setState(BidStates.XMR_SWAP_SECRET_SHARED)
|
||||||
|
print('[rm] saveBidInSession 24')
|
||||||
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
# Update copy of bid in swaps_in_progress
|
# Update copy of bid in swaps_in_progress
|
||||||
self.swaps_in_progress[bid_id] = (bid, offer)
|
self.swaps_in_progress[bid_id] = (bid, offer)
|
||||||
@ -3428,6 +3590,7 @@ class BasicSwap(BaseApp):
|
|||||||
)
|
)
|
||||||
bid.xmr_a_lock_spend_tx.setState(TxStates.TX_NONE)
|
bid.xmr_a_lock_spend_tx.setState(TxStates.TX_NONE)
|
||||||
|
|
||||||
|
print('[rm] saveBidInSession 25')
|
||||||
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
# Update copy of bid in swaps_in_progress
|
# Update copy of bid in swaps_in_progress
|
||||||
self.swaps_in_progress[bid_id] = (bid, offer)
|
self.swaps_in_progress[bid_id] = (bid, offer)
|
||||||
@ -3451,12 +3614,12 @@ class BasicSwap(BaseApp):
|
|||||||
# Extract the leader's decrypted signature and use it to recover the follower's privatekey
|
# Extract the leader's decrypted signature and use it to recover the follower's privatekey
|
||||||
xmr_swap.al_lock_spend_tx_sig = ci_from.extractLeaderSig(xmr_swap.a_lock_spend_tx)
|
xmr_swap.al_lock_spend_tx_sig = ci_from.extractLeaderSig(xmr_swap.a_lock_spend_tx)
|
||||||
|
|
||||||
xmr_swap.kbsf = ci_from.recoverEncKey(xmr_swap.al_lock_spend_tx_esig, xmr_swap.al_lock_spend_tx_sig, xmr_swap.pkasf)
|
kbsf = ci_from.recoverEncKey(xmr_swap.al_lock_spend_tx_esig, xmr_swap.al_lock_spend_tx_sig, xmr_swap.pkasf)
|
||||||
assert(xmr_swap.kbsf is not None)
|
assert(kbsf is not None)
|
||||||
|
|
||||||
kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3, for_xmr=True)
|
kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3, for_xmr=True)
|
||||||
|
|
||||||
vkbs = ci_to.sumKeys(kbsl, xmr_swap.kbsf)
|
vkbs = ci_to.sumKeys(kbsl, kbsf)
|
||||||
Kbs_test = ci_to.getPubkey(vkbs)
|
Kbs_test = ci_to.getPubkey(vkbs)
|
||||||
print('Kbs_test', Kbs_test.hex())
|
print('Kbs_test', Kbs_test.hex())
|
||||||
print('Kbs', xmr_swap.pkbsf.hex())
|
print('Kbs', xmr_swap.pkbsf.hex())
|
||||||
@ -3466,10 +3629,57 @@ class BasicSwap(BaseApp):
|
|||||||
txid = ci_to.spendBLockTx(address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, xmr_swap.b_restore_height)
|
txid = ci_to.spendBLockTx(address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, xmr_swap.b_restore_height)
|
||||||
|
|
||||||
bid.xmr_b_lock_tx.spend_txid = txid
|
bid.xmr_b_lock_tx.spend_txid = txid
|
||||||
|
#txn = bid.txns[TxTypes.XMR_SWAP_B_LOCK]
|
||||||
|
#print('[rm] TxTypes.XMR_SWAP_B_LOCK', txn.bid_id.hex())
|
||||||
|
#txn.spend_txid = txid
|
||||||
|
#bid.txns[TxTypes.XMR_SWAP_B_LOCK].spend_txid = txid
|
||||||
|
print('[rm] saveBidInSession 26')
|
||||||
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
# Update copy of bid in swaps_in_progress
|
# Update copy of bid in swaps_in_progress
|
||||||
self.swaps_in_progress[bid_id] = (bid, offer)
|
self.swaps_in_progress[bid_id] = (bid, offer)
|
||||||
|
|
||||||
|
def recoverXmrBidCoinBLockTx(self, bid_id, session):
|
||||||
|
# Follower recovering B lock tx
|
||||||
|
self.log.debug('Recovering coin B 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)
|
||||||
|
|
||||||
|
# Extract the follower's decrypted signature and use it to recover the leader's privatekey
|
||||||
|
af_lock_refund_spend_tx_sig = ci_from.extractFollowerSig(xmr_swap.a_lock_refund_spend_tx)
|
||||||
|
|
||||||
|
kbsl = ci_from.recoverEncKey(xmr_swap.af_lock_refund_spend_tx_esig, af_lock_refund_spend_tx_sig, xmr_swap.pkasl)
|
||||||
|
assert(kbsl is not None)
|
||||||
|
|
||||||
|
kbsf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_xmr=True)
|
||||||
|
|
||||||
|
vkbs = ci_to.sumKeys(kbsl, kbsf)
|
||||||
|
Kbs_test = ci_to.getPubkey(vkbs)
|
||||||
|
print('Kbs_test', Kbs_test.hex())
|
||||||
|
print('Kbs', xmr_swap.pkbsf.hex())
|
||||||
|
|
||||||
|
address_to = ci_to.getMainWalletAddress()
|
||||||
|
|
||||||
|
txid = ci_to.spendBLockTx(address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, xmr_swap.b_restore_height)
|
||||||
|
|
||||||
|
bid.xmr_b_lock_tx.spend_txid = txid
|
||||||
|
|
||||||
|
bid.setState(BidStates.XMR_SWAP_NOSCRIPT_TX_RECOVERED)
|
||||||
|
print('[rm] saveBidInSession 32')
|
||||||
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
# Update copy of bid in swaps_in_progress
|
||||||
|
self.swaps_in_progress[bid_id] = (bid, offer)
|
||||||
|
|
||||||
|
|
||||||
def processXmrBidCoinALockSigs(self, msg):
|
def processXmrBidCoinALockSigs(self, msg):
|
||||||
# Leader processing MSG3L
|
# Leader processing MSG3L
|
||||||
self.log.debug('Processing xmr coin a follower lock sigs msg %s', msg['msgid'])
|
self.log.debug('Processing xmr coin a follower lock sigs msg %s', msg['msgid'])
|
||||||
@ -3498,16 +3708,33 @@ class BasicSwap(BaseApp):
|
|||||||
xmr_swap.af_lock_refund_tx_sig = msg_data.af_lock_refund_tx_sig
|
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)
|
kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 3, for_xmr=True)
|
||||||
|
karl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 5)
|
||||||
|
|
||||||
xmr_swap.af_lock_refund_spend_tx_sig = ci_from.decryptOtVES(kbsl, xmr_swap.af_lock_refund_spend_tx_esig)
|
xmr_swap.af_lock_refund_spend_tx_sig = ci_from.decryptOtVES(kbsl, xmr_swap.af_lock_refund_spend_tx_esig)
|
||||||
|
al_lock_refund_spend_tx_sig = ci_from.signTx(karl, xmr_swap.a_lock_refund_spend_tx, 0, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value)
|
||||||
|
|
||||||
|
self.log.debug('Setting lock refund spend tx sigs')
|
||||||
|
witness_stack = [
|
||||||
|
b'',
|
||||||
|
al_lock_refund_spend_tx_sig,
|
||||||
|
xmr_swap.af_lock_refund_spend_tx_sig,
|
||||||
|
bytes((1,)),
|
||||||
|
xmr_swap.a_lock_refund_tx_script,
|
||||||
|
]
|
||||||
|
signed_tx = ci_from.setTxSignature(xmr_swap.a_lock_refund_spend_tx, witness_stack)
|
||||||
|
assert(signed_tx), 'setTxSignature failed'
|
||||||
|
xmr_swap.a_lock_refund_spend_tx = signed_tx
|
||||||
|
|
||||||
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)
|
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'
|
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_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.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)
|
self.createEvent(delay, EventTypes.SEND_XMR_SWAP_LOCK_TX_A, bid_id)
|
||||||
|
|
||||||
bid.setState(BidStates.SWAP_DELAYING)
|
bid.setState(BidStates.SWAP_DELAYING)
|
||||||
|
print('[rm] saveBid 27')
|
||||||
self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
|
self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
if self.debug:
|
if self.debug:
|
||||||
@ -3553,6 +3780,7 @@ class BasicSwap(BaseApp):
|
|||||||
assert(v), 'verifyTxOtVES failed'
|
assert(v), 'verifyTxOtVES failed'
|
||||||
|
|
||||||
bid.setState(BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX)
|
bid.setState(BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX)
|
||||||
|
print('[rm] saveBid 28')
|
||||||
self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
|
self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
if self.debug:
|
if self.debug:
|
||||||
@ -3607,6 +3835,7 @@ class BasicSwap(BaseApp):
|
|||||||
self.createEvent(delay, EventTypes.REDEEM_XMR_SWAP_LOCK_TX_A, bid_id)
|
self.createEvent(delay, EventTypes.REDEEM_XMR_SWAP_LOCK_TX_A, bid_id)
|
||||||
|
|
||||||
bid.setState(BidStates.XMR_SWAP_SECRET_SHARED)
|
bid.setState(BidStates.XMR_SWAP_SECRET_SHARED)
|
||||||
|
print('[rm] saveBid 29')
|
||||||
self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
|
self.saveBid(bid_id, bid, xmr_swap=xmr_swap)
|
||||||
self.swaps_in_progress[bid_id] = (bid, offer)
|
self.swaps_in_progress[bid_id] = (bid, offer)
|
||||||
|
|
||||||
@ -3676,6 +3905,7 @@ class BasicSwap(BaseApp):
|
|||||||
for bid_id, v in self.swaps_in_progress.items():
|
for bid_id, v in self.swaps_in_progress.items():
|
||||||
try:
|
try:
|
||||||
if self.checkBidState(bid_id, v[0], v[1]) is True:
|
if self.checkBidState(bid_id, v[0], v[1]) is True:
|
||||||
|
self.log.debug('[rm] removing state: %s', BidStates(v[0].state))
|
||||||
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))
|
||||||
@ -3730,6 +3960,7 @@ class BasicSwap(BaseApp):
|
|||||||
if has_changed:
|
if has_changed:
|
||||||
session = scoped_session(self.session_factory)
|
session = scoped_session(self.session_factory)
|
||||||
try:
|
try:
|
||||||
|
print('[rm] saveBidInSession 30')
|
||||||
self.saveBidInSession(bid_id, bid, session)
|
self.saveBidInSession(bid_id, bid, session)
|
||||||
session.commit()
|
session.commit()
|
||||||
if bid.state and bid.state > BidStates.BID_RECEIVED and bid.state < BidStates.SWAP_COMPLETED:
|
if bid.state and bid.state > BidStates.BID_RECEIVED and bid.state < BidStates.SWAP_COMPLETED:
|
||||||
@ -3942,3 +4173,45 @@ class BasicSwap(BaseApp):
|
|||||||
session.close()
|
session.close()
|
||||||
session.remove()
|
session.remove()
|
||||||
self.mxDB.release()
|
self.mxDB.release()
|
||||||
|
|
||||||
|
def addLockRefundSigs(self, xmr_swap, ci):
|
||||||
|
self.log.debug('Setting lock refund tx sigs')
|
||||||
|
witness_stack = [
|
||||||
|
b'',
|
||||||
|
xmr_swap.al_lock_refund_tx_sig,
|
||||||
|
xmr_swap.af_lock_refund_tx_sig,
|
||||||
|
b'',
|
||||||
|
xmr_swap.a_lock_tx_script,
|
||||||
|
]
|
||||||
|
|
||||||
|
signed_tx = ci.setTxSignature(xmr_swap.a_lock_refund_tx, witness_stack)
|
||||||
|
assert(signed_tx), 'setTxSignature failed'
|
||||||
|
xmr_swap.a_lock_refund_tx = signed_tx
|
||||||
|
|
||||||
|
def createCoinALockRefundSwipeTx(self, ci, bid, offer, xmr_swap, xmr_offer):
|
||||||
|
self.log.debug('Creating %s lock refund swipe tx', ci.coin_name())
|
||||||
|
|
||||||
|
pkh_dest = ci.decodeAddress(self.getReceiveAddressForCoin(ci.coin_type()))
|
||||||
|
spend_tx = ci.createScriptLockRefundSpendToFTx(
|
||||||
|
xmr_swap.a_lock_refund_tx, xmr_swap.a_lock_refund_tx_script,
|
||||||
|
pkh_dest,
|
||||||
|
xmr_offer.a_fee_rate
|
||||||
|
)
|
||||||
|
|
||||||
|
vkaf = self.getPathKey(offer.coin_from, offer.coin_to, bid.created_at, xmr_swap.contract_count, 3)
|
||||||
|
sig = ci.signTx(vkaf, spend_tx, 0, xmr_swap.a_lock_refund_tx_script, xmr_swap.a_swap_refund_value)
|
||||||
|
|
||||||
|
witness_stack = [
|
||||||
|
sig,
|
||||||
|
b'',
|
||||||
|
xmr_swap.a_lock_refund_tx_script,
|
||||||
|
]
|
||||||
|
|
||||||
|
xmr_swap.a_lock_refund_swipe_tx = ci.setTxSignature(spend_tx, witness_stack)
|
||||||
|
|
||||||
|
def setBidDebugInd(self, bid_id, debug_ind):
|
||||||
|
self.log.debug('Bid %s Setting debug flag: %s', bid_id.hex(), debug_ind)
|
||||||
|
bid = self.getBid(bid_id)
|
||||||
|
bid.debug_ind = debug_ind
|
||||||
|
print('[rm] saveBid 31')
|
||||||
|
self.saveBid(bid_id, bid)
|
||||||
|
@ -16,7 +16,7 @@ class Coins(IntEnum):
|
|||||||
PART = 1
|
PART = 1
|
||||||
BTC = 2
|
BTC = 2
|
||||||
LTC = 3
|
LTC = 3
|
||||||
#DCR = 4
|
# DCR = 4
|
||||||
NMC = 5
|
NMC = 5
|
||||||
XMR = 6
|
XMR = 6
|
||||||
|
|
||||||
@ -190,6 +190,6 @@ chainparams = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class CoinInterface:
|
class CoinInterface:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -105,11 +105,15 @@ class Bid(Base):
|
|||||||
|
|
||||||
state_note = sa.Column(sa.String)
|
state_note = sa.Column(sa.String)
|
||||||
|
|
||||||
|
debug_ind = sa.Column(sa.Integer)
|
||||||
|
|
||||||
initiate_tx = None
|
initiate_tx = None
|
||||||
participate_tx = None
|
participate_tx = None
|
||||||
xmr_a_lock_tx = None
|
xmr_a_lock_tx = None
|
||||||
xmr_b_lock_tx = None
|
|
||||||
xmr_a_lock_spend_tx = None
|
xmr_a_lock_spend_tx = None
|
||||||
|
xmr_b_lock_tx = None # TODO: Can't move to txns due to error: Exception UPDATE statement on table expected to update 1 row(s); 0 were matched
|
||||||
|
|
||||||
|
txns = {}
|
||||||
|
|
||||||
def getITxState(self):
|
def getITxState(self):
|
||||||
if self.initiate_tx is None:
|
if self.initiate_tx is None:
|
||||||
@ -273,18 +277,23 @@ class XmrSwap(Base):
|
|||||||
|
|
||||||
a_lock_refund_tx = sa.Column(sa.LargeBinary)
|
a_lock_refund_tx = sa.Column(sa.LargeBinary)
|
||||||
a_lock_refund_tx_script = sa.Column(sa.LargeBinary)
|
a_lock_refund_tx_script = sa.Column(sa.LargeBinary)
|
||||||
|
a_lock_refund_tx_id = sa.Column(sa.LargeBinary)
|
||||||
a_swap_refund_value = sa.Column(sa.BigInteger)
|
a_swap_refund_value = sa.Column(sa.BigInteger)
|
||||||
|
al_lock_refund_tx_sig = sa.Column(sa.LargeBinary)
|
||||||
|
af_lock_refund_tx_sig = sa.Column(sa.LargeBinary)
|
||||||
|
|
||||||
a_lock_refund_spend_tx = sa.Column(sa.LargeBinary)
|
a_lock_refund_spend_tx = sa.Column(sa.LargeBinary)
|
||||||
|
a_lock_refund_spend_tx_id = sa.Column(sa.LargeBinary)
|
||||||
|
|
||||||
af_lock_refund_spend_tx_esig = 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_spend_tx_sig = sa.Column(sa.LargeBinary)
|
||||||
af_lock_refund_tx_sig = sa.Column(sa.LargeBinary)
|
|
||||||
|
|
||||||
a_lock_spend_tx = sa.Column(sa.LargeBinary)
|
a_lock_spend_tx = sa.Column(sa.LargeBinary)
|
||||||
a_lock_spend_tx_id = sa.Column(sa.LargeBinary)
|
a_lock_spend_tx_id = sa.Column(sa.LargeBinary)
|
||||||
al_lock_spend_tx_esig = sa.Column(sa.LargeBinary)
|
al_lock_spend_tx_esig = sa.Column(sa.LargeBinary)
|
||||||
|
|
||||||
|
a_lock_refund_swipe_tx = sa.Column(sa.LargeBinary) # Follower spends script coin lock refund tx
|
||||||
|
|
||||||
b_lock_tx_id = sa.Column(sa.LargeBinary)
|
b_lock_tx_id = 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
|
||||||
|
@ -11,7 +11,6 @@ import logging
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from basicswap.contrib.test_framework import segwit_addr
|
from basicswap.contrib.test_framework import segwit_addr
|
||||||
|
|
||||||
|
|
||||||
from .util import (
|
from .util import (
|
||||||
decodeScriptNum,
|
decodeScriptNum,
|
||||||
getCompactSizeLen,
|
getCompactSizeLen,
|
||||||
@ -42,8 +41,7 @@ from .contrib.test_framework.messages import (
|
|||||||
CTxIn,
|
CTxIn,
|
||||||
CTxInWitness,
|
CTxInWitness,
|
||||||
CTxOut,
|
CTxOut,
|
||||||
FromHex,
|
FromHex)
|
||||||
ToHex)
|
|
||||||
|
|
||||||
from .contrib.test_framework.script import (
|
from .contrib.test_framework.script import (
|
||||||
CScript,
|
CScript,
|
||||||
@ -110,7 +108,8 @@ class BTCInterface(CoinInterface):
|
|||||||
rv += output.nValue
|
rv += output.nValue
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def compareFeeRates(self, a, b):
|
@staticmethod
|
||||||
|
def compareFeeRates(a, b):
|
||||||
return abs(a - b) < 20
|
return abs(a - b) < 20
|
||||||
|
|
||||||
def __init__(self, coin_settings, network):
|
def __init__(self, coin_settings, network):
|
||||||
@ -119,6 +118,9 @@ class BTCInterface(CoinInterface):
|
|||||||
self._network = network
|
self._network = network
|
||||||
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
||||||
|
|
||||||
|
def coin_name(self):
|
||||||
|
return chainparams[self.coin_type()]['name']
|
||||||
|
|
||||||
def testDaemonRPC(self):
|
def testDaemonRPC(self):
|
||||||
self.rpc_callback('getwalletinfo', [])
|
self.rpc_callback('getwalletinfo', [])
|
||||||
|
|
||||||
@ -330,10 +332,10 @@ class BTCInterface(CoinInterface):
|
|||||||
|
|
||||||
return tx.serialize(), refund_script, tx.vout[0].nValue
|
return tx.serialize(), refund_script, tx.vout[0].nValue
|
||||||
|
|
||||||
def createScriptLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, Kal, tx_fee_rate):
|
def createScriptLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate):
|
||||||
# Returns the coinA locked coin to the leader
|
# Returns the coinA locked coin to the leader
|
||||||
# The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey
|
# The follower will sign the multisig path with a signature encumbered by the leader's coinB spend pubkey
|
||||||
# When the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower
|
# If the leader publishes the decrypted signature the leader's coinB spend privatekey will be revealed to the follower
|
||||||
|
|
||||||
tx_lock_refund = self.loadTx(tx_lock_refund_bytes)
|
tx_lock_refund = self.loadTx(tx_lock_refund_bytes)
|
||||||
|
|
||||||
@ -349,9 +351,8 @@ class BTCInterface(CoinInterface):
|
|||||||
tx.nVersion = self.txVersion()
|
tx.nVersion = self.txVersion()
|
||||||
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), nSequence=0))
|
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n), nSequence=0))
|
||||||
|
|
||||||
#pubkeyhash = hash160(self.encodePubkey(Kal))
|
#pubkeyhash = hash160(Kal)
|
||||||
pubkeyhash = hash160(Kal)
|
tx.vout.append(self.txoType(locked_coin, CScript([OP_0, pkh_refund_to])))
|
||||||
tx.vout.append(self.txoType(locked_coin, CScript([OP_0, pubkeyhash])))
|
|
||||||
|
|
||||||
witness_bytes = len(script_lock_refund)
|
witness_bytes = len(script_lock_refund)
|
||||||
witness_bytes += 73 * 2 # 2 signatures (72 + 1 byte size)
|
witness_bytes += 73 * 2 # 2 signatures (72 + 1 byte size)
|
||||||
@ -367,8 +368,11 @@ class BTCInterface(CoinInterface):
|
|||||||
|
|
||||||
return tx.serialize()
|
return tx.serialize()
|
||||||
|
|
||||||
def createScriptLockRefundSpendToFTx(self, tx_lock_refund, script_lock_refund, pkh_dest, tx_fee_rate):
|
def createScriptLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate):
|
||||||
# Sends the coinA locked coin to the follower
|
# Sends the coinA locked coin to the follower
|
||||||
|
|
||||||
|
tx_lock_refund = self.loadTx(tx_lock_refund_bytes)
|
||||||
|
|
||||||
output_script = CScript([OP_0, hashlib.sha256(script_lock_refund).digest()])
|
output_script = CScript([OP_0, hashlib.sha256(script_lock_refund).digest()])
|
||||||
locked_n = findOutput(tx_lock_refund, output_script)
|
locked_n = findOutput(tx_lock_refund, 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')
|
||||||
@ -575,10 +579,11 @@ 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(Kal)])
|
# Destination doesn't matter to the follower
|
||||||
locked_n = findOutput(tx, p2wpkh)
|
#p2wpkh = CScript([OP_0, hash160(Kal)])
|
||||||
assert_cond(locked_n is not None, 'Output not found in lock refund spend tx')
|
#locked_n = findOutput(tx, p2wpkh)
|
||||||
tx_value = tx.vout[locked_n].nValue
|
#assert_cond(locked_n is not None, 'Output not found in lock refund spend tx')
|
||||||
|
tx_value = tx.vout[0].nValue
|
||||||
|
|
||||||
fee_paid = prevout_value - tx_value
|
fee_paid = prevout_value - tx_value
|
||||||
assert(fee_paid > 0)
|
assert(fee_paid > 0)
|
||||||
@ -661,27 +666,28 @@ class BTCInterface(CoinInterface):
|
|||||||
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 ecdsaotves_enc_sign(key_sign, pubkey_encrypt, sig_hash)
|
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):
|
def verifyTxOtVES(self, tx_bytes, ct, Ks, Ke, prevout_n, prevout_script, prevout_value):
|
||||||
tx = self.loadTx(tx_bytes)
|
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 ecdsaotves_enc_verify(Ks, Ke, sig_hash, sig)
|
return ecdsaotves_enc_verify(Ks, Ke, sig_hash, ct)
|
||||||
#return otves.EncVrfy(Ks, Ke, sig_hash, sig)
|
|
||||||
|
|
||||||
def decryptOtVES(self, k, esig):
|
def decryptOtVES(self, k, esig):
|
||||||
return ecdsaotves_dec_sig(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)
|
||||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
|
sig_hash = SegwitV0SignatureHash(prevout_script, tx, prevout_n, SIGHASH_ALL, prevout_value)
|
||||||
|
|
||||||
ecK = ECPubKey()
|
ecK = ECPubKey()
|
||||||
#ecK.set_int(K.x(), K.y())
|
|
||||||
ecK.set(K)
|
ecK.set(K)
|
||||||
return ecK.verify_ecdsa(sig[: -1], sig_hash) # Pop the hashtype byte
|
return ecK.verify_ecdsa(sig[: -1], sig_hash) # Pop the hashtype byte
|
||||||
|
|
||||||
|
def verifySig(self, pubkey, signed_hash, sig):
|
||||||
|
ecK = ECPubKey()
|
||||||
|
ecK.set(pubkey)
|
||||||
|
return ecK.verify_ecdsa(sig, signed_hash)
|
||||||
|
|
||||||
def fundTx(self, tx, feerate):
|
def fundTx(self, tx, feerate):
|
||||||
feerate_str = format_amount(feerate, self.exp())
|
feerate_str = format_amount(feerate, self.exp())
|
||||||
# TODO: unlock unspents if bid cancelled
|
# TODO: unlock unspents if bid cancelled
|
||||||
|
@ -10,7 +10,7 @@ from .contrib.test_framework.messages import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from .interface_btc import BTCInterface
|
from .interface_btc import BTCInterface
|
||||||
from .chainparams import CoinInterface, Coins
|
from .chainparams import Coins
|
||||||
from .rpc import make_rpc_func
|
from .rpc import make_rpc_func
|
||||||
|
|
||||||
|
|
||||||
|
@ -280,7 +280,6 @@ class XMRInterface(CoinInterface):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def spendBLockTx(self, address_to, kbv, kbs, cb_swap_value, b_fee_rate, restore_height):
|
def spendBLockTx(self, address_to, kbv, kbs, cb_swap_value, b_fee_rate, restore_height):
|
||||||
|
|
||||||
Kbv = self.getPubkey(kbv)
|
Kbv = self.getPubkey(kbv)
|
||||||
|
@ -82,4 +82,3 @@ def make_xmr_wallet_rpc_func(port, auth):
|
|||||||
nonlocal port, auth
|
nonlocal port, auth
|
||||||
return callrpc_xmr(port, auth, method, params)
|
return callrpc_xmr(port, auth, method, params)
|
||||||
return rpc_func
|
return rpc_func
|
||||||
|
|
||||||
|
@ -128,6 +128,8 @@ def prepareCore(coin, version, settings, data_dir):
|
|||||||
if coin == 'monero':
|
if coin == 'monero':
|
||||||
url = 'https://downloads.getmonero.org/cli/monero-linux-x64-v${}.tar.bz2'.format(version)
|
url = 'https://downloads.getmonero.org/cli/monero-linux-x64-v${}.tar.bz2'.format(version)
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
|
||||||
release_path = os.path.join(bin_dir, release_filename)
|
release_path = os.path.join(bin_dir, release_filename)
|
||||||
if not os.path.exists(release_path):
|
if not os.path.exists(release_path):
|
||||||
downloadFile(release_url, release_path)
|
downloadFile(release_url, release_path)
|
||||||
|
6
doc/notes.md
Normal file
6
doc/notes.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
## Run One Test
|
||||||
|
|
||||||
|
```
|
||||||
|
python setup.py test -s tests.basicswap.test_xmr.Test.test_02_leader_recover_a_lock_tx
|
||||||
|
```
|
@ -6,6 +6,7 @@
|
|||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
import secrets
|
||||||
|
|
||||||
import basicswap.contrib.ed25519_fast as edf
|
import basicswap.contrib.ed25519_fast as edf
|
||||||
import basicswap.ed25519_fast_util as edu
|
import basicswap.ed25519_fast_util as edu
|
||||||
@ -13,6 +14,14 @@ import basicswap.ed25519_fast_util as edu
|
|||||||
from basicswap.ecc_util import i2b
|
from basicswap.ecc_util import i2b
|
||||||
from coincurve.ed25519 import ed25519_get_pubkey
|
from coincurve.ed25519 import ed25519_get_pubkey
|
||||||
|
|
||||||
|
from coincurve.ecdsaotves import (
|
||||||
|
ecdsaotves_enc_sign,
|
||||||
|
ecdsaotves_enc_verify,
|
||||||
|
ecdsaotves_dec_sig,
|
||||||
|
ecdsaotves_rec_enc_key)
|
||||||
|
|
||||||
|
from basicswap.interface_btc import BTCInterface
|
||||||
|
from basicswap.interface_xmr import XMRInterface
|
||||||
|
|
||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
SerialiseNum,
|
SerialiseNum,
|
||||||
@ -30,6 +39,9 @@ from basicswap.basicswap import (
|
|||||||
SEQUENCE_LOCK_TIME,
|
SEQUENCE_LOCK_TIME,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from basicswap.ecc_util import (
|
||||||
|
i2b)
|
||||||
|
|
||||||
|
|
||||||
class Test(unittest.TestCase):
|
class Test(unittest.TestCase):
|
||||||
def test_serialise_num(self):
|
def test_serialise_num(self):
|
||||||
@ -152,6 +164,36 @@ class Test(unittest.TestCase):
|
|||||||
pubkey_test = ed25519_get_pubkey(privkey_bytes)
|
pubkey_test = ed25519_get_pubkey(privkey_bytes)
|
||||||
assert(pubkey == pubkey_test)
|
assert(pubkey == pubkey_test)
|
||||||
|
|
||||||
|
def test_ecdsa_otves(self):
|
||||||
|
coin_settings = {'rpcport': 0, 'rpcauth': 'none', 'blocks_confirmed': 1}
|
||||||
|
ci = BTCInterface(coin_settings, 'regtest')
|
||||||
|
vk_sign = i2b(ci.getNewSecretKey())
|
||||||
|
vk_encrypt = i2b(ci.getNewSecretKey())
|
||||||
|
|
||||||
|
pk_sign = ci.getPubkey(vk_sign)
|
||||||
|
pk_encrypt = ci.getPubkey(vk_encrypt)
|
||||||
|
sign_hash = secrets.token_bytes(32)
|
||||||
|
|
||||||
|
cipher_text = ecdsaotves_enc_sign(vk_sign, pk_encrypt, sign_hash)
|
||||||
|
|
||||||
|
assert(ecdsaotves_enc_verify(pk_sign, pk_encrypt, sign_hash, cipher_text))
|
||||||
|
|
||||||
|
sig = ecdsaotves_dec_sig(vk_encrypt, cipher_text)
|
||||||
|
|
||||||
|
assert(ci.verifySig(pk_sign, sign_hash, sig))
|
||||||
|
|
||||||
|
recovered_key = ecdsaotves_rec_enc_key(pk_encrypt, cipher_text, sig)
|
||||||
|
|
||||||
|
assert(vk_encrypt == recovered_key)
|
||||||
|
|
||||||
|
def test_dleag(self):
|
||||||
|
coin_settings = {'rpcport': 0, 'walletrpcport': 0, 'walletrpcauth': 'none', 'blocks_confirmed': 1}
|
||||||
|
ci = XMRInterface(coin_settings, 'regtest')
|
||||||
|
|
||||||
|
key = i2b(ci.getNewSecretKey())
|
||||||
|
proof = ci.proveDLEAG(key)
|
||||||
|
assert(ci.verifyDLEAG(proof))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import shutil
|
import shutil
|
||||||
@ -17,14 +16,6 @@ import traceback
|
|||||||
import threading
|
import threading
|
||||||
import subprocess
|
import subprocess
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
from coincurve.ecdsaotves import (
|
|
||||||
ecdsaotves_enc_sign,
|
|
||||||
ecdsaotves_enc_verify,
|
|
||||||
ecdsaotves_dec_sig,
|
|
||||||
ecdsaotves_rec_enc_key)
|
|
||||||
from coincurve.dleag import (
|
|
||||||
dleag_prove,
|
|
||||||
dleag_verify)
|
|
||||||
|
|
||||||
import basicswap.config as cfg
|
import basicswap.config as cfg
|
||||||
from basicswap.basicswap import (
|
from basicswap.basicswap import (
|
||||||
@ -33,6 +24,7 @@ from basicswap.basicswap import (
|
|||||||
SwapTypes,
|
SwapTypes,
|
||||||
BidStates,
|
BidStates,
|
||||||
TxStates,
|
TxStates,
|
||||||
|
DebugTypes,
|
||||||
SEQUENCE_LOCK_BLOCKS,
|
SEQUENCE_LOCK_BLOCKS,
|
||||||
)
|
)
|
||||||
from basicswap.util import (
|
from basicswap.util import (
|
||||||
@ -201,7 +193,7 @@ def prepare_swapclient_dir(datadir, node_id, network_key, network_pubkey):
|
|||||||
'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',
|
||||||
'htmlport': 12700 + node_id,
|
'htmlport': TEST_HTTP_PORT + node_id,
|
||||||
'network_key': network_key,
|
'network_key': network_key,
|
||||||
'network_pubkey': network_pubkey,
|
'network_pubkey': network_pubkey,
|
||||||
'chainclients': {
|
'chainclients': {
|
||||||
@ -303,6 +295,7 @@ def callnoderpc(node_id, method, params=[], wallet=None, base_rpc_port=BASE_RPC_
|
|||||||
auth = 'test{0}:test_pass{0}'.format(node_id)
|
auth = 'test{0}:test_pass{0}'.format(node_id)
|
||||||
return callrpc(base_rpc_port + node_id, auth, method, params, wallet)
|
return callrpc(base_rpc_port + node_id, auth, method, params, wallet)
|
||||||
|
|
||||||
|
|
||||||
def run_coins_loop(cls):
|
def run_coins_loop(cls):
|
||||||
global stop_test
|
global stop_test
|
||||||
while not stop_test:
|
while not stop_test:
|
||||||
@ -313,6 +306,7 @@ def run_coins_loop(cls):
|
|||||||
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1})
|
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1})
|
||||||
time.sleep(1.0)
|
time.sleep(1.0)
|
||||||
|
|
||||||
|
|
||||||
def run_loop(cls):
|
def run_loop(cls):
|
||||||
global stop_test
|
global stop_test
|
||||||
while not stop_test:
|
while not stop_test:
|
||||||
@ -547,8 +541,6 @@ class Test(unittest.TestCase):
|
|||||||
|
|
||||||
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 100 * COIN, 10 * XMR_COIN, 100 * COIN, SwapTypes.XMR_SWAP)
|
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 100 * COIN, 10 * XMR_COIN, 100 * COIN, SwapTypes.XMR_SWAP)
|
||||||
self.wait_for_offer(swap_clients[1], offer_id)
|
self.wait_for_offer(swap_clients[1], offer_id)
|
||||||
offer = swap_clients[1].getOffer(offer_id)
|
|
||||||
|
|
||||||
offers = swap_clients[1].listOffers(filters={'offer_id': offer_id})
|
offers = swap_clients[1].listOffers(filters={'offer_id': offer_id})
|
||||||
assert(len(offers) == 1)
|
assert(len(offers) == 1)
|
||||||
offer = offers[0]
|
offer = offers[0]
|
||||||
@ -572,7 +564,106 @@ class Test(unittest.TestCase):
|
|||||||
self.wait_for_bid(swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=180)
|
self.wait_for_bid(swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=180)
|
||||||
self.wait_for_bid(swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True)
|
self.wait_for_bid(swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True)
|
||||||
|
|
||||||
|
def test_02_leader_recover_a_lock_tx(self):
|
||||||
|
logging.info('---------- Test PART to XMR leader recovers coin a lock tx')
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
|
||||||
|
js_w0_before = json.loads(urlopen('http://localhost:1800/json/wallets').read())
|
||||||
|
|
||||||
|
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 101 * COIN, 1.1 * XMR_COIN, 101 * COIN, SwapTypes.XMR_SWAP,
|
||||||
|
lock_type=SEQUENCE_LOCK_BLOCKS, lock_value=12)
|
||||||
|
self.wait_for_offer(swap_clients[1], offer_id)
|
||||||
|
offer = swap_clients[1].getOffer(offer_id)
|
||||||
|
|
||||||
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||||
|
|
||||||
|
self.wait_for_bid(swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||||
|
|
||||||
|
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||||
|
assert(xmr_swap)
|
||||||
|
|
||||||
|
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
|
||||||
|
|
||||||
|
swap_clients[0].acceptXmrBid(bid_id)
|
||||||
|
|
||||||
|
self.wait_for_bid(swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180)
|
||||||
|
self.wait_for_bid(swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True)
|
||||||
|
|
||||||
|
js_w0_after = json.loads(urlopen('http://localhost:1800/json/wallets').read())
|
||||||
|
print('[rm] js_w0_before', json.dumps(js_w0_before))
|
||||||
|
print('[rm] js_w0_after', json.dumps(js_w0_after))
|
||||||
|
|
||||||
|
def test_03_follower_recover_a_lock_tx(self):
|
||||||
|
logging.info('---------- Test PART to XMR follower recovers coin a lock tx')
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
|
||||||
|
js_w0_before = json.loads(urlopen('http://localhost:1800/json/wallets').read())
|
||||||
|
|
||||||
|
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 101 * COIN, 1.1 * XMR_COIN, 101 * COIN, SwapTypes.XMR_SWAP,
|
||||||
|
lock_type=SEQUENCE_LOCK_BLOCKS, lock_value=12)
|
||||||
|
self.wait_for_offer(swap_clients[1], offer_id)
|
||||||
|
offer = swap_clients[1].getOffer(offer_id)
|
||||||
|
|
||||||
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||||
|
|
||||||
|
self.wait_for_bid(swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||||
|
|
||||||
|
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||||
|
assert(xmr_swap)
|
||||||
|
|
||||||
|
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
|
||||||
|
swap_clients[0].setBidDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND)
|
||||||
|
|
||||||
|
swap_clients[0].acceptXmrBid(bid_id)
|
||||||
|
|
||||||
|
self.wait_for_bid(swap_clients[0], bid_id, BidStates.BID_ABANDONED, wait_for=180)
|
||||||
|
self.wait_for_bid(swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_SWIPED, sent=True)
|
||||||
|
|
||||||
|
js_w0_after = json.loads(urlopen('http://localhost:1800/json/wallets').read())
|
||||||
|
|
||||||
|
def test_04_follower_recover_b_lock_tx(self):
|
||||||
|
logging.info('---------- Test PART to XMR follower recovers coin b lock tx')
|
||||||
|
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
|
||||||
|
offer_id = swap_clients[0].postOffer(Coins.PART, Coins.XMR, 101 * COIN, 1.1 * XMR_COIN, 101 * COIN, SwapTypes.XMR_SWAP,
|
||||||
|
lock_type=SEQUENCE_LOCK_BLOCKS, lock_value=18)
|
||||||
|
self.wait_for_offer(swap_clients[1], offer_id)
|
||||||
|
offer = swap_clients[1].getOffer(offer_id)
|
||||||
|
|
||||||
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||||
|
|
||||||
|
self.wait_for_bid(swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||||
|
|
||||||
|
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||||
|
assert(xmr_swap)
|
||||||
|
|
||||||
|
swap_clients[1].setBidDebugInd(bid_id, DebugTypes.CREATE_INVALID_COIN_B_LOCK)
|
||||||
|
|
||||||
|
swap_clients[0].acceptXmrBid(bid_id)
|
||||||
|
|
||||||
|
self.wait_for_bid(swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=180)
|
||||||
|
self.wait_for_bid(swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=True)
|
||||||
|
|
||||||
|
def test_05_btc_xmr(self):
|
||||||
|
logging.info('---------- Test BTC to XMR')
|
||||||
|
swap_clients = self.swap_clients
|
||||||
|
offer_id = swap_clients[0].postOffer(Coins.BTC, Coins.XMR, 10 * COIN, 100 * XMR_COIN, 100 * COIN, SwapTypes.XMR_SWAP)
|
||||||
|
self.wait_for_offer(swap_clients[1], offer_id)
|
||||||
|
offers = swap_clients[1].listOffers(filters={'offer_id': offer_id})
|
||||||
|
offer = offers[0]
|
||||||
|
|
||||||
|
bid_id = swap_clients[1].postXmrBid(offer_id, offer.amount_from)
|
||||||
|
|
||||||
|
self.wait_for_bid(swap_clients[0], bid_id, BidStates.BID_RECEIVED)
|
||||||
|
|
||||||
|
bid, xmr_swap = swap_clients[0].getXmrBid(bid_id)
|
||||||
|
assert(xmr_swap)
|
||||||
|
|
||||||
|
swap_clients[0].acceptXmrBid(bid_id)
|
||||||
|
|
||||||
|
self.wait_for_bid(swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=180)
|
||||||
|
self.wait_for_bid(swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
Loading…
Reference in New Issue
Block a user