mirror of
https://github.com/basicswap/basicswap.git
synced 2025-03-21 14:48:57 +00:00
Detect BCH mercy txn.
This commit is contained in:
parent
1a085ec97b
commit
92c7cb7223
5 changed files with 79 additions and 18 deletions
|
@ -3828,10 +3828,13 @@ class BasicSwap(BaseApp):
|
||||||
if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns:
|
if TxTypes.XMR_SWAP_A_LOCK_REFUND in bid.txns:
|
||||||
refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND]
|
refund_tx = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND]
|
||||||
if was_received:
|
if was_received:
|
||||||
if bid.debug_ind == DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND:
|
if bid.debug_ind in (DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND2):
|
||||||
self.log.debug('Adaptor-sig bid %s: Stalling bid for testing: %d.', bid_id.hex(), bid.debug_ind)
|
self.log.debug('Adaptor-sig bid %s: Stalling bid for testing: %d.', bid_id.hex(), bid.debug_ind)
|
||||||
bid.setState(BidStates.BID_STALLED_FOR_TEST)
|
if bid.debug_ind == DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND:
|
||||||
rv = True
|
bid.setState(BidStates.BID_STALLED_FOR_TEST)
|
||||||
|
rv = True
|
||||||
|
else:
|
||||||
|
bid.setState(BidStates.BID_STALLED_FOR_TEST_TYPE2)
|
||||||
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session)
|
self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
@ -4055,10 +4058,16 @@ class BasicSwap(BaseApp):
|
||||||
self.log.debug('getrawtransaction lock spend tx failed: %s', str(e))
|
self.log.debug('getrawtransaction lock spend tx failed: %s', str(e))
|
||||||
elif state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
|
elif state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
|
||||||
if was_received and self.countQueuedActions(session, bid_id, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B) < 1:
|
if was_received and self.countQueuedActions(session, bid_id, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B) < 1:
|
||||||
bid.setState(BidStates.SWAP_DELAYING)
|
if bid.debug_ind == DebugTypes.BID_DONT_SPEND_COIN_B_LOCK:
|
||||||
delay = self.get_delay_event_seconds()
|
self.log.debug('Adaptor-sig bid %s: Stalling bid for testing: %d.', bid_id.hex(), bid.debug_ind)
|
||||||
self.log.info('Redeeming coin b lock tx for bid %s in %d seconds', bid_id.hex(), delay)
|
bid.setState(BidStates.BID_STALLED_FOR_TEST_TYPE2) # If BID_STALLED_FOR_TEST is set process_XMR_SWAP_A_LOCK_tx_spend would fail
|
||||||
self.createActionInSession(delay, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, session)
|
|
||||||
|
self.logBidEvent(bid.bid_id, EventLogTypes.DEBUG_TWEAK_APPLIED, 'ind {}'.format(bid.debug_ind), session)
|
||||||
|
else:
|
||||||
|
bid.setState(BidStates.SWAP_DELAYING)
|
||||||
|
delay = self.get_delay_event_seconds()
|
||||||
|
self.log.info('Redeeming coin b lock tx for bid %s in %d seconds', bid_id.hex(), delay)
|
||||||
|
self.createActionInSession(delay, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, session)
|
||||||
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
session.commit()
|
session.commit()
|
||||||
elif state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED:
|
elif state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED:
|
||||||
|
@ -4464,6 +4473,10 @@ class BasicSwap(BaseApp):
|
||||||
xmr_swap.a_lock_refund_spend_tx = tx.serialize_without_witness()
|
xmr_swap.a_lock_refund_spend_tx = tx.serialize_without_witness()
|
||||||
xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_spend_tx)
|
xmr_swap.a_lock_refund_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_refund_spend_tx)
|
||||||
|
|
||||||
|
if was_received:
|
||||||
|
_, out_1, _, _, _ = ci_from.extractScriptLockScriptValues(xmr_swap.a_lock_refund_tx_script)
|
||||||
|
self.addWatchedScript(ci_from.coin_type(), bid_id, out_1, TxTypes.BCH_MERCY)
|
||||||
|
|
||||||
bid.setState(BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND)
|
bid.setState(BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND)
|
||||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_TX_SEEN, '', use_session)
|
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_REFUND_TX_SEEN, '', use_session)
|
||||||
else:
|
else:
|
||||||
|
@ -4587,6 +4600,34 @@ class BasicSwap(BaseApp):
|
||||||
|
|
||||||
self.removeWatchedScript(coin_type, watched_script.bid_id, watched_script.script)
|
self.removeWatchedScript(coin_type, watched_script.bid_id, watched_script.script)
|
||||||
|
|
||||||
|
def processMercyTx(self, coin_type, watched_script, txid: bytes, vout: int, tx) -> None:
|
||||||
|
bid_id = watched_script.bid_id
|
||||||
|
self.log.warning('Found mercy tx for bid: {}'.format(bid_id.hex()))
|
||||||
|
|
||||||
|
self.logBidEvent(bid_id, EventLogTypes.BCH_MERCY_TX_FOUND, txid.hex(), session=None)
|
||||||
|
|
||||||
|
if bid_id not in self.swaps_in_progress:
|
||||||
|
self.log.warning('Could not find active bid for found mercy tx: {}'.format(bid_id.hex()))
|
||||||
|
else:
|
||||||
|
remote_keyshare = bytes.fromhex(tx['vout'][0]['scriptPubKey']['asm'].split(' ')[2])
|
||||||
|
ci = self.ci(coin_type)
|
||||||
|
ensure(ci.verifyKey(remote_keyshare), 'Invalid keyshare')
|
||||||
|
|
||||||
|
bid = self.swaps_in_progress[bid_id][0]
|
||||||
|
bid.txns[TxTypes.BCH_MERCY] = SwapTx(
|
||||||
|
bid_id=bid_id,
|
||||||
|
tx_type=TxTypes.BCH_MERCY,
|
||||||
|
txid=txid,
|
||||||
|
tx_data=remote_keyshare,
|
||||||
|
)
|
||||||
|
self.saveBid(bid_id, bid)
|
||||||
|
|
||||||
|
delay = self.get_delay_event_seconds()
|
||||||
|
self.log.info('Redeeming coin b lock tx for bid %s in %d seconds', bid_id.hex(), delay)
|
||||||
|
self.createAction(delay, ActionTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id)
|
||||||
|
|
||||||
|
self.removeWatchedScript(coin_type, bid_id, watched_script.script)
|
||||||
|
|
||||||
def checkNewBlock(self, coin_type, c):
|
def checkNewBlock(self, coin_type, c):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -4690,7 +4731,10 @@ class BasicSwap(BaseApp):
|
||||||
# TODO: Optimise by loading rawtx in CTransaction
|
# TODO: Optimise by loading rawtx in CTransaction
|
||||||
if bytes.fromhex(txo['scriptPubKey']['hex']) == s.script:
|
if bytes.fromhex(txo['scriptPubKey']['hex']) == s.script:
|
||||||
self.log.debug('Found script from search for bid %s: %s %d', s.bid_id.hex(), tx['txid'], i)
|
self.log.debug('Found script from search for bid %s: %s %d', s.bid_id.hex(), tx['txid'], i)
|
||||||
self.processFoundScript(coin_type, s, bytes.fromhex(tx['txid']), i)
|
if s.tx_type == TxTypes.BCH_MERCY:
|
||||||
|
self.processMercyTx(coin_type, s, bytes.fromhex(tx['txid']), i, tx)
|
||||||
|
else:
|
||||||
|
self.processFoundScript(coin_type, s, bytes.fromhex(tx['txid']), i, tx)
|
||||||
|
|
||||||
for o in c['watched_outputs']:
|
for o in c['watched_outputs']:
|
||||||
for i, inp in enumerate(tx['vin']):
|
for i, inp in enumerate(tx['vin']):
|
||||||
|
@ -6105,7 +6149,13 @@ 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)
|
||||||
|
|
||||||
kbsf = ci_from.recoverEncKey(xmr_swap.al_lock_spend_tx_esig, xmr_swap.al_lock_spend_tx_sig, xmr_swap.pkasf)
|
if TxTypes.BCH_MERCY in bid.txns:
|
||||||
|
self.log.info('Using keyshare from mercy tx.')
|
||||||
|
kbsf = bid.txns[TxTypes.BCH_MERCY].tx_data
|
||||||
|
pkbsf = ci_to.getPubkey(kbsf)
|
||||||
|
ensure(pkbsf == xmr_swap.pkbsf, 'Keyshare from mercy tx does not match expected pubkey')
|
||||||
|
else:
|
||||||
|
kbsf = ci_from.recoverEncKey(xmr_swap.al_lock_spend_tx_esig, xmr_swap.al_lock_spend_tx_sig, xmr_swap.pkasf)
|
||||||
assert (kbsf is not None)
|
assert (kbsf is not None)
|
||||||
|
|
||||||
for_ed25519: bool = True if ci_to.curve_type() == Curves.ed25519 else False
|
for_ed25519: bool = True if ci_to.curve_type() == Curves.ed25519 else False
|
||||||
|
@ -6170,7 +6220,6 @@ class BasicSwap(BaseApp):
|
||||||
|
|
||||||
# Extract the follower's decrypted signature and use it to recover the leader's privatekey
|
# 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)
|
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)
|
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)
|
assert (kbsl is not None)
|
||||||
|
|
||||||
|
@ -6443,7 +6492,7 @@ class BasicSwap(BaseApp):
|
||||||
ensure(bid, 'Bid not found: {}.'.format(bid_id.hex()))
|
ensure(bid, 'Bid not found: {}.'.format(bid_id.hex()))
|
||||||
ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex()))
|
ensure(xmr_swap, 'Adaptor-sig swap not found: {}.'.format(bid_id.hex()))
|
||||||
|
|
||||||
if BidStates(bid.state) == BidStates.BID_STALLED_FOR_TEST:
|
if BidStates(bid.state) in (BidStates.BID_STALLED_FOR_TEST, BidStates.BID_STALLED_FOR_TEST_TYPE2):
|
||||||
self.log.debug('Bid stalled %s', bid_id.hex())
|
self.log.debug('Bid stalled %s', bid_id.hex())
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,7 @@ class BidStates(IntEnum):
|
||||||
BID_REQUEST_SENT = 29
|
BID_REQUEST_SENT = 29
|
||||||
BID_REQUEST_ACCEPTED = 30
|
BID_REQUEST_ACCEPTED = 30
|
||||||
BID_EXPIRED = 31
|
BID_EXPIRED = 31
|
||||||
|
BID_STALLED_FOR_TEST_TYPE2 = 32
|
||||||
|
|
||||||
|
|
||||||
class TxStates(IntEnum):
|
class TxStates(IntEnum):
|
||||||
|
@ -137,6 +138,8 @@ class TxTypes(IntEnum):
|
||||||
|
|
||||||
ITX_PRE_FUNDED = auto()
|
ITX_PRE_FUNDED = auto()
|
||||||
|
|
||||||
|
BCH_MERCY = auto()
|
||||||
|
|
||||||
|
|
||||||
class ActionTypes(IntEnum):
|
class ActionTypes(IntEnum):
|
||||||
ACCEPT_BID = auto()
|
ACCEPT_BID = auto()
|
||||||
|
@ -184,6 +187,7 @@ class EventLogTypes(IntEnum):
|
||||||
PTX_REDEEM_PUBLISHED = auto()
|
PTX_REDEEM_PUBLISHED = auto()
|
||||||
PTX_REFUND_PUBLISHED = auto()
|
PTX_REFUND_PUBLISHED = auto()
|
||||||
LOCK_TX_B_IN_MEMPOOL = auto()
|
LOCK_TX_B_IN_MEMPOOL = auto()
|
||||||
|
BCH_MERCY_TX_FOUND = auto()
|
||||||
|
|
||||||
|
|
||||||
class XmrSplitMsgTypes(IntEnum):
|
class XmrSplitMsgTypes(IntEnum):
|
||||||
|
@ -195,6 +199,7 @@ class DebugTypes(IntEnum):
|
||||||
NONE = 0
|
NONE = 0
|
||||||
BID_STOP_AFTER_COIN_A_LOCK = auto()
|
BID_STOP_AFTER_COIN_A_LOCK = auto()
|
||||||
BID_DONT_SPEND_COIN_A_LOCK_REFUND = auto()
|
BID_DONT_SPEND_COIN_A_LOCK_REFUND = auto()
|
||||||
|
BID_DONT_SPEND_COIN_A_LOCK_REFUND2 = auto() # continues
|
||||||
CREATE_INVALID_COIN_B_LOCK = auto()
|
CREATE_INVALID_COIN_B_LOCK = auto()
|
||||||
BUYER_STOP_AFTER_ITX = auto()
|
BUYER_STOP_AFTER_ITX = auto()
|
||||||
MAKE_INVALID_PTX = auto()
|
MAKE_INVALID_PTX = auto()
|
||||||
|
@ -206,6 +211,7 @@ class DebugTypes(IntEnum):
|
||||||
DONT_CONFIRM_PTX = auto()
|
DONT_CONFIRM_PTX = auto()
|
||||||
OFFER_LOCK_2_VALUE_INC = auto()
|
OFFER_LOCK_2_VALUE_INC = auto()
|
||||||
BID_STOP_AFTER_COIN_B_LOCK = auto()
|
BID_STOP_AFTER_COIN_B_LOCK = auto()
|
||||||
|
BID_DONT_SPEND_COIN_B_LOCK = auto()
|
||||||
|
|
||||||
|
|
||||||
class NotificationTypes(IntEnum):
|
class NotificationTypes(IntEnum):
|
||||||
|
@ -445,6 +451,8 @@ def describeEventEntry(event_type, event_msg):
|
||||||
return 'Participate tx redeem tx published'
|
return 'Participate tx redeem tx published'
|
||||||
if event_type == EventLogTypes.PTX_REFUND_PUBLISHED:
|
if event_type == EventLogTypes.PTX_REFUND_PUBLISHED:
|
||||||
return 'Participate tx refund tx published'
|
return 'Participate tx refund tx published'
|
||||||
|
if event_type == EventLogTypes.BCH_MERCY_TX_FOUND:
|
||||||
|
return 'BCH mercy tx found'
|
||||||
|
|
||||||
|
|
||||||
def getVoutByAddress(txjs, p2sh):
|
def getVoutByAddress(txjs, p2sh):
|
||||||
|
|
|
@ -201,7 +201,7 @@ class BCHInterface(BTCInterface):
|
||||||
if not txid:
|
if not txid:
|
||||||
txns = self.rpc_wallet('listtransactions', ["*", 100000, 0, True])
|
txns = self.rpc_wallet('listtransactions', ["*", 100000, 0, True])
|
||||||
for tx in txns:
|
for tx in txns:
|
||||||
if self.make_int(tx['amount']) == bid_amount and tx['category'] == 'send' and tx['address'] == dest_address:
|
if self.make_int(tx['amount']) == bid_amount and tx['category'] == 'send' and tx.get('address', '_NONE_') == dest_address:
|
||||||
txid = bytes.fromhex(tx['txid'])
|
txid = bytes.fromhex(tx['txid'])
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
|
@ -564,7 +564,7 @@ class TestBCH(BasicSwapTest):
|
||||||
super().test_02_a_leader_recover_a_lock_tx()
|
super().test_02_a_leader_recover_a_lock_tx()
|
||||||
|
|
||||||
def test_03_a_follower_recover_a_lock_tx(self):
|
def test_03_a_follower_recover_a_lock_tx(self):
|
||||||
super().test_03_a_follower_recover_a_lock_tx()
|
self.do_test_03_follower_recover_a_lock_tx(self.test_coin_from, Coins.XMR, with_mercy=True)
|
||||||
|
|
||||||
def test_03_b_follower_recover_a_lock_tx_reverse(self):
|
def test_03_b_follower_recover_a_lock_tx_reverse(self):
|
||||||
self.prepare_balance(Coins.BCH, 100.0, 1801, 1800)
|
self.prepare_balance(Coins.BCH, 100.0, 1801, 1800)
|
||||||
|
|
|
@ -264,8 +264,8 @@ class TestFunctions(BaseTest):
|
||||||
# TODO: Discard block rewards
|
# TODO: Discard block rewards
|
||||||
# assert (node0_from_before - node0_from_after < 0.02)
|
# assert (node0_from_before - node0_from_after < 0.02)
|
||||||
|
|
||||||
def do_test_03_follower_recover_a_lock_tx(self, coin_from, coin_to, lock_value: int = 32):
|
def do_test_03_follower_recover_a_lock_tx(self, coin_from, coin_to, lock_value: int = 32, with_mercy: bool = False):
|
||||||
logging.info('---------- Test {} to {} follower recovers coin a lock tx'.format(coin_from.name, coin_to.name))
|
logging.info('---------- Test {} to {} follower recovers coin a lock tx{}'.format(coin_from.name, coin_to.name, ' (with mercy tx)' if with_mercy else ''))
|
||||||
|
|
||||||
# Leader is too slow to recover the coin a lock tx and follower swipes it
|
# Leader is too slow to recover the coin a lock tx and follower swipes it
|
||||||
# coin b lock tx remains unspent
|
# coin b lock tx remains unspent
|
||||||
|
@ -296,13 +296,17 @@ class TestFunctions(BaseTest):
|
||||||
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, offer.amount_from)
|
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, offer.amount_from)
|
||||||
wait_for_bid(test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED)
|
wait_for_bid(test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED)
|
||||||
|
|
||||||
swap_clients[id_follower].setBidDebugInd(bid_id, DebugTypes.BID_STOP_AFTER_COIN_A_LOCK)
|
debug_type = DebugTypes.BID_DONT_SPEND_COIN_B_LOCK if with_mercy else DebugTypes.BID_STOP_AFTER_COIN_A_LOCK
|
||||||
swap_clients[id_leader].setBidDebugInd(bid_id, DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND)
|
swap_clients[id_follower].setBidDebugInd(bid_id, debug_type)
|
||||||
|
debug_type = DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND2 if with_mercy else DebugTypes.BID_DONT_SPEND_COIN_A_LOCK_REFUND
|
||||||
|
swap_clients[id_leader].setBidDebugInd(bid_id, debug_type)
|
||||||
|
|
||||||
swap_clients[id_offerer].acceptBid(bid_id)
|
swap_clients[id_offerer].acceptBid(bid_id)
|
||||||
|
|
||||||
leader_sent_bid: bool = True if reverse_bid else False
|
leader_sent_bid: bool = True if reverse_bid else False
|
||||||
wait_for_bid(test_delay_event, swap_clients[id_leader], bid_id, BidStates.BID_STALLED_FOR_TEST, wait_for=(self.extra_wait_time + 180), sent=leader_sent_bid)
|
|
||||||
|
expect_state = BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED if with_mercy else BidStates.BID_STALLED_FOR_TEST
|
||||||
|
wait_for_bid(test_delay_event, swap_clients[id_leader], bid_id, expect_state, wait_for=(self.extra_wait_time + 180), sent=leader_sent_bid)
|
||||||
wait_for_bid(test_delay_event, swap_clients[id_follower], bid_id, BidStates.XMR_SWAP_FAILED_SWIPED, wait_for=(self.extra_wait_time + 80), sent=(not leader_sent_bid))
|
wait_for_bid(test_delay_event, swap_clients[id_follower], bid_id, BidStates.XMR_SWAP_FAILED_SWIPED, wait_for=(self.extra_wait_time + 80), sent=(not leader_sent_bid))
|
||||||
|
|
||||||
js_w1_after = read_json_api(1800 + id_bidder, 'wallets')
|
js_w1_after = read_json_api(1800 + id_bidder, 'wallets')
|
||||||
|
|
Loading…
Reference in a new issue