xmr: Check for existing spend of lock tx
This commit is contained in:
parent
0f8ed24217
commit
eff5235205
@ -35,6 +35,7 @@ from .interface_passthrough_btc import PassthroughBTCInterface
|
|||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from .util import (
|
from .util import (
|
||||||
|
TemporaryError,
|
||||||
pubkeyToAddress,
|
pubkeyToAddress,
|
||||||
format_amount,
|
format_amount,
|
||||||
format_timestamp,
|
format_timestamp,
|
||||||
@ -197,7 +198,6 @@ class BasicSwap(BaseApp):
|
|||||||
self._updating_wallets_info = {}
|
self._updating_wallets_info = {}
|
||||||
self._last_updated_wallets_info = 0
|
self._last_updated_wallets_info = 0
|
||||||
|
|
||||||
|
|
||||||
# TODO: Adjust ranges
|
# TODO: Adjust ranges
|
||||||
self.min_delay_event = self.settings.get('min_delay_event', 10)
|
self.min_delay_event = self.settings.get('min_delay_event', 10)
|
||||||
self.max_delay_event = self.settings.get('max_delay_event', 60)
|
self.max_delay_event = self.settings.get('max_delay_event', 60)
|
||||||
@ -2903,6 +2903,14 @@ class BasicSwap(BaseApp):
|
|||||||
self.process_XMR_SWAP_A_LOCK_tx_spend(bid_id, xmr_swap.a_lock_spend_tx_id.hex(), txn_hex)
|
self.process_XMR_SWAP_A_LOCK_tx_spend(bid_id, xmr_swap.a_lock_spend_tx_id.hex(), txn_hex)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
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:
|
||||||
|
if bid.was_received and self.countQueuedEvents(session, bid_id, EventTypes.REDEEM_XMR_SWAP_LOCK_TX_B) < 1:
|
||||||
|
bid.setState(BidStates.SWAP_DELAYING)
|
||||||
|
delay = random.randrange(self.min_delay_event, self.max_delay_event)
|
||||||
|
self.log.info('Redeeming coin b lock tx for bid %s in %d seconds', bid_id.hex(), delay)
|
||||||
|
self.createEventInSession(delay, EventTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, session)
|
||||||
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
session.commit()
|
||||||
elif state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED:
|
elif state == BidStates.XMR_SWAP_NOSCRIPT_TX_REDEEMED:
|
||||||
txid_hex = bid.xmr_b_lock_tx.spend_txid.hex()
|
txid_hex = bid.xmr_b_lock_tx.spend_txid.hex()
|
||||||
|
|
||||||
@ -3210,18 +3218,13 @@ class BasicSwap(BaseApp):
|
|||||||
|
|
||||||
if not bid.was_received:
|
if not bid.was_received:
|
||||||
bid.setState(BidStates.SWAP_COMPLETED)
|
bid.setState(BidStates.SWAP_COMPLETED)
|
||||||
if bid.was_received:
|
|
||||||
bid.setState(BidStates.SWAP_DELAYING)
|
|
||||||
delay = random.randrange(self.min_delay_event, self.max_delay_event)
|
|
||||||
self.log.info('Redeeming coin b lock tx for bid %s in %d seconds', bid_id.hex(), delay)
|
|
||||||
self.createEventInSession(delay, EventTypes.REDEEM_XMR_SWAP_LOCK_TX_B, bid_id, session)
|
|
||||||
else:
|
else:
|
||||||
# Could already be processed if spend was detected in the mempool
|
# Could already be processed if spend was detected in the mempool
|
||||||
self.log.warning('Coin a lock tx spend ignored due to bid state for bid {}'.format(bid_id.hex()))
|
self.log.warning('Coin a lock tx spend ignored due to bid state for bid {}'.format(bid_id.hex()))
|
||||||
|
|
||||||
elif spending_txid == xmr_swap.a_lock_refund_tx_id:
|
elif spending_txid == xmr_swap.a_lock_refund_tx_id:
|
||||||
self.log.debug('Coin a lock tx spent by lock refund tx.')
|
self.log.debug('Coin a lock tx spent by lock refund tx.')
|
||||||
pass
|
bid.setState(BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND)
|
||||||
else:
|
else:
|
||||||
self.setBidError(bid.bid_id, bid, 'Unexpected txn spent coin a lock tx: {}'.format(spend_txid_hex), save_bid=False)
|
self.setBidError(bid.bid_id, bid, 'Unexpected txn spent coin a lock tx: {}'.format(spend_txid_hex), save_bid=False)
|
||||||
|
|
||||||
@ -3381,6 +3384,10 @@ class BasicSwap(BaseApp):
|
|||||||
finally:
|
finally:
|
||||||
self.mxDB.release()
|
self.mxDB.release()
|
||||||
|
|
||||||
|
def countQueuedEvents(self, session, bid_id, event_type):
|
||||||
|
q = session.query(EventQueue).filter(sa.and_(EventQueue.active_ind == 1, EventQueue.linked_id == bid_id, EventQueue.event_type == event_type))
|
||||||
|
return q.count()
|
||||||
|
|
||||||
def checkEvents(self):
|
def checkEvents(self):
|
||||||
self.mxDB.acquire()
|
self.mxDB.acquire()
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
@ -4364,17 +4371,22 @@ 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)
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
for_ed25519 = True if coin_to == Coins.XMR else False
|
|
||||||
kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_ed25519)
|
|
||||||
vkbs = ci_to.sumKeys(kbsl, kbsf)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
chain_height = ci_to.getChainHeight()
|
||||||
|
lock_tx_depth = (chain_height - bid.xmr_b_lock_tx.chain_height) + 1
|
||||||
|
if lock_tx_depth < ci_to.depth_spendable():
|
||||||
|
raise TemporaryError(f'Chain B lock tx depth {lock_tx_depth} < required for spending.')
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
for_ed25519 = True if coin_to == Coins.XMR else False
|
||||||
|
kbsl = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_ed25519)
|
||||||
|
vkbs = ci_to.sumKeys(kbsl, kbsf)
|
||||||
|
|
||||||
if coin_to == Coins.XMR:
|
if coin_to == Coins.XMR:
|
||||||
address_to = self.getCachedMainWalletAddress(ci_to)
|
address_to = self.getCachedMainWalletAddress(ci_to)
|
||||||
else:
|
else:
|
||||||
@ -4383,7 +4395,6 @@ class BasicSwap(BaseApp):
|
|||||||
self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
|
self.log.debug('Submitted lock B spend txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
|
||||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, '', session)
|
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SPEND_TX_PUBLISHED, '', session)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
# TODO: Make min-conf 10?
|
|
||||||
error_msg = 'spendBLockTx failed for bid {} with error {}'.format(bid_id.hex(), str(ex))
|
error_msg = 'spendBLockTx failed for bid {} with error {}'.format(bid_id.hex(), str(ex))
|
||||||
num_retries = self.countBidEvents(bid, EventLogTypes.FAILED_TX_B_SPEND, session)
|
num_retries = self.countBidEvents(bid, EventLogTypes.FAILED_TX_B_SPEND, session)
|
||||||
if num_retries > 0:
|
if num_retries > 0:
|
||||||
|
@ -73,6 +73,7 @@ class BidStates(IntEnum):
|
|||||||
XMR_SWAP_NOSCRIPT_COIN_LOCKED = auto()
|
XMR_SWAP_NOSCRIPT_COIN_LOCKED = auto()
|
||||||
XMR_SWAP_LOCK_RELEASED = auto()
|
XMR_SWAP_LOCK_RELEASED = auto()
|
||||||
XMR_SWAP_SCRIPT_TX_REDEEMED = auto()
|
XMR_SWAP_SCRIPT_TX_REDEEMED = auto()
|
||||||
|
XMR_SWAP_SCRIPT_TX_PREREFUND = auto() # script txo moved into pre-refund tx
|
||||||
XMR_SWAP_NOSCRIPT_TX_REDEEMED = auto()
|
XMR_SWAP_NOSCRIPT_TX_REDEEMED = auto()
|
||||||
XMR_SWAP_NOSCRIPT_TX_RECOVERED = auto()
|
XMR_SWAP_NOSCRIPT_TX_RECOVERED = auto()
|
||||||
XMR_SWAP_FAILED_REFUNDED = auto()
|
XMR_SWAP_FAILED_REFUNDED = auto()
|
||||||
@ -279,7 +280,7 @@ def describeEventEntry(event_type, event_msg):
|
|||||||
if event_type == EventLogTypes.LOCK_TX_B_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:
|
if event_type == EventLogTypes.FAILED_TX_B_SPEND:
|
||||||
return 'Failed to publish lock tx B spend'
|
return 'Failed to publish lock tx B spend: ' + event_msg
|
||||||
if event_type == EventLogTypes.LOCK_TX_A_SEEN:
|
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:
|
if event_type == EventLogTypes.LOCK_TX_A_CONFIRMED:
|
||||||
@ -370,4 +371,6 @@ def isActiveBidState(state):
|
|||||||
return True
|
return True
|
||||||
if state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
|
if state == BidStates.XMR_SWAP_SCRIPT_TX_REDEEMED:
|
||||||
return True
|
return True
|
||||||
|
if state == BidStates.XMR_SWAP_SCRIPT_TX_PREREFUND:
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
|
@ -607,6 +607,10 @@ class PARTInterfaceAnon(PARTInterface):
|
|||||||
def balance_type():
|
def balance_type():
|
||||||
return BalanceTypes.ANON
|
return BalanceTypes.ANON
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def depth_spendable() -> int:
|
||||||
|
return 12
|
||||||
|
|
||||||
def coin_name(self):
|
def coin_name(self):
|
||||||
return super().coin_name() + ' Anon'
|
return super().coin_name() + ' Anon'
|
||||||
|
|
||||||
|
@ -60,6 +60,10 @@ class XMRInterface(CoinInterface):
|
|||||||
def nbK() -> int: # No. of bytes requires to encode a public key
|
def nbK() -> int: # No. of bytes requires to encode a public key
|
||||||
return 32
|
return 32
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def depth_spendable() -> int:
|
||||||
|
return 10
|
||||||
|
|
||||||
def __init__(self, coin_settings, network, swap_client=None):
|
def __init__(self, coin_settings, network, swap_client=None):
|
||||||
super().__init__(network)
|
super().__init__(network)
|
||||||
self.rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'], host=coin_settings.get('rpchost', '127.0.0.1'))
|
self.rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'], host=coin_settings.get('rpchost', '127.0.0.1'))
|
||||||
@ -396,15 +400,18 @@ class XMRInterface(CoinInterface):
|
|||||||
self._log.info('generate_from_keys %s', dumpj(rv))
|
self._log.info('generate_from_keys %s', dumpj(rv))
|
||||||
self.rpc_wallet_cb('open_wallet', {'filename': wallet_filename})
|
self.rpc_wallet_cb('open_wallet', {'filename': wallet_filename})
|
||||||
|
|
||||||
# For a while after opening the wallet rpc cmds return empty data
|
self.rpc_wallet_cb('refresh')
|
||||||
for i in range(10):
|
rv = self.rpc_wallet_cb('get_balance')
|
||||||
rv = self.rpc_wallet_cb('get_balance')
|
|
||||||
print('get_balance', rv)
|
|
||||||
if rv['balance'] >= cb_swap_value:
|
|
||||||
break
|
|
||||||
|
|
||||||
time.sleep(1 + i)
|
|
||||||
if rv['balance'] < cb_swap_value:
|
if rv['balance'] < cb_swap_value:
|
||||||
|
self._log.warning('Balance is too low, checking for existing spend.')
|
||||||
|
txns = self.rpc_wallet_cb('get_transfers', {'out': True})['out']
|
||||||
|
print(txns, txns)
|
||||||
|
if len(txns) > 0:
|
||||||
|
txid = txns[0]['txid']
|
||||||
|
self._log.warning(f'spendBLockTx detected spending tx: {txid}.')
|
||||||
|
if txns[0]['address'] == address_b58:
|
||||||
|
return bytes.fromhex(txid)
|
||||||
|
|
||||||
self._log.error('wallet {} balance {}, expected {}'.format(wallet_filename, rv['balance'], cb_swap_value))
|
self._log.error('wallet {} balance {}, expected {}'.format(wallet_filename, rv['balance'], cb_swap_value))
|
||||||
raise TemporaryError('Invalid balance')
|
raise TemporaryError('Invalid balance')
|
||||||
if rv['unlocked_balance'] < cb_swap_value:
|
if rv['unlocked_balance'] < cb_swap_value:
|
||||||
@ -414,6 +421,7 @@ class XMRInterface(CoinInterface):
|
|||||||
params = {'address': address_to}
|
params = {'address': address_to}
|
||||||
if self._fee_priority > 0:
|
if self._fee_priority > 0:
|
||||||
params['priority'] = self._fee_priority
|
params['priority'] = self._fee_priority
|
||||||
|
|
||||||
rv = self.rpc_wallet_cb('sweep_all', params)
|
rv = self.rpc_wallet_cb('sweep_all', params)
|
||||||
print('sweep_all', rv)
|
print('sweep_all', rv)
|
||||||
|
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
- Added protocol version to order and bid messages
|
- Added protocol version to order and bid messages
|
||||||
- Moved chain start heights to bid.
|
- Moved chain start heights to bid.
|
||||||
- Avoid scantxoutset for decred style swaps
|
- Avoid scantxoutset for decred style swaps
|
||||||
|
- xmr: spend chain B lock tx will look for existing spends
|
||||||
|
- xmrswaps:
|
||||||
|
- Setting state to 'Script tx redeemed' will trigger an attempt to redeem the scriptless lock tx.
|
||||||
|
- Node will wait for the chain B lock tx to reach a spendable depth before attempting to spend.
|
||||||
|
|
||||||
|
|
||||||
0.0.25
|
0.0.25
|
||||||
|
Loading…
Reference in New Issue
Block a user