mirror of
https://github.com/basicswap/basicswap.git
synced 2024-12-22 19:49:20 +00:00
Add more event log types.
Auto accept only bids of the exact offer amount. Retry sending lock B refund tx.
This commit is contained in:
parent
a27cfcba0f
commit
c66160fb09
6 changed files with 110 additions and 18 deletions
|
@ -1,3 +1,3 @@
|
|||
name = "basicswap"
|
||||
|
||||
__version__ = "0.0.15"
|
||||
__version__ = "0.0.16"
|
||||
|
|
|
@ -200,6 +200,12 @@ class EventLogTypes(IntEnum):
|
|||
LOCK_TX_B_SEEN = auto()
|
||||
LOCK_TX_B_CONFIRMED = auto()
|
||||
DEBUG_TWEAK_APPLIED = auto()
|
||||
FAILED_TX_B_REFUND = auto()
|
||||
LOCK_TX_B_INVALID = auto()
|
||||
LOCK_TX_A_REFUND_TX_PUBLISHED = auto()
|
||||
LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED = auto()
|
||||
LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED = auto()
|
||||
LOCK_TX_B_REFUND_TX_PUBLISHED = auto()
|
||||
|
||||
|
||||
class XmrSplitMsgTypes(IntEnum):
|
||||
|
@ -327,25 +333,37 @@ def getLockName(lock_type):
|
|||
|
||||
def describeEventEntry(event_type, event_msg):
|
||||
if event_type == EventLogTypes.FAILED_TX_B_LOCK_PUBLISH:
|
||||
return 'Failed to publish lock tx b'
|
||||
return 'Failed to publish lock tx B'
|
||||
if event_type == EventLogTypes.FAILED_TX_B_LOCK_PUBLISH:
|
||||
return 'Failed to publish lock tx b'
|
||||
return 'Failed to publish lock tx B'
|
||||
if event_type == EventLogTypes.LOCK_TX_A_PUBLISHED:
|
||||
return 'Lock tx a published'
|
||||
return 'Lock tx A published'
|
||||
if event_type == EventLogTypes.LOCK_TX_B_PUBLISHED:
|
||||
return 'Lock tx b published'
|
||||
return 'Lock tx B published'
|
||||
if event_type == EventLogTypes.FAILED_TX_B_SPEND:
|
||||
return 'Failed to publish lock tx b spend'
|
||||
return 'Failed to publish lock tx B spend'
|
||||
if event_type == EventLogTypes.LOCK_TX_A_SEEN:
|
||||
return 'Lock tx a seen in chain'
|
||||
return 'Lock tx A seen in chain'
|
||||
if event_type == EventLogTypes.LOCK_TX_A_CONFIRMED:
|
||||
return 'Lock tx a confirmed in chain'
|
||||
return 'Lock tx A confirmed in chain'
|
||||
if event_type == EventLogTypes.LOCK_TX_B_SEEN:
|
||||
return 'Lock tx b seen in chain'
|
||||
return 'Lock tx B seen in chain'
|
||||
if event_type == EventLogTypes.LOCK_TX_B_CONFIRMED:
|
||||
return 'Lock tx b confirmed in chain'
|
||||
return 'Lock tx B confirmed in chain'
|
||||
if event_type == EventLogTypes.DEBUG_TWEAK_APPLIED:
|
||||
return 'Debug tweak applied ' + event_msg
|
||||
if event_type == EventLogTypes.FAILED_TX_B_REFUND:
|
||||
return 'Failed to publish lock tx B refund'
|
||||
if event_type == EventLogTypes.LOCK_TX_B_INVALID:
|
||||
return 'Detected invalid lock Tx B'
|
||||
if event_type == EventLogTypes.LOCK_TX_A_REFUND_TX_PUBLISHED:
|
||||
return 'Lock tx A refund tx published'
|
||||
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED:
|
||||
return 'Lock tx A refund spend tx published'
|
||||
if event_type == EventLogTypes.LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED:
|
||||
return 'Lock tx A refund swipe tx published'
|
||||
if event_type == EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED:
|
||||
return 'Lock tx B refund tx published'
|
||||
|
||||
|
||||
def getExpectedSequence(lockType, lockVal, coin_type):
|
||||
|
@ -2693,6 +2711,7 @@ class BasicSwap(BaseApp):
|
|||
if TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND not in bid.txns:
|
||||
try:
|
||||
txid = ci_from.publishTx(xmr_swap.a_lock_refund_spend_tx)
|
||||
self.logBidEvent(bid, EventLogTypes.LOCK_TX_A_REFUND_SPEND_TX_PUBLISHED, '', session)
|
||||
|
||||
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(
|
||||
|
@ -2714,6 +2733,7 @@ class BasicSwap(BaseApp):
|
|||
if TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE not in bid.txns:
|
||||
try:
|
||||
txid = ci_from.publishTx(xmr_swap.a_lock_refund_swipe_tx)
|
||||
self.logBidEvent(bid, EventLogTypes.LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED, '', session)
|
||||
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,
|
||||
|
@ -2742,6 +2762,7 @@ class BasicSwap(BaseApp):
|
|||
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()))
|
||||
self.logBidEvent(bid, EventLogTypes.LOCK_TX_A_REFUND_TX_PUBLISHED, '', session)
|
||||
bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND] = SwapTx(
|
||||
bid_id=bid_id,
|
||||
tx_type=TxTypes.XMR_SWAP_A_LOCK_REFUND,
|
||||
|
@ -2789,6 +2810,13 @@ class BasicSwap(BaseApp):
|
|||
utxo = utxos[0]
|
||||
if not bid.xmr_a_lock_tx.chain_height and utxo['height'] != 0:
|
||||
self.logBidEvent(bid, EventLogTypes.LOCK_TX_A_SEEN, '', session)
|
||||
|
||||
block_header = ci_from.getBlockHeaderFromHeight(utxo['height'])
|
||||
|
||||
bid.xmr_a_lock_tx.block_hash = bytes.fromhex(block_header['hash'])
|
||||
bid.xmr_a_lock_tx.block_height = block_header['height']
|
||||
bid.xmr_a_lock_tx.block_time = block_header['time'] # Or median_time?
|
||||
|
||||
bid_changed = True
|
||||
if bid.xmr_a_lock_tx.chain_height != utxo['height'] and utxo['height'] != 0:
|
||||
bid.xmr_a_lock_tx.chain_height = utxo['height']
|
||||
|
@ -2817,7 +2845,12 @@ class BasicSwap(BaseApp):
|
|||
bid_changed = False
|
||||
# Have to use findTxB instead of relying on the first seen height to detect chain reorgs
|
||||
found_tx = ci_to.findTxB(xmr_swap.vkbv, xmr_swap.pkbs, bid.amount_to, ci_to.blocks_confirmed, xmr_swap.b_restore_height)
|
||||
if found_tx is not None:
|
||||
|
||||
if isinstance(found_tx, int) and found_tx == -1:
|
||||
if self.countBidEvents(bid, EventLogTypes.LOCK_TX_B_INVALID, session) < 1:
|
||||
self.logBidEvent(bid, EventLogTypes.LOCK_TX_B_INVALID, 'Detected invalid lock tx B', session)
|
||||
bid_changed = True
|
||||
elif found_tx is not None:
|
||||
if bid.xmr_b_lock_tx is None or not bid.xmr_b_lock_tx.chain_height:
|
||||
self.logBidEvent(bid, EventLogTypes.LOCK_TX_B_SEEN, '', session)
|
||||
if bid.xmr_b_lock_tx is None:
|
||||
|
@ -3581,6 +3614,7 @@ class BasicSwap(BaseApp):
|
|||
assert(msg['to'] == offer.addr_from), 'Received on incorrect address'
|
||||
assert(now <= offer.expire_at), 'Offer expired'
|
||||
assert(bid_data.amount >= offer.min_bid_amount), 'Bid amount below minimum'
|
||||
assert(bid_data.amount <= offer.amount_from), 'Bid amount above offer amount'
|
||||
assert(now <= msg['sent'] + bid_data.time_valid), 'Bid expired'
|
||||
|
||||
# TODO: Allow higher bids
|
||||
|
@ -3646,6 +3680,8 @@ class BasicSwap(BaseApp):
|
|||
if offer.auto_accept_bids:
|
||||
if self.countAcceptedBids(offer_id) > 0:
|
||||
self.log.info('Not auto accepting bid %s, already have', bid_id.hex())
|
||||
elif bid_data.amount != offer.amount_from:
|
||||
self.log.info('Not auto accepting bid %s, want exact amount match', bid_id.hex())
|
||||
else:
|
||||
delay = random.randrange(self.min_delay_event, self.max_delay_event)
|
||||
self.log.info('Auto accepting bid %s in %d seconds', bid_id.hex(), delay)
|
||||
|
@ -3766,6 +3802,8 @@ class BasicSwap(BaseApp):
|
|||
if offer.auto_accept_bids:
|
||||
if self.countAcceptedBids(bid.offer_id) > 0:
|
||||
self.log.info('Not auto accepting bid %s, already have', bid.bid_id.hex())
|
||||
elif bid.amount != offer.amount_from:
|
||||
self.log.info('Not auto accepting bid %s, want exact amount match', bid_id.hex())
|
||||
else:
|
||||
delay = random.randrange(self.min_delay_event, self.max_delay_event)
|
||||
self.log.info('Auto accepting xmr bid %s in %d seconds', bid.bid_id.hex(), delay)
|
||||
|
@ -3842,7 +3880,15 @@ class BasicSwap(BaseApp):
|
|||
ci_from = self.ci(Coins(offer.coin_from))
|
||||
ci_to = self.ci(Coins(offer.coin_to))
|
||||
|
||||
assert(offer.state == OfferStates.OFFER_RECEIVED), 'Bad offer state'
|
||||
assert(msg['to'] == offer.addr_from), 'Received on incorrect address'
|
||||
assert(now <= offer.expire_at), 'Offer expired'
|
||||
assert(bid_data.amount >= offer.min_bid_amount), 'Bid amount below minimum'
|
||||
assert(bid_data.amount <= offer.amount_from), 'Bid amount above offer amount'
|
||||
assert(now <= msg['sent'] + bid_data.time_valid), 'Bid expired'
|
||||
|
||||
self.log.debug('TODO: xmr bid validation')
|
||||
|
||||
assert(ci_to.verifyKey(bid_data.kbvf))
|
||||
assert(ci_from.verifyPubkey(bid_data.pkaf))
|
||||
|
||||
|
@ -4310,7 +4356,29 @@ class BasicSwap(BaseApp):
|
|||
|
||||
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)
|
||||
try:
|
||||
txid = ci_to.spendBLockTx(address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, xmr_swap.b_restore_height)
|
||||
self.log.debug('Submitted lock B refund txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
|
||||
self.logBidEvent(bid, EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED, '', session)
|
||||
except Exception as ex:
|
||||
# TODO: Make min-conf 10?
|
||||
error_msg = 'spendBLockTx refund failed for bid {} with error {}'.format(bid_id.hex(), str(ex))
|
||||
num_retries = self.countBidEvents(bid, EventLogTypes.FAILED_TX_B_REFUND, session)
|
||||
if num_retries > 0:
|
||||
error_msg += ', retry no. {}'.format(num_retries)
|
||||
self.log.error(error_msg)
|
||||
|
||||
str_error = str(ex)
|
||||
if num_retries < 100 and 'Invalid unlocked_balance' in str_error:
|
||||
delay = random.randrange(self.min_delay_retry, self.max_delay_retry)
|
||||
self.log.info('Retrying sending xmr swap chain B refund 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:
|
||||
self.setBidError(bid_id, bid, 'spendBLockTx for refund failed: ' + str(ex), save_bid=False)
|
||||
self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer)
|
||||
|
||||
self.logBidEvent(bid, EventLogTypes.FAILED_TX_B_REFUND, str_error, session)
|
||||
return
|
||||
|
||||
bid.xmr_b_lock_tx.spend_txid = txid
|
||||
|
||||
|
|
|
@ -150,6 +150,10 @@ class BTCInterface(CoinInterface):
|
|||
def getMempoolTx(self, txid):
|
||||
return self.rpc_callback('getrawtransaction', [txid.hex()])
|
||||
|
||||
def getBlockHeaderFromHeight(self, height):
|
||||
block_hash = self.rpc_callback('getblockhash', [height])
|
||||
return self.rpc_callback('getblockheader', [block_hash])
|
||||
|
||||
def initialiseWallet(self, key_bytes):
|
||||
wif_prefix = chainparams[self.coin_type()][self._network]['key_prefix']
|
||||
key_wif = toWIF(wif_prefix, key_bytes)
|
||||
|
|
|
@ -268,7 +268,7 @@ class XMRInterface(CoinInterface):
|
|||
return {'txid': transfer['tx_hash'], 'amount': transfer['amount'], 'height': 0 if 'block_height' not in transfer else transfer['block_height']}
|
||||
else:
|
||||
self._log.warning('Incorrect amount detected for coin b lock txn: {}'.format(transfer['tx_hash']))
|
||||
|
||||
return -1
|
||||
return None
|
||||
|
||||
def waitForLockTxB(self, kbv, Kbs, cb_swap_value, cb_block_confirmed, restore_height):
|
||||
|
|
|
@ -101,11 +101,19 @@ def updateThread(cls):
|
|||
try:
|
||||
if cls.btc_addr is not None:
|
||||
callbtcrpc(0, 'generatetoaddress', [1, cls.btc_addr])
|
||||
except Exception as e:
|
||||
print('updateThread error', str(e))
|
||||
cls.delay_event.wait(random.randrange(cls.update_min, cls.update_max))
|
||||
|
||||
|
||||
def updateThreadXmr(cls):
|
||||
while not cls.delay_event.is_set():
|
||||
try:
|
||||
if cls.xmr_addr is not None:
|
||||
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1})
|
||||
except Exception as e:
|
||||
print('updateThread error', str(e))
|
||||
cls.delay_event.wait(random.randrange(1, 4))
|
||||
print('updateThreadXmr error', str(e))
|
||||
cls.delay_event.wait(random.randrange(cls.xmr_update_min, cls.xmr_update_max))
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
@ -113,8 +121,15 @@ class Test(unittest.TestCase):
|
|||
def setUpClass(cls):
|
||||
super(Test, cls).setUpClass()
|
||||
|
||||
cls.update_min = int(os.getenv('UPDATE_THREAD_MIN_WAIT', '1'))
|
||||
cls.update_max = cls.update_min * 4
|
||||
|
||||
cls.xmr_update_min = int(os.getenv('XMR_UPDATE_THREAD_MIN_WAIT', '1'))
|
||||
cls.xmr_update_max = cls.xmr_update_min * 4
|
||||
|
||||
cls.delay_event = threading.Event()
|
||||
cls.update_thread = None
|
||||
cls.update_thread_xmr = None
|
||||
cls.processes = []
|
||||
cls.btc_addr = None
|
||||
cls.xmr_addr = None
|
||||
|
@ -204,8 +219,8 @@ class Test(unittest.TestCase):
|
|||
|
||||
settings['min_delay_event'] = 1
|
||||
settings['max_delay_event'] = 4
|
||||
settings['min_delay_retry'] = 10
|
||||
settings['max_delay_retry'] = 20
|
||||
settings['min_delay_retry'] = 15
|
||||
settings['max_delay_retry'] = 30
|
||||
settings['min_sequence_lock_seconds'] = 60
|
||||
|
||||
settings['check_progress_seconds'] = 5
|
||||
|
@ -270,6 +285,9 @@ class Test(unittest.TestCase):
|
|||
self.update_thread = threading.Thread(target=updateThread, args=(self,))
|
||||
self.update_thread.start()
|
||||
|
||||
self.update_thread_xmr = threading.Thread(target=updateThreadXmr, args=(self,))
|
||||
self.update_thread_xmr.start()
|
||||
|
||||
# Wait for height, or sequencelock is thrown off by genesis blocktime
|
||||
num_blocks = 3
|
||||
logging.info('Waiting for Particl chain height %d', num_blocks)
|
||||
|
@ -291,11 +309,14 @@ class Test(unittest.TestCase):
|
|||
cls.delay_event.set()
|
||||
if cls.update_thread:
|
||||
cls.update_thread.join()
|
||||
if cls.update_thread_xmr:
|
||||
cls.update_thread_xmr.join()
|
||||
for p in cls.processes:
|
||||
p.terminate()
|
||||
for p in cls.processes:
|
||||
p.join()
|
||||
cls.update_thread = None
|
||||
cls.update_thread_xmr = None
|
||||
cls.processes = []
|
||||
|
||||
def test_persistent(self):
|
||||
|
|
|
@ -632,7 +632,6 @@ class Test(unittest.TestCase):
|
|||
|
||||
assert(make_int(js_w1_after['2']['balance'], scale=8, r=1) - (make_int(js_w1_before['2']['balance'], scale=8, r=1) + amt_1) < 1000)
|
||||
|
||||
|
||||
def test_07_revoke_offer(self):
|
||||
logging.info('---------- Test offer revocaction')
|
||||
swap_clients = self.swap_clients
|
||||
|
|
Loading…
Reference in a new issue