mirror of
https://github.com/basicswap/basicswap.git
synced 2024-12-31 15:59:28 +00:00
Decred xmr swap tests.
This commit is contained in:
parent
a3f5bc1a5a
commit
614fc9ccbd
21 changed files with 1271 additions and 293 deletions
|
@ -1153,14 +1153,26 @@ class BasicSwap(BaseApp):
|
|||
raise ValueError('Offer not found')
|
||||
|
||||
self.loadBidTxns(bid, session)
|
||||
|
||||
coin_from = Coins(offer.coin_from)
|
||||
coin_to = Coins(offer.coin_to)
|
||||
|
||||
if offer.swap_type == SwapTypes.XMR_SWAP:
|
||||
xmr_swap = session.query(XmrSwap).filter_by(bid_id=bid.bid_id).first()
|
||||
self.watchXmrSwap(bid, offer, xmr_swap)
|
||||
if coin_to.watch_blocks_for_scripts() and bid.xmr_a_lock_tx and bid.xmr_a_lock_tx.chain_height:
|
||||
if not bid.xmr_b_lock_tx or not bid.xmr_b_lock_tx.txid:
|
||||
ci_from = self.ci(coin_from)
|
||||
ci_to = self.ci(coin_to)
|
||||
chain_a_block_header = ci_from.getBlockHeaderFromHeight(bid.xmr_a_lock_tx.chain_height)
|
||||
block_time = chain_a_block_header['time']
|
||||
chain_b_block_header = ci_to.getBlockHeaderAt(block_time)
|
||||
dest_script = ci_to.getPkDest(xmr_swap.pkbs)
|
||||
self.addWatchedScript(ci_to.coin_type(), bid.bid_id, dest_script, TxTypes.XMR_SWAP_B_LOCK)
|
||||
self.setLastHeightCheckedStart(ci_to.coin_type(), chain_b_block_header['height'])
|
||||
else:
|
||||
self.swaps_in_progress[bid.bid_id] = (bid, offer)
|
||||
|
||||
coin_from = Coins(offer.coin_from)
|
||||
coin_to = Coins(offer.coin_to)
|
||||
if bid.initiate_tx and bid.initiate_tx.txid:
|
||||
self.addWatchedOutput(coin_from, bid.bid_id, bid.initiate_tx.txid.hex(), bid.initiate_tx.vout, BidStates.SWAP_INITIATED)
|
||||
if bid.participate_tx and bid.participate_tx.txid:
|
||||
|
@ -1632,16 +1644,12 @@ class BasicSwap(BaseApp):
|
|||
if swap_type == SwapTypes.XMR_SWAP:
|
||||
xmr_offer = XmrOffer()
|
||||
|
||||
if reverse_bid:
|
||||
# Delay before the chain a lock refund tx can be mined
|
||||
xmr_offer.lock_time_1 = ci_to.getExpectedSequence(lock_type, lock_value)
|
||||
# Delay before the follower can spend from the chain a lock refund tx
|
||||
xmr_offer.lock_time_2 = ci_to.getExpectedSequence(lock_type, lock_value)
|
||||
else:
|
||||
# Delay before the chain a lock refund tx can be mined
|
||||
xmr_offer.lock_time_1 = ci_from.getExpectedSequence(lock_type, lock_value)
|
||||
# Delay before the follower can spend from the chain a lock refund tx
|
||||
xmr_offer.lock_time_2 = ci_from.getExpectedSequence(lock_type, lock_value)
|
||||
chain_a_ci = ci_to if reverse_bid else ci_from
|
||||
lock_value_2 = lock_value + 1000 if (None, DebugTypes.OFFER_LOCK_2_VALUE_INC) in self._debug_cases else lock_value
|
||||
# Delay before the chain a lock refund tx can be mined
|
||||
xmr_offer.lock_time_1 = chain_a_ci.getExpectedSequence(lock_type, lock_value)
|
||||
# Delay before the follower can spend from the chain a lock refund tx
|
||||
xmr_offer.lock_time_2 = chain_a_ci.getExpectedSequence(lock_type, lock_value_2)
|
||||
|
||||
xmr_offer.a_fee_rate = msg_buf.fee_rate_from
|
||||
xmr_offer.b_fee_rate = msg_buf.fee_rate_to # Unused: TODO - Set priority?
|
||||
|
@ -2643,8 +2651,9 @@ class BasicSwap(BaseApp):
|
|||
txid = ci_from.publishTx(bid.initiate_txn_refund)
|
||||
self.log.error('Submit refund_txn unexpectedly worked: ' + txid)
|
||||
except Exception as ex:
|
||||
if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex):
|
||||
if ci_from.isTxNonFinalError(str(ex)) is False:
|
||||
self.log.error('Submit refund_txn unexpected error' + str(ex))
|
||||
raise ex
|
||||
|
||||
if txid is not None:
|
||||
msg_buf = BidAcceptMessage()
|
||||
|
@ -2654,7 +2663,6 @@ class BasicSwap(BaseApp):
|
|||
|
||||
# pkh sent in script is hashed with sha256, Decred expects blake256
|
||||
if bid.pkhash_seller != pkhash_refund:
|
||||
assert (ci_to.coin_type() == Coins.DCR or ci_from.coin_type() == Coins.DCR) # [rm]
|
||||
msg_buf.pkhash_seller = bid.pkhash_seller
|
||||
|
||||
bid_bytes = msg_buf.SerializeToString()
|
||||
|
@ -2785,7 +2793,7 @@ class BasicSwap(BaseApp):
|
|||
msg_buf.amount_to = amount_to
|
||||
|
||||
address_out = self.getReceiveAddressFromPool(coin_from, offer_id, TxTypes.XMR_SWAP_A_LOCK)
|
||||
if coin_from == Coins.PART_BLIND:
|
||||
if coin_from in (Coins.PART_BLIND, ):
|
||||
addrinfo = ci_from.rpc('getaddressinfo', [address_out])
|
||||
msg_buf.dest_af = bytes.fromhex(addrinfo['pubkey'])
|
||||
else:
|
||||
|
@ -3025,7 +3033,7 @@ class BasicSwap(BaseApp):
|
|||
msg_buf.a_lock_tx = xmr_swap.a_lock_tx
|
||||
msg_buf.a_lock_tx_script = xmr_swap.a_lock_tx_script
|
||||
msg_buf.a_lock_refund_tx = xmr_swap.a_lock_refund_tx
|
||||
msg_buf.a_lock_refund_tx_script = xmr_swap.a_lock_refund_tx_script
|
||||
msg_buf.a_lock_refund_tx_script = bytes(xmr_swap.a_lock_refund_tx_script)
|
||||
msg_buf.a_lock_refund_spend_tx = xmr_swap.a_lock_refund_spend_tx
|
||||
msg_buf.al_lock_refund_tx_sig = xmr_swap.al_lock_refund_tx_sig
|
||||
|
||||
|
@ -3327,9 +3335,6 @@ class BasicSwap(BaseApp):
|
|||
secret = self.getContractSecret(bid_date, bid.contract_count)
|
||||
ensure(len(secret) == 32, 'Bad secret length')
|
||||
|
||||
self.log.debug('secret {}'.format(secret.hex()))
|
||||
self.log.debug('sha256(secret) {}'.format(sha256(secret).hex()))
|
||||
|
||||
if self.coin_clients[coin_type]['connection_type'] != 'rpc':
|
||||
return None
|
||||
|
||||
|
@ -3475,14 +3480,7 @@ class BasicSwap(BaseApp):
|
|||
else:
|
||||
privkey_wif = self.ci(Coins.PART).encodeKey(privkey)
|
||||
refund_sig = self.callcoinrpc(Coins.PART, 'createsignaturewithkey', [refund_txn, prevout, privkey_wif, 'ALL', options])
|
||||
if coin_type in (Coins.DCR, ):
|
||||
witness_stack = [
|
||||
bytes.fromhex(refund_sig),
|
||||
pubkey,
|
||||
(OpCodes.OP_0).to_bytes(1),
|
||||
txn_script]
|
||||
refund_txn = ci.setTxSignature(bytes.fromhex(refund_txn), witness_stack).hex()
|
||||
elif coin_type in (Coins.PART, ) or self.coin_clients[coin_type]['use_segwit']:
|
||||
if coin_type in (Coins.PART, Coins.DCR) or self.coin_clients[coin_type]['use_segwit']:
|
||||
witness_stack = [
|
||||
bytes.fromhex(refund_sig),
|
||||
pubkey,
|
||||
|
@ -3706,8 +3704,18 @@ class BasicSwap(BaseApp):
|
|||
|
||||
def findTxB(self, ci_to, xmr_swap, bid, session, bid_sender: bool) -> bool:
|
||||
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, bid.chain_b_height_start, bid_sender)
|
||||
|
||||
found_tx = None
|
||||
if ci_to.coin_type() in (Coins.DCR, ):
|
||||
if bid.xmr_b_lock_tx is None or bid.xmr_b_lock_tx.txid is None:
|
||||
# Watching chain for dest_address with WatchedScript
|
||||
pass
|
||||
else:
|
||||
dest_address = ci_to.pkh_to_address(ci_to.pkh(xmr_swap.pkbs))
|
||||
found_tx = ci_to.getLockTxHeight(bid.xmr_b_lock_tx.txid, dest_address, bid.amount_to, bid.chain_b_height_start, vout=bid.xmr_b_lock_tx.vout)
|
||||
else:
|
||||
# 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, bid.chain_b_height_start, bid_sender)
|
||||
|
||||
if isinstance(found_tx, int) and found_tx == -1:
|
||||
if self.countBidEvents(bid, EventLogTypes.LOCK_TX_B_INVALID, session) < 1:
|
||||
|
@ -3716,20 +3724,18 @@ class BasicSwap(BaseApp):
|
|||
elif found_tx is not None:
|
||||
if found_tx['height'] != 0 and (bid.xmr_b_lock_tx is None or not bid.xmr_b_lock_tx.chain_height):
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_SEEN, '', session)
|
||||
if bid.xmr_b_lock_tx is None:
|
||||
|
||||
if bid.xmr_b_lock_tx is None or bid.xmr_b_lock_tx.chain_height is None:
|
||||
self.log.debug('Found {} lock tx in chain'.format(ci_to.coin_name()))
|
||||
xmr_swap.b_lock_tx_id = bytes.fromhex(found_tx['txid'])
|
||||
if bid.xmr_b_lock_tx is None:
|
||||
bid.xmr_b_lock_tx = SwapTx(
|
||||
bid_id=bid.bid_id,
|
||||
tx_type=TxTypes.XMR_SWAP_B_LOCK,
|
||||
txid=xmr_swap.b_lock_tx_id,
|
||||
chain_height=found_tx['height'],
|
||||
)
|
||||
bid_changed = True
|
||||
bid.xmr_b_lock_tx.setState(TxStates.TX_IN_CHAIN)
|
||||
else:
|
||||
bid.xmr_b_lock_tx.chain_height = found_tx['height']
|
||||
bid_changed = True
|
||||
bid.xmr_b_lock_tx.chain_height = found_tx['height']
|
||||
bid_changed = True
|
||||
return bid_changed
|
||||
|
||||
def checkXmrBidState(self, bid_id: bytes, bid, offer):
|
||||
|
@ -3785,7 +3791,7 @@ class BasicSwap(BaseApp):
|
|||
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||
session.commit()
|
||||
|
||||
if TxTypes.XMR_SWAP_A_LOCK_REFUND_SWIPE not in bid.txns and BidStates(bid.state) != BidStates.BID_STALLED_FOR_TEST:
|
||||
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.bid_id, EventLogTypes.LOCK_TX_A_REFUND_SWIPE_TX_PUBLISHED, '', session)
|
||||
|
@ -3827,7 +3833,7 @@ class BasicSwap(BaseApp):
|
|||
session.commit()
|
||||
return rv
|
||||
except Exception as ex:
|
||||
if 'Transaction already in block chain' in str(ex):
|
||||
if ci_from.isTxExistsError(str(ex)):
|
||||
self.log.info('Found coin a lock refund tx for bid {}'.format(bid_id.hex()))
|
||||
txid = ci_from.getTxid(xmr_swap.a_lock_refund_tx)
|
||||
if TxTypes.XMR_SWAP_A_LOCK_REFUND not in bid.txns:
|
||||
|
@ -3882,6 +3888,7 @@ class BasicSwap(BaseApp):
|
|||
if lock_tx_chain_info['depth'] >= ci_from.blocks_confirmed:
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_CONFIRMED, '', session)
|
||||
bid.xmr_a_lock_tx.setState(TxStates.TX_CONFIRMED)
|
||||
|
||||
bid.setState(BidStates.XMR_SWAP_SCRIPT_COIN_LOCKED)
|
||||
bid_changed = True
|
||||
|
||||
|
@ -3890,6 +3897,13 @@ class BasicSwap(BaseApp):
|
|||
self.log.info('Sending adaptor-sig swap chain B lock tx for bid %s in %d seconds', bid_id.hex(), delay)
|
||||
self.createActionInSession(delay, ActionTypes.SEND_XMR_SWAP_LOCK_TX_B, bid_id, session)
|
||||
# bid.setState(BidStates.SWAP_DELAYING)
|
||||
elif ci_to.watch_blocks_for_scripts():
|
||||
chain_a_block_header = ci_from.getBlockHeaderFromHeight(bid.xmr_a_lock_tx.chain_height)
|
||||
block_time = chain_a_block_header['time']
|
||||
chain_b_block_header = ci_to.getBlockHeaderAt(block_time)
|
||||
dest_script = ci_to.getPkDest(xmr_swap.pkbs)
|
||||
self.addWatchedScript(ci_to.coin_type(), bid.bid_id, dest_script, TxTypes.XMR_SWAP_B_LOCK)
|
||||
self.setLastHeightCheckedStart(ci_to.coin_type(), chain_b_block_header['height'])
|
||||
|
||||
if bid_changed:
|
||||
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||
|
@ -4131,7 +4145,7 @@ class BasicSwap(BaseApp):
|
|||
self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.ITX_REFUND_PUBLISHED, '', None)
|
||||
# State will update when spend is detected
|
||||
except Exception as ex:
|
||||
if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex):
|
||||
if ci_from.isTxNonFinalError(str(ex)) is False:
|
||||
self.log.warning('Error trying to submit initiate refund txn: %s', str(ex))
|
||||
|
||||
if bid.getPTxState() in (TxStates.TX_SENT, TxStates.TX_CONFIRMED) \
|
||||
|
@ -4142,7 +4156,7 @@ class BasicSwap(BaseApp):
|
|||
self.logEvent(Concepts.BID, bid.bid_id, EventLogTypes.PTX_REFUND_PUBLISHED, '', None)
|
||||
# State will update when spend is detected
|
||||
except Exception as ex:
|
||||
if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex) and 'locks on inputs not met' not in str(ex):
|
||||
if ci_to.isTxNonFinalError(str(ex)):
|
||||
self.log.warning('Error trying to submit participate refund txn: %s', str(ex))
|
||||
return False # Bid is still active
|
||||
|
||||
|
@ -4298,6 +4312,7 @@ class BasicSwap(BaseApp):
|
|||
state = BidStates(bid.state)
|
||||
spending_txid = bytes.fromhex(spend_txid_hex)
|
||||
|
||||
bid.xmr_a_lock_tx.spend_txid = spending_txid
|
||||
if spending_txid == xmr_swap.a_lock_spend_tx_id:
|
||||
if state == BidStates.XMR_SWAP_LOCK_RELEASED:
|
||||
xmr_swap.a_lock_spend_tx = bytes.fromhex(spend_txn_hex)
|
||||
|
@ -4416,6 +4431,16 @@ class BasicSwap(BaseApp):
|
|||
self.saveBid(watched_script.bid_id, bid)
|
||||
else:
|
||||
self.log.warning('Could not find active bid for found watched script: {}'.format(watched_script.bid_id.hex()))
|
||||
elif watched_script.tx_type == TxTypes.XMR_SWAP_B_LOCK:
|
||||
bid = self.swaps_in_progress[watched_script.bid_id][0]
|
||||
bid.xmr_b_lock_tx = SwapTx(
|
||||
bid_id=watched_script.bid_id,
|
||||
tx_type=TxTypes.XMR_SWAP_B_LOCK,
|
||||
txid=txid,
|
||||
vout=vout,
|
||||
)
|
||||
bid.xmr_b_lock_tx.setState(TxStates.TX_IN_CHAIN)
|
||||
self.saveBid(watched_script.bid_id, bid)
|
||||
else:
|
||||
self.log.warning('Unknown found watched script tx type for bid {}'.format(watched_script.bid_id.hex()))
|
||||
|
||||
|
@ -4852,12 +4877,12 @@ class BasicSwap(BaseApp):
|
|||
|
||||
xmr_offer.offer_id = offer_id
|
||||
|
||||
if reverse_bid:
|
||||
xmr_offer.lock_time_1 = ci_to.getExpectedSequence(offer_data.lock_type, offer_data.lock_value)
|
||||
xmr_offer.lock_time_2 = ci_to.getExpectedSequence(offer_data.lock_type, offer_data.lock_value)
|
||||
else:
|
||||
xmr_offer.lock_time_1 = ci_from.getExpectedSequence(offer_data.lock_type, offer_data.lock_value)
|
||||
xmr_offer.lock_time_2 = ci_from.getExpectedSequence(offer_data.lock_type, offer_data.lock_value)
|
||||
chain_a_ci = ci_to if reverse_bid else ci_from
|
||||
lock_value_2 = offer_data.lock_value
|
||||
if (None, DebugTypes.OFFER_LOCK_2_VALUE_INC) in self._debug_cases:
|
||||
lock_value_2 += 1000
|
||||
xmr_offer.lock_time_1 = chain_a_ci.getExpectedSequence(offer_data.lock_type, offer_data.lock_value)
|
||||
xmr_offer.lock_time_2 = chain_a_ci.getExpectedSequence(offer_data.lock_type, lock_value_2)
|
||||
|
||||
xmr_offer.a_fee_rate = offer_data.fee_rate_from
|
||||
xmr_offer.b_fee_rate = offer_data.fee_rate_to
|
||||
|
@ -5618,6 +5643,8 @@ class BasicSwap(BaseApp):
|
|||
a_fee_rate, xmr_swap.vkbv)
|
||||
|
||||
xmr_swap.a_lock_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_spend_tx)
|
||||
if bid.xmr_a_lock_tx:
|
||||
bid.xmr_a_lock_tx.spend_txid = xmr_swap.a_lock_spend_tx_id
|
||||
prevout_amount = ci_from.getLockTxSwapOutputValue(bid, xmr_swap)
|
||||
xmr_swap.al_lock_spend_tx_esig = ci_from.signTxOtVES(kal, xmr_swap.pkasf, xmr_swap.a_lock_spend_tx, 0, xmr_swap.a_lock_tx_script, prevout_amount)
|
||||
'''
|
||||
|
@ -5628,33 +5655,37 @@ class BasicSwap(BaseApp):
|
|||
xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script,
|
||||
xmr_swap.dest_af, a_fee_rate, xmr_swap.vkbv)
|
||||
'''
|
||||
delay = self.get_short_delay_event_seconds()
|
||||
self.log.info('Sending lock spend tx message for bid %s in %d seconds', bid_id.hex(), delay)
|
||||
self.createActionInSession(delay, ActionTypes.SEND_XMR_SWAP_LOCK_SPEND_MSG, bid_id, session)
|
||||
|
||||
lock_tx_sent: bool = False
|
||||
# publishalocktx
|
||||
if bid.xmr_a_lock_tx and bid.xmr_a_lock_tx.state:
|
||||
if bid.xmr_a_lock_tx.state >= TxStates.TX_SENT:
|
||||
raise ValueError('Lock tx has already been sent {}'.format(bid.xmr_a_lock_tx.txid.hex()))
|
||||
self.log.warning('Lock tx has already been sent {}'.format(bid.xmr_a_lock_tx.txid.hex()))
|
||||
lock_tx_sent = True
|
||||
|
||||
lock_tx_signed = ci_from.signTxWithWallet(xmr_swap.a_lock_tx)
|
||||
txid_hex = ci_from.publishTx(lock_tx_signed)
|
||||
if lock_tx_sent is False:
|
||||
lock_tx_signed = ci_from.signTxWithWallet(xmr_swap.a_lock_tx)
|
||||
txid_hex = ci_from.publishTx(lock_tx_signed)
|
||||
|
||||
vout_pos = ci_from.getTxOutputPos(xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script)
|
||||
self.log.debug('Submitted lock txn %s to %s chain for bid %s', txid_hex, ci_from.coin_name(), bid_id.hex())
|
||||
vout_pos = ci_from.getTxOutputPos(xmr_swap.a_lock_tx, xmr_swap.a_lock_tx_script)
|
||||
self.log.debug('Submitted lock txn %s to %s chain for bid %s', txid_hex, ci_from.coin_name(), bid_id.hex())
|
||||
|
||||
if bid.xmr_a_lock_tx is None:
|
||||
bid.xmr_a_lock_tx = SwapTx(
|
||||
bid_id=bid_id,
|
||||
tx_type=TxTypes.XMR_SWAP_A_LOCK,
|
||||
txid=bytes.fromhex(txid_hex),
|
||||
vout=vout_pos,
|
||||
)
|
||||
bid.xmr_a_lock_tx.setState(TxStates.TX_SENT)
|
||||
if bid.xmr_a_lock_tx is None:
|
||||
bid.xmr_a_lock_tx = SwapTx(
|
||||
bid_id=bid_id,
|
||||
tx_type=TxTypes.XMR_SWAP_A_LOCK,
|
||||
txid=bytes.fromhex(txid_hex),
|
||||
vout=vout_pos,
|
||||
)
|
||||
bid.xmr_a_lock_tx.setState(TxStates.TX_SENT)
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_PUBLISHED, '', session)
|
||||
|
||||
bid.setState(BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX)
|
||||
self.watchXmrSwap(bid, offer, xmr_swap)
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_A_PUBLISHED, '', session)
|
||||
|
||||
delay = self.get_short_delay_event_seconds()
|
||||
self.log.info('Sending lock spend tx message for bid %s in %d seconds', bid_id.hex(), delay)
|
||||
self.createActionInSession(delay, ActionTypes.SEND_XMR_SWAP_LOCK_SPEND_MSG, bid_id, session)
|
||||
|
||||
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||
|
||||
|
@ -5802,8 +5833,10 @@ class BasicSwap(BaseApp):
|
|||
v = ci_from.verifyTxSig(xmr_swap.a_lock_spend_tx, af_lock_spend_sig, xmr_swap.pkaf, 0, xmr_swap.a_lock_tx_script, prevout_amount)
|
||||
ensure(v, 'Invalid coin A lock tx spend tx follower sig')
|
||||
|
||||
witness_stack = [
|
||||
b'',
|
||||
witness_stack = []
|
||||
if coin_from not in (Coins.DCR,):
|
||||
witness_stack += [b'',]
|
||||
witness_stack += [
|
||||
al_lock_spend_sig,
|
||||
af_lock_spend_sig,
|
||||
xmr_swap.a_lock_tx_script,
|
||||
|
@ -5868,7 +5901,8 @@ class BasicSwap(BaseApp):
|
|||
else:
|
||||
address_to = self.getReceiveAddressFromPool(coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_SPEND)
|
||||
|
||||
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, b_fee_rate, bid.chain_b_height_start)
|
||||
lock_tx_vout = bid.getLockTXBVout()
|
||||
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, b_fee_rate, bid.chain_b_height_start, lock_tx_vout=lock_tx_vout)
|
||||
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)
|
||||
except Exception as ex:
|
||||
|
@ -5933,7 +5967,9 @@ class BasicSwap(BaseApp):
|
|||
address_to = self.getCachedStealthAddressForCoin(coin_to)
|
||||
else:
|
||||
address_to = self.getReceiveAddressFromPool(coin_to, bid_id, TxTypes.XMR_SWAP_B_LOCK_REFUND)
|
||||
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, b_fee_rate, bid.chain_b_height_start)
|
||||
|
||||
lock_tx_vout = bid.getLockTXBVout()
|
||||
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, b_fee_rate, bid.chain_b_height_start, lock_tx_vout=lock_tx_vout)
|
||||
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.bid_id, EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED, '', session)
|
||||
except Exception as ex:
|
||||
|
@ -6041,8 +6077,10 @@ class BasicSwap(BaseApp):
|
|||
al_lock_refund_spend_tx_sig = ci_from.signTx(kal, xmr_swap.a_lock_refund_spend_tx, 0, xmr_swap.a_lock_refund_tx_script, prevout_amount)
|
||||
|
||||
self.log.debug('Setting lock refund spend tx sigs')
|
||||
witness_stack = [
|
||||
b'',
|
||||
witness_stack = []
|
||||
if coin_from not in (Coins.DCR, ):
|
||||
witness_stack += [b'',]
|
||||
witness_stack += [
|
||||
al_lock_refund_spend_tx_sig,
|
||||
xmr_swap.af_lock_refund_spend_tx_sig,
|
||||
bytes((1,)),
|
||||
|
@ -6099,6 +6137,8 @@ class BasicSwap(BaseApp):
|
|||
try:
|
||||
xmr_swap.a_lock_spend_tx = msg_data.a_lock_spend_tx
|
||||
xmr_swap.a_lock_spend_tx_id = ci_from.getTxid(xmr_swap.a_lock_spend_tx)
|
||||
if bid.xmr_a_lock_tx:
|
||||
bid.xmr_a_lock_tx.spend_txid = xmr_swap.a_lock_spend_tx_id
|
||||
xmr_swap.kal_sig = msg_data.kal_sig
|
||||
|
||||
ci_from.verifySCLockSpendTx(
|
||||
|
|
|
@ -203,6 +203,7 @@ class DebugTypes(IntEnum):
|
|||
B_LOCK_TX_MISSED_SEND = auto()
|
||||
DUPLICATE_ACTIONS = auto()
|
||||
DONT_CONFIRM_PTX = auto()
|
||||
OFFER_LOCK_2_VALUE_INC = auto()
|
||||
|
||||
|
||||
class NotificationTypes(IntEnum):
|
||||
|
|
|
@ -192,6 +192,11 @@ class Bid(Base):
|
|||
else:
|
||||
self.states += pack_state(new_state, now)
|
||||
|
||||
def getLockTXBVout(self):
|
||||
if self.xmr_b_lock_tx:
|
||||
return self.xmr_b_lock_tx.vout
|
||||
return None
|
||||
|
||||
|
||||
class SwapTx(Base):
|
||||
__tablename__ = 'transactions'
|
||||
|
|
|
@ -44,6 +44,10 @@ class CoinInterface:
|
|||
def watch_blocks_for_scripts() -> bool:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def compareFeeRates(a, b) -> bool:
|
||||
return abs(a - b) < 20
|
||||
|
||||
def __init__(self, network):
|
||||
self.setDefaults()
|
||||
self._network = network
|
||||
|
@ -149,8 +153,44 @@ class CoinInterface:
|
|||
def use_tx_vsize(self) -> bool:
|
||||
return self._use_segwit
|
||||
|
||||
def getLockTxSwapOutputValue(self, bid, xmr_swap):
|
||||
return bid.amount
|
||||
|
||||
class Secp256k1Interface(CoinInterface):
|
||||
def getLockRefundTxSwapOutputValue(self, bid, xmr_swap):
|
||||
return xmr_swap.a_swap_refund_value
|
||||
|
||||
def getLockRefundTxSwapOutput(self, xmr_swap):
|
||||
# Only one prevout exists
|
||||
return 0
|
||||
|
||||
|
||||
class AdaptorSigInterface():
|
||||
def getScriptLockTxDummyWitness(self, script: bytes):
|
||||
return [
|
||||
b'',
|
||||
bytes(72),
|
||||
bytes(72),
|
||||
bytes(len(script))
|
||||
]
|
||||
|
||||
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
|
||||
return [
|
||||
b'',
|
||||
bytes(72),
|
||||
bytes(72),
|
||||
bytes((1,)),
|
||||
bytes(len(script))
|
||||
]
|
||||
|
||||
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes):
|
||||
return [
|
||||
bytes(72),
|
||||
b'',
|
||||
bytes(len(script))
|
||||
]
|
||||
|
||||
|
||||
class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
|
||||
@staticmethod
|
||||
def curve_type():
|
||||
return Curves.secp256k1
|
||||
|
@ -170,3 +210,26 @@ class Secp256k1Interface(CoinInterface):
|
|||
|
||||
def verifyPubkey(self, pubkey_bytes: bytes) -> bool:
|
||||
return verify_secp256k1_point(pubkey_bytes)
|
||||
|
||||
def isValidAddressHash(self, address_hash: bytes) -> bool:
|
||||
hash_len = len(address_hash)
|
||||
if hash_len == 20:
|
||||
return True
|
||||
|
||||
def isValidPubkey(self, pubkey: bytes) -> bool:
|
||||
try:
|
||||
self.verifyPubkey(pubkey)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def verifySig(self, pubkey: bytes, signed_hash: bytes, sig: bytes) -> bool:
|
||||
pubkey = PublicKey(pubkey)
|
||||
return pubkey.verify(sig, signed_hash, hasher=None)
|
||||
|
||||
def sumKeys(self, ka: bytes, kb: bytes) -> bytes:
|
||||
# TODO: Add to coincurve
|
||||
return i2b((b2i(ka) + b2i(kb)) % ep.o)
|
||||
|
||||
def sumPubkeys(self, Ka: bytes, Kb: bytes) -> bytes:
|
||||
return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format()
|
||||
|
|
|
@ -28,7 +28,6 @@ from basicswap.util import (
|
|||
b2h, i2b, b2i, i2h,
|
||||
)
|
||||
from basicswap.util.ecc import (
|
||||
ep,
|
||||
pointToCPK, CPKToPoint,
|
||||
)
|
||||
from basicswap.util.script import (
|
||||
|
@ -66,7 +65,6 @@ from basicswap.contrib.test_framework.messages import (
|
|||
CTxIn,
|
||||
CTxInWitness,
|
||||
CTxOut,
|
||||
uint256_from_str,
|
||||
)
|
||||
from basicswap.contrib.test_framework.script import (
|
||||
CScript, CScriptOp,
|
||||
|
@ -120,6 +118,57 @@ def find_vout_for_address_from_txobj(tx_obj, addr: str) -> int:
|
|||
raise RuntimeError("Vout not found for address: txid={}, addr={}".format(tx_obj['txid'], addr))
|
||||
|
||||
|
||||
def extractScriptLockScriptValues(script_bytes: bytes) -> (bytes, bytes):
|
||||
script_len = len(script_bytes)
|
||||
ensure(script_len == 71, 'Bad script length')
|
||||
o = 0
|
||||
ensure_op(script_bytes[o] == OP_2)
|
||||
ensure_op(script_bytes[o + 1] == 33)
|
||||
o += 2
|
||||
pk1 = script_bytes[o: o + 33]
|
||||
o += 33
|
||||
ensure_op(script_bytes[o] == 33)
|
||||
o += 1
|
||||
pk2 = script_bytes[o: o + 33]
|
||||
o += 33
|
||||
ensure_op(script_bytes[o] == OP_2)
|
||||
ensure_op(script_bytes[o + 1] == OP_CHECKMULTISIG)
|
||||
|
||||
return pk1, pk2
|
||||
|
||||
|
||||
def extractScriptLockRefundScriptValues(script_bytes: bytes):
|
||||
script_len = len(script_bytes)
|
||||
ensure(script_len > 73, 'Bad script length')
|
||||
ensure_op(script_bytes[0] == OP_IF)
|
||||
ensure_op(script_bytes[1] == OP_2)
|
||||
ensure_op(script_bytes[2] == 33)
|
||||
pk1 = script_bytes[3: 3 + 33]
|
||||
ensure_op(script_bytes[36] == 33)
|
||||
pk2 = script_bytes[37: 37 + 33]
|
||||
ensure_op(script_bytes[70] == OP_2)
|
||||
ensure_op(script_bytes[71] == OP_CHECKMULTISIG)
|
||||
ensure_op(script_bytes[72] == OP_ELSE)
|
||||
o = 73
|
||||
csv_val, nb = decodeScriptNum(script_bytes, o)
|
||||
o += nb
|
||||
|
||||
ensure(script_len == o + 5 + 33, 'Bad script length') # Fails if script too long
|
||||
ensure_op(script_bytes[o] == OP_CHECKSEQUENCEVERIFY)
|
||||
o += 1
|
||||
ensure_op(script_bytes[o] == OP_DROP)
|
||||
o += 1
|
||||
ensure_op(script_bytes[o] == 33)
|
||||
o += 1
|
||||
pk3 = script_bytes[o: o + 33]
|
||||
o += 33
|
||||
ensure_op(script_bytes[o] == OP_CHECKSIG)
|
||||
o += 1
|
||||
ensure_op(script_bytes[o] == OP_ENDIF)
|
||||
|
||||
return pk1, pk2, csv_val, pk3
|
||||
|
||||
|
||||
class BTCInterface(Secp256k1Interface):
|
||||
|
||||
@staticmethod
|
||||
|
@ -157,10 +206,6 @@ class BTCInterface(Secp256k1Interface):
|
|||
rv += output.nValue
|
||||
return rv
|
||||
|
||||
@staticmethod
|
||||
def compareFeeRates(a, b) -> bool:
|
||||
return abs(a - b) < 20
|
||||
|
||||
@staticmethod
|
||||
def xmr_swap_a_lock_spend_tx_vsize() -> int:
|
||||
return 147
|
||||
|
@ -214,6 +259,23 @@ class BTCInterface(Secp256k1Interface):
|
|||
self._log = self._sc.log if self._sc and self._sc.log else logging
|
||||
self._expect_seedid_hex = None
|
||||
|
||||
def open_rpc(self, wallet=None):
|
||||
return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
|
||||
|
||||
def json_request(self, rpc_conn, method, params):
|
||||
try:
|
||||
v = rpc_conn.json_request(method, params)
|
||||
r = json.loads(v.decode('utf-8'))
|
||||
except Exception as ex:
|
||||
traceback.print_exc()
|
||||
raise ValueError('RPC Server Error ' + str(ex))
|
||||
if 'error' in r and r['error'] is not None:
|
||||
raise ValueError('RPC error ' + str(r['error']))
|
||||
return r['result']
|
||||
|
||||
def close_rpc(self, rpc_conn):
|
||||
rpc_conn.close()
|
||||
|
||||
def checkWallets(self) -> int:
|
||||
wallets = self.rpc('listwallets')
|
||||
|
||||
|
@ -231,25 +293,6 @@ class BTCInterface(Secp256k1Interface):
|
|||
|
||||
return len(wallets)
|
||||
|
||||
def open_rpc(self, wallet=None):
|
||||
return openrpc(self._rpcport, self._rpcauth, wallet=wallet, host=self._rpc_host)
|
||||
|
||||
def json_request(self, rpc_conn, method, params):
|
||||
try:
|
||||
v = rpc_conn.json_request(method, params)
|
||||
r = json.loads(v.decode('utf-8'))
|
||||
except Exception as ex:
|
||||
traceback.print_exc()
|
||||
raise ValueError('RPC Server Error ' + str(ex))
|
||||
|
||||
if 'error' in r and r['error'] is not None:
|
||||
raise ValueError('RPC error ' + str(r['error']))
|
||||
|
||||
return r['result']
|
||||
|
||||
def close_rpc(self, rpc_conn):
|
||||
rpc_conn.close()
|
||||
|
||||
def testDaemonRPC(self, with_wallet=True) -> None:
|
||||
self.rpc_wallet('getwalletinfo' if with_wallet else 'getblockchaininfo')
|
||||
|
||||
|
@ -278,7 +321,7 @@ class BTCInterface(Secp256k1Interface):
|
|||
|
||||
max_tries = 5000
|
||||
for i in range(max_tries):
|
||||
prev_block_header = self.rpc('getblock', [last_block_header['previousblockhash']])
|
||||
prev_block_header = self.rpc('getblockheader', [last_block_header['previousblockhash']])
|
||||
if prev_block_header['time'] <= time:
|
||||
return last_block_header if block_after else prev_block_header
|
||||
|
||||
|
@ -343,18 +386,6 @@ class BTCInterface(Secp256k1Interface):
|
|||
self._log.debug('validateaddress failed: {}'.format(address))
|
||||
return False
|
||||
|
||||
def isValidAddressHash(self, address_hash: bytes) -> bool:
|
||||
hash_len = len(address_hash)
|
||||
if hash_len == 20:
|
||||
return True
|
||||
|
||||
def isValidPubkey(self, pubkey: bytes) -> bool:
|
||||
try:
|
||||
self.verifyPubkey(pubkey)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def isAddressMine(self, address: str, or_watch_only: bool = False) -> bool:
|
||||
addr_info = self.rpc_wallet('getaddressinfo', [address])
|
||||
if not or_watch_only:
|
||||
|
@ -468,13 +499,6 @@ class BTCInterface(Secp256k1Interface):
|
|||
def decodeKey(self, k: str) -> bytes:
|
||||
return decodeWif(k)
|
||||
|
||||
def sumKeys(self, ka, kb):
|
||||
# TODO: Add to coincurve
|
||||
return i2b((b2i(ka) + b2i(kb)) % ep.o)
|
||||
|
||||
def sumPubkeys(self, Ka, Kb):
|
||||
return PublicKey.combine_keys([PublicKey(Ka), PublicKey(Kb)]).format()
|
||||
|
||||
def getScriptForPubkeyHash(self, pkh: bytes) -> CScript:
|
||||
# p2wpkh
|
||||
return CScript([OP_0, pkh])
|
||||
|
@ -485,24 +509,6 @@ class BTCInterface(Secp256k1Interface):
|
|||
tx.deserialize(BytesIO(tx_bytes))
|
||||
return tx
|
||||
|
||||
def extractScriptLockScriptValues(self, script_bytes: bytes):
|
||||
script_len = len(script_bytes)
|
||||
ensure(script_len == 71, 'Bad script length')
|
||||
o = 0
|
||||
ensure_op(script_bytes[o] == OP_2)
|
||||
ensure_op(script_bytes[o + 1] == 33)
|
||||
o += 2
|
||||
pk1 = script_bytes[o: o + 33]
|
||||
o += 33
|
||||
ensure_op(script_bytes[o] == 33)
|
||||
o += 1
|
||||
pk2 = script_bytes[o: o + 33]
|
||||
o += 33
|
||||
ensure_op(script_bytes[o] == OP_2)
|
||||
ensure_op(script_bytes[o + 1] == OP_CHECKMULTISIG)
|
||||
|
||||
return pk1, pk2
|
||||
|
||||
def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes:
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
|
@ -512,37 +518,6 @@ class BTCInterface(Secp256k1Interface):
|
|||
def fundSCLockTx(self, tx_bytes, feerate, vkbv=None):
|
||||
return self.fundTx(tx_bytes, feerate)
|
||||
|
||||
def extractScriptLockRefundScriptValues(self, script_bytes: bytes):
|
||||
script_len = len(script_bytes)
|
||||
ensure(script_len > 73, 'Bad script length')
|
||||
ensure_op(script_bytes[0] == OP_IF)
|
||||
ensure_op(script_bytes[1] == OP_2)
|
||||
ensure_op(script_bytes[2] == 33)
|
||||
pk1 = script_bytes[3: 3 + 33]
|
||||
ensure_op(script_bytes[36] == 33)
|
||||
pk2 = script_bytes[37: 37 + 33]
|
||||
ensure_op(script_bytes[70] == OP_2)
|
||||
ensure_op(script_bytes[71] == OP_CHECKMULTISIG)
|
||||
ensure_op(script_bytes[72] == OP_ELSE)
|
||||
o = 73
|
||||
csv_val, nb = decodeScriptNum(script_bytes, o)
|
||||
o += nb
|
||||
|
||||
ensure(script_len == o + 5 + 33, 'Bad script length') # Fails if script too long
|
||||
ensure_op(script_bytes[o] == OP_CHECKSEQUENCEVERIFY)
|
||||
o += 1
|
||||
ensure_op(script_bytes[o] == OP_DROP)
|
||||
o += 1
|
||||
ensure_op(script_bytes[o] == 33)
|
||||
o += 1
|
||||
pk3 = script_bytes[o: o + 33]
|
||||
o += 33
|
||||
ensure_op(script_bytes[o] == OP_CHECKSIG)
|
||||
o += 1
|
||||
ensure_op(script_bytes[o] == OP_ENDIF)
|
||||
|
||||
return pk1, pk2, csv_val, pk3
|
||||
|
||||
def genScriptLockRefundTxScript(self, Kal, Kaf, csv_val) -> CScript:
|
||||
|
||||
Kal_enc = Kal if len(Kal) == 33 else self.encodePubkey(Kal)
|
||||
|
@ -634,7 +609,7 @@ class BTCInterface(Secp256k1Interface):
|
|||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx_lock_refund.vout[locked_n].nValue
|
||||
|
||||
A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
|
||||
A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund)
|
||||
|
||||
tx_lock_refund.rehash()
|
||||
tx_lock_refund_hash_int = tx_lock_refund.sha256
|
||||
|
@ -721,7 +696,7 @@ class BTCInterface(Secp256k1Interface):
|
|||
ensure(locked_coin == swap_value, 'Bad locked value')
|
||||
|
||||
# Check script
|
||||
A, B = self.extractScriptLockScriptValues(script_out)
|
||||
A, B = extractScriptLockScriptValues(script_out)
|
||||
ensure(A == Kal, 'Bad script pubkey')
|
||||
ensure(B == Kaf, 'Bad script pubkey')
|
||||
|
||||
|
@ -789,7 +764,7 @@ class BTCInterface(Secp256k1Interface):
|
|||
locked_coin = tx.vout[locked_n].nValue
|
||||
|
||||
# Check script and values
|
||||
A, B, csv_val, C = self.extractScriptLockRefundScriptValues(script_out)
|
||||
A, B, csv_val, C = extractScriptLockRefundScriptValues(script_out)
|
||||
ensure(A == Kal, 'Bad script pubkey')
|
||||
ensure(B == Kaf, 'Bad script pubkey')
|
||||
ensure(csv_val == csv_val_expect, 'Bad script csv value')
|
||||
|
@ -922,6 +897,9 @@ class BTCInterface(Secp256k1Interface):
|
|||
def decryptOtVES(self, k: bytes, esig: bytes) -> bytes:
|
||||
return ecdsaotves_dec_sig(k, esig) + bytes((SIGHASH_ALL,))
|
||||
|
||||
def recoverEncKey(self, esig, sig, K):
|
||||
return ecdsaotves_rec_enc_key(K, esig, sig[:-1]) # Strip sighash type
|
||||
|
||||
def verifyTxSig(self, tx_bytes: bytes, sig: bytes, K: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bool:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
sig_hash = SegwitV0SignatureHash(prevout_script, tx, input_n, SIGHASH_ALL, prevout_value)
|
||||
|
@ -929,11 +907,7 @@ class BTCInterface(Secp256k1Interface):
|
|||
pubkey = PublicKey(K)
|
||||
return pubkey.verify(sig[: -1], sig_hash, hasher=None) # Pop the hashtype byte
|
||||
|
||||
def verifySig(self, pubkey, signed_hash, sig):
|
||||
pubkey = PublicKey(pubkey)
|
||||
return pubkey.verify(sig, signed_hash, hasher=None)
|
||||
|
||||
def fundTx(self, tx, feerate):
|
||||
def fundTx(self, tx: bytes, feerate) -> bytes:
|
||||
feerate_str = self.format_amount(feerate)
|
||||
# TODO: unlock unspents if bid cancelled
|
||||
options = {
|
||||
|
@ -943,7 +917,7 @@ class BTCInterface(Secp256k1Interface):
|
|||
rv = self.rpc_wallet('fundrawtransaction', [tx.hex(), options])
|
||||
return bytes.fromhex(rv['hex'])
|
||||
|
||||
def listInputs(self, tx_bytes):
|
||||
def listInputs(self, tx_bytes: bytes):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
|
||||
all_locked = self.rpc_wallet('listlockunspent')
|
||||
|
@ -1049,11 +1023,11 @@ class BTCInterface(Secp256k1Interface):
|
|||
tx.wit.vtxinwit.clear()
|
||||
return tx.serialize()
|
||||
|
||||
def extractLeaderSig(self, tx_bytes) -> bytes:
|
||||
def extractLeaderSig(self, tx_bytes: bytes) -> bytes:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
return tx.wit.vtxinwit[0].scriptWitness.stack[1]
|
||||
|
||||
def extractFollowerSig(self, tx_bytes) -> bytes:
|
||||
def extractFollowerSig(self, tx_bytes: bytes) -> bytes:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
return tx.wit.vtxinwit[0].scriptWitness.stack[2]
|
||||
|
||||
|
@ -1076,9 +1050,6 @@ class BTCInterface(Secp256k1Interface):
|
|||
|
||||
return bytes.fromhex(self.publishTx(b_lock_tx))
|
||||
|
||||
def recoverEncKey(self, esig, sig, K):
|
||||
return ecdsaotves_rec_enc_key(K, esig, sig[:-1]) # Strip sighash type
|
||||
|
||||
def getTxVSize(self, tx, add_bytes: int = 0, add_witness_bytes: int = 0) -> int:
|
||||
wsf = self.witnessScaleFactor()
|
||||
len_full = len(tx.serialize_with_witness()) + add_bytes + add_witness_bytes
|
||||
|
@ -1108,10 +1079,10 @@ class BTCInterface(Secp256k1Interface):
|
|||
witness_bytes = 109
|
||||
vsize = self.getTxVSize(tx, add_witness_bytes=witness_bytes)
|
||||
pay_fee = round(fee_rate * vsize / 1000)
|
||||
self._log.info(f'BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {vsize}, {pay_fee}.')
|
||||
self._log.info(f'BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {vsize}, {pay_fee}.')
|
||||
return pay_fee
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, lock_tx_vout=None) -> bytes:
|
||||
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
|
||||
wtx = self.rpc_wallet('gettransaction', [chain_b_lock_txid.hex(), ])
|
||||
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
|
||||
|
@ -1126,7 +1097,7 @@ class BTCInterface(Secp256k1Interface):
|
|||
tx.nVersion = self.txVersion()
|
||||
|
||||
script_lock = self.getScriptForPubkeyHash(Kbs)
|
||||
chain_b_lock_txid_int = uint256_from_str(chain_b_lock_txid[::-1])
|
||||
chain_b_lock_txid_int = b2i(chain_b_lock_txid)
|
||||
|
||||
tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n),
|
||||
nSequence=0,
|
||||
|
@ -1148,7 +1119,7 @@ class BTCInterface(Secp256k1Interface):
|
|||
addr_info = self.rpc_wallet('getaddressinfo', [address])
|
||||
return addr_info['iswatchonly']
|
||||
|
||||
def getSCLockScriptAddress(self, lock_script):
|
||||
def getSCLockScriptAddress(self, lock_script: bytes) -> str:
|
||||
lock_tx_dest = self.getScriptDest(lock_script)
|
||||
return self.encodeScriptDest(lock_tx_dest)
|
||||
|
||||
|
@ -1225,25 +1196,25 @@ class BTCInterface(Secp256k1Interface):
|
|||
params = [addr_to, value, '', '', subfee, True, self._conf_target]
|
||||
return self.rpc_wallet('sendtoaddress', params)
|
||||
|
||||
def signCompact(self, k, message):
|
||||
def signCompact(self, k, message: str) -> bytes:
|
||||
message_hash = sha256(bytes(message, 'utf-8'))
|
||||
|
||||
privkey = PrivateKey(k)
|
||||
return privkey.sign_recoverable(message_hash, hasher=None)[:64]
|
||||
|
||||
def signRecoverable(self, k, message):
|
||||
def signRecoverable(self, k, message: str) -> bytes:
|
||||
message_hash = sha256(bytes(message, 'utf-8'))
|
||||
|
||||
privkey = PrivateKey(k)
|
||||
return privkey.sign_recoverable(message_hash, hasher=None)
|
||||
|
||||
def verifyCompactSig(self, K, message, sig):
|
||||
def verifyCompactSig(self, K, message: str, sig) -> None:
|
||||
message_hash = sha256(bytes(message, 'utf-8'))
|
||||
pubkey = PublicKey(K)
|
||||
rv = pubkey.verify_compact(sig, message_hash, hasher=None)
|
||||
assert (rv is True)
|
||||
|
||||
def verifySigAndRecover(self, sig, message):
|
||||
def verifySigAndRecover(self, sig, message: str) -> bytes:
|
||||
message_hash = sha256(bytes(message, 'utf-8'))
|
||||
pubkey = PublicKey.from_signature_and_message(sig, message_hash, hasher=None)
|
||||
return pubkey.format()
|
||||
|
@ -1271,40 +1242,6 @@ class BTCInterface(Secp256k1Interface):
|
|||
def showLockTransfers(self, kbv, Kbs, restore_height):
|
||||
raise ValueError('Unimplemented')
|
||||
|
||||
def getLockTxSwapOutputValue(self, bid, xmr_swap):
|
||||
return bid.amount
|
||||
|
||||
def getLockRefundTxSwapOutputValue(self, bid, xmr_swap):
|
||||
return xmr_swap.a_swap_refund_value
|
||||
|
||||
def getLockRefundTxSwapOutput(self, xmr_swap):
|
||||
# Only one prevout exists
|
||||
return 0
|
||||
|
||||
def getScriptLockTxDummyWitness(self, script: bytes):
|
||||
return [
|
||||
b'',
|
||||
bytes(72),
|
||||
bytes(72),
|
||||
bytes(len(script))
|
||||
]
|
||||
|
||||
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
|
||||
return [
|
||||
b'',
|
||||
bytes(72),
|
||||
bytes(72),
|
||||
bytes((1,)),
|
||||
bytes(len(script))
|
||||
]
|
||||
|
||||
def getScriptLockRefundSwipeTxDummyWitness(self, script: bytes):
|
||||
return [
|
||||
bytes(72),
|
||||
b'',
|
||||
bytes(len(script))
|
||||
]
|
||||
|
||||
def getWitnessStackSerialisedLength(self, witness_stack):
|
||||
length = getCompactSizeLen(len(witness_stack))
|
||||
for e in witness_stack:
|
||||
|
@ -1501,7 +1438,7 @@ class BTCInterface(Secp256k1Interface):
|
|||
def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes = None) -> str:
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
|
||||
prev_txid = b2i(bytes.fromhex(prevout['txid']))
|
||||
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout'])))
|
||||
pkh = self.decodeAddress(output_addr)
|
||||
script = self.getScriptForPubkeyHash(pkh)
|
||||
|
@ -1513,7 +1450,7 @@ class BTCInterface(Secp256k1Interface):
|
|||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
tx.nLockTime = locktime
|
||||
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
|
||||
prev_txid = b2i(bytes.fromhex(prevout['txid']))
|
||||
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']), nSequence=sequence,))
|
||||
pkh = self.decodeAddress(output_addr)
|
||||
script = self.getScriptForPubkeyHash(pkh)
|
||||
|
@ -1551,6 +1488,12 @@ class BTCInterface(Secp256k1Interface):
|
|||
'amount': txjs['vout'][n]['value']
|
||||
}
|
||||
|
||||
def isTxExistsError(self, err_str: str) -> bool:
|
||||
return 'Transaction already in block chain' in err_str
|
||||
|
||||
def isTxNonFinalError(self, err_str: str) -> bool:
|
||||
return 'non-BIP68-final' in err_str or 'non-final' in err_str
|
||||
|
||||
|
||||
def testBTCInterface():
|
||||
print('TODO: testBTCInterface')
|
||||
|
|
|
@ -66,7 +66,7 @@ class DASHInterface(BTCInterface):
|
|||
add_bytes = 107
|
||||
size = len(tx.serialize_with_witness()) + add_bytes
|
||||
pay_fee = round(fee_rate * size / 1000)
|
||||
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
|
||||
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
|
||||
return pay_fee
|
||||
|
||||
def findTxnByHash(self, txid_hex: str):
|
||||
|
|
|
@ -7,20 +7,29 @@
|
|||
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
import traceback
|
||||
|
||||
from basicswap.basicswap_util import (
|
||||
getVoutByScriptPubKey,
|
||||
TxLockTypes
|
||||
)
|
||||
from basicswap.chainparams import Coins
|
||||
from basicswap.contrib.test_framework.messages import (
|
||||
uint256_from_str,
|
||||
from basicswap.contrib.test_framework.script import (
|
||||
CScriptNum,
|
||||
)
|
||||
from basicswap.interface.base import (
|
||||
Secp256k1Interface,
|
||||
)
|
||||
from basicswap.interface.btc import (
|
||||
extractScriptLockScriptValues,
|
||||
extractScriptLockRefundScriptValues,
|
||||
)
|
||||
from basicswap.interface.btc import Secp256k1Interface
|
||||
from basicswap.util import (
|
||||
ensure,
|
||||
b2h, b2i, i2b, i2h,
|
||||
)
|
||||
from basicswap.util.address import (
|
||||
b58decode,
|
||||
|
@ -36,28 +45,40 @@ from basicswap.util.script import (
|
|||
)
|
||||
from basicswap.util.extkey import ExtKeyPair
|
||||
from basicswap.util.integer import encode_varint
|
||||
from basicswap.interface.dcr.rpc import make_rpc_func
|
||||
from basicswap.interface.dcr.rpc import make_rpc_func, openrpc
|
||||
from .messages import (
|
||||
COutPoint,
|
||||
CTransaction,
|
||||
CTxIn,
|
||||
CTxOut,
|
||||
COutPoint,
|
||||
findOutput,
|
||||
SigHashType,
|
||||
TxSerializeType,
|
||||
)
|
||||
from .script import (
|
||||
push_script_data,
|
||||
OP_HASH160,
|
||||
OP_EQUAL,
|
||||
OP_DUP,
|
||||
OP_EQUALVERIFY,
|
||||
OP_CHECKMULTISIG,
|
||||
OP_CHECKSEQUENCEVERIFY,
|
||||
OP_CHECKSIG,
|
||||
OP_DROP,
|
||||
OP_DUP,
|
||||
OP_ELSE,
|
||||
OP_ENDIF,
|
||||
OP_EQUAL,
|
||||
OP_EQUALVERIFY,
|
||||
OP_HASH160,
|
||||
OP_IF,
|
||||
push_script_data,
|
||||
)
|
||||
|
||||
from coincurve.keys import (
|
||||
PrivateKey,
|
||||
PublicKey,
|
||||
)
|
||||
from coincurve.ecdsaotves import (
|
||||
ecdsaotves_enc_sign,
|
||||
ecdsaotves_enc_verify,
|
||||
ecdsaotves_dec_sig,
|
||||
ecdsaotves_rec_enc_key
|
||||
)
|
||||
|
||||
|
||||
SEQUENCE_LOCKTIME_GRANULARITY = 9 # 512 seconds
|
||||
|
@ -203,6 +224,10 @@ class DCRInterface(Secp256k1Interface):
|
|||
def watch_blocks_for_scripts() -> bool:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def depth_spendable() -> int:
|
||||
return 0
|
||||
|
||||
def __init__(self, coin_settings, network, swap_client=None):
|
||||
super().__init__(network)
|
||||
self._rpc_host = coin_settings.get('rpchost', '127.0.0.1')
|
||||
|
@ -212,8 +237,10 @@ class DCRInterface(Secp256k1Interface):
|
|||
self._log = self._sc.log if self._sc and self._sc.log else logging
|
||||
self.rpc = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||
if 'walletrpcport' in coin_settings:
|
||||
self.rpc_wallet = make_rpc_func(coin_settings['walletrpcport'], self._rpcauth, host=self._rpc_host)
|
||||
self._walletrpcport = coin_settings['walletrpcport']
|
||||
self.rpc_wallet = make_rpc_func(self._walletrpcport, self._rpcauth, host=self._rpc_host)
|
||||
else:
|
||||
self._walletrpcport = None
|
||||
self.rpc_wallet = None
|
||||
self.blocks_confirmed = coin_settings['blocks_confirmed']
|
||||
self.setConfTarget(coin_settings['conf_target'])
|
||||
|
@ -221,6 +248,23 @@ class DCRInterface(Secp256k1Interface):
|
|||
self._use_segwit = True # Decred is natively segwit
|
||||
self._connection_type = coin_settings['connection_type']
|
||||
|
||||
def open_rpc(self):
|
||||
return openrpc(self._rpcport, self._rpcauth, host=self._rpc_host)
|
||||
|
||||
def json_request(self, rpc_conn, method, params):
|
||||
try:
|
||||
v = rpc_conn.json_request(method, params)
|
||||
r = json.loads(v.decode('utf-8'))
|
||||
except Exception as ex:
|
||||
traceback.print_exc()
|
||||
raise ValueError('RPC Server Error ' + str(ex))
|
||||
if 'error' in r and r['error'] is not None:
|
||||
raise ValueError('RPC error ' + str(r['error']))
|
||||
return r['result']
|
||||
|
||||
def close_rpc(self, rpc_conn):
|
||||
rpc_conn.close()
|
||||
|
||||
def use_tx_vsize(self) -> bool:
|
||||
return False
|
||||
|
||||
|
@ -242,6 +286,7 @@ class DCRInterface(Secp256k1Interface):
|
|||
return b58encode(data + checksum[0:4])
|
||||
|
||||
def decode_address(self, address: str) -> bytes:
|
||||
# Different from decodeAddress returns more prefix bytes
|
||||
addr_data = b58decode(address)
|
||||
if addr_data is None:
|
||||
return None
|
||||
|
@ -251,6 +296,9 @@ class DCRInterface(Secp256k1Interface):
|
|||
raise ValueError('Checksum mismatch')
|
||||
return prefixed_data
|
||||
|
||||
def decodeAddress(self, address: str) -> bytes:
|
||||
return self.decode_address(address)[2:]
|
||||
|
||||
def testDaemonRPC(self, with_wallet=True) -> None:
|
||||
if with_wallet:
|
||||
self.rpc_wallet('getinfo')
|
||||
|
@ -289,6 +337,11 @@ class DCRInterface(Secp256k1Interface):
|
|||
|
||||
return rv
|
||||
|
||||
def getSpendableBalance(self) -> int:
|
||||
balances = self.rpc_wallet('getbalance')
|
||||
default_account_bal = balances['balances'][0] # 0 always default?
|
||||
return self.make_int(default_account_bal['spendable'])
|
||||
|
||||
def getSeedHash(self, seed: bytes) -> bytes:
|
||||
# m / purpose' / coin_type' / account' / change / address_index
|
||||
# m/44'/coin_type'/0'/0/0
|
||||
|
@ -339,6 +392,7 @@ class DCRInterface(Secp256k1Interface):
|
|||
|
||||
def setTxSignature(self, tx_bytes: bytes, stack, txi: int = 0) -> bytes:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
|
||||
script_data = bytearray()
|
||||
for data in stack:
|
||||
push_script_data(script_data, data)
|
||||
|
@ -390,6 +444,13 @@ class DCRInterface(Secp256k1Interface):
|
|||
assert len(pkh) == 20
|
||||
return OP_DUP.to_bytes(1) + OP_HASH160.to_bytes(1) + len(pkh).to_bytes(1) + pkh + OP_EQUALVERIFY.to_bytes(1) + OP_CHECKSIG.to_bytes(1)
|
||||
|
||||
def getPkDest(self, K: bytes) -> bytearray:
|
||||
return self.getPubkeyHashDest(self.pkh(K))
|
||||
|
||||
def getSCLockScriptAddress(self, lock_script: bytes) -> str:
|
||||
lock_tx_dest = self.getScriptDest(lock_script)
|
||||
return self.encodeScriptDest(lock_tx_dest)
|
||||
|
||||
def get_fee_rate(self, conf_target: int = 2) -> (float, str):
|
||||
chain_client_settings = self._sc.getChainClientSettings(self.coin_type()) # basicswap.json
|
||||
override_feerate = chain_client_settings.get('override_feerate', None)
|
||||
|
@ -525,6 +586,29 @@ class DCRInterface(Secp256k1Interface):
|
|||
|
||||
return sum_value
|
||||
|
||||
def signCompact(self, k, message):
|
||||
message_hash = blake256(bytes(message, 'utf-8'))
|
||||
|
||||
privkey = PrivateKey(k)
|
||||
return privkey.sign_recoverable(message_hash, hasher=None)[:64]
|
||||
|
||||
def signRecoverable(self, k, message: str) -> bytes:
|
||||
message_hash = blake256(bytes(message, 'utf-8'))
|
||||
|
||||
privkey = PrivateKey(k)
|
||||
return privkey.sign_recoverable(message_hash, hasher=None)
|
||||
|
||||
def verifyCompactSig(self, K, message: str, sig) -> None:
|
||||
message_hash = blake256(bytes(message, 'utf-8'))
|
||||
pubkey = PublicKey(K)
|
||||
rv = pubkey.verify_compact(sig, message_hash, hasher=None)
|
||||
assert (rv is True)
|
||||
|
||||
def verifySigAndRecover(self, sig, message: str) -> bytes:
|
||||
message_hash = blake256(bytes(message, 'utf-8'))
|
||||
pubkey = PublicKey.from_signature_and_message(sig, message_hash, hasher=None)
|
||||
return pubkey.format()
|
||||
|
||||
def verifyMessage(self, address: str, message: str, signature: str, message_magic: str = None) -> bool:
|
||||
if message_magic is None:
|
||||
message_magic = self.chainparams()['message_magic']
|
||||
|
@ -546,7 +630,12 @@ class DCRInterface(Secp256k1Interface):
|
|||
return True if address_hash == pubkey_hash else False
|
||||
|
||||
def signTxWithWallet(self, tx) -> bytes:
|
||||
return bytes.fromhex(self.rpc('signrawtransaction', [tx.hex()])['hex'])
|
||||
return bytes.fromhex(self.rpc_wallet('signrawtransaction', [tx.hex()])['hex'])
|
||||
|
||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||
key_wif = self.encodeKey(key)
|
||||
rv = self.rpc_wallet('signrawtransaction', [tx.hex(), [], [key_wif, ]])
|
||||
return bytes.fromhex(rv['hex'])
|
||||
|
||||
def createRawFundedTransaction(self, addr_to: str, amount: int, sub_fee: bool = False, lock_unspents: bool = True) -> str:
|
||||
|
||||
|
@ -593,6 +682,9 @@ class DCRInterface(Secp256k1Interface):
|
|||
# self._log.warning('gettxout {}'.format(e))
|
||||
return None
|
||||
|
||||
if found_vout is None:
|
||||
return None
|
||||
|
||||
block_height: int = 0
|
||||
confirmations: int = 0 if 'confirmations' not in txout else txout['confirmations']
|
||||
|
||||
|
@ -601,6 +693,7 @@ class DCRInterface(Secp256k1Interface):
|
|||
block_height = self.getChainHeight() - confirmations
|
||||
|
||||
rv = {
|
||||
'txid': txid.hex(),
|
||||
'depth': confirmations,
|
||||
'index': found_vout,
|
||||
'height': block_height}
|
||||
|
@ -628,7 +721,7 @@ class DCRInterface(Secp256k1Interface):
|
|||
def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes = None) -> str:
|
||||
tx = CTransaction()
|
||||
tx.version = self.txVersion()
|
||||
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
|
||||
prev_txid = b2i(bytes.fromhex(prevout['txid']))
|
||||
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout'], 0)))
|
||||
pkh = self.decode_address(output_addr)[2:]
|
||||
script = self.getPubkeyHashDest(pkh)
|
||||
|
@ -639,7 +732,7 @@ class DCRInterface(Secp256k1Interface):
|
|||
tx = CTransaction()
|
||||
tx.version = self.txVersion()
|
||||
tx.locktime = locktime
|
||||
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
|
||||
prev_txid = b2i(bytes.fromhex(prevout['txid']))
|
||||
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout'], 0), sequence=sequence,))
|
||||
pkh = self.decode_address(output_addr)[2:]
|
||||
script = self.getPubkeyHashDest(pkh)
|
||||
|
@ -678,6 +771,22 @@ class DCRInterface(Secp256k1Interface):
|
|||
block_hash = self.rpc('getblockhash', [height])
|
||||
return self.rpc('getblockheader', [block_hash])
|
||||
|
||||
def getBlockHeaderAt(self, time: int, block_after=False):
|
||||
blockchaininfo = self.rpc('getblockchaininfo')
|
||||
last_block_header = self.rpc('getblockheader', [blockchaininfo['bestblockhash']])
|
||||
|
||||
max_tries = 5000
|
||||
for i in range(max_tries):
|
||||
prev_block_header = self.rpc('getblockheader', [last_block_header['previousblockhash']])
|
||||
if prev_block_header['time'] <= time:
|
||||
return last_block_header if block_after else prev_block_header
|
||||
|
||||
last_block_header = prev_block_header
|
||||
raise ValueError(f'Block header not found at time: {time}')
|
||||
|
||||
def getMempoolTx(self, txid):
|
||||
raise ValueError('TODO')
|
||||
|
||||
def getBlockWithTxns(self, block_hash: str):
|
||||
block = self.rpc('getblock', [block_hash, True, True])
|
||||
|
||||
|
@ -697,3 +806,549 @@ class DCRInterface(Secp256k1Interface):
|
|||
|
||||
def describeTx(self, tx_hex: str):
|
||||
return self.rpc('decoderawtransaction', [tx_hex])
|
||||
|
||||
def fundTx(self, tx: bytes, feerate) -> bytes:
|
||||
feerate_str = float(self.format_amount(feerate))
|
||||
# TODO: unlock unspents if bid cancelled
|
||||
options = {
|
||||
'feeRate': feerate_str,
|
||||
}
|
||||
rv = self.rpc_wallet('fundrawtransaction', [tx.hex(), 'default', options])
|
||||
tx_bytes = bytes.fromhex(rv['hex'])
|
||||
|
||||
tx_obj = self.loadTx(tx_bytes)
|
||||
for txi in tx_obj.vin:
|
||||
utxos = [{'amount': float(self.format_amount(txi.value_in)),
|
||||
'txid': i2h(txi.prevout.hash),
|
||||
'vout': txi.prevout.n,
|
||||
'tree': txi.prevout.tree}]
|
||||
rv = self.rpc_wallet('lockunspent', [False, utxos])
|
||||
|
||||
return tx_bytes
|
||||
|
||||
def createSCLockTx(self, value: int, script: bytearray, vkbv: bytes = None) -> bytes:
|
||||
tx = CTransaction()
|
||||
tx.version = self.txVersion()
|
||||
tx.vout.append(self.txoType()(value, self.getScriptDest(script)))
|
||||
return tx.serialize()
|
||||
|
||||
def fundSCLockTx(self, tx_bytes, feerate, vkbv=None):
|
||||
return self.fundTx(tx_bytes, feerate)
|
||||
|
||||
def genScriptLockRefundTxScript(self, Kal, Kaf, csv_val) -> bytes:
|
||||
|
||||
Kal_enc = Kal if len(Kal) == 33 else self.encodePubkey(Kal)
|
||||
Kaf_enc = Kaf if len(Kaf) == 33 else self.encodePubkey(Kaf)
|
||||
|
||||
script = bytearray()
|
||||
script += OP_IF.to_bytes(1)
|
||||
push_script_data(script, bytes((2,)))
|
||||
push_script_data(script, Kal_enc)
|
||||
push_script_data(script, Kaf_enc)
|
||||
push_script_data(script, bytes((2,)))
|
||||
script += OP_CHECKMULTISIG.to_bytes(1)
|
||||
script += OP_ELSE.to_bytes(1)
|
||||
script += CScriptNum.encode(CScriptNum(csv_val))
|
||||
script += OP_CHECKSEQUENCEVERIFY.to_bytes(1)
|
||||
script += OP_DROP.to_bytes(1)
|
||||
push_script_data(script, Kaf_enc)
|
||||
script += OP_CHECKSIG.to_bytes(1)
|
||||
script += OP_ENDIF.to_bytes(1)
|
||||
|
||||
return script
|
||||
|
||||
def createSCLockSpendTx(self, tx_lock_bytes, script_lock, pkh_dest, tx_fee_rate, vkbv=None, fee_info={}):
|
||||
tx_lock = self.loadTx(tx_lock_bytes)
|
||||
output_script = self.getScriptDest(script_lock)
|
||||
locked_n = findOutput(tx_lock, output_script)
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx_lock.vout[locked_n].value
|
||||
|
||||
tx_lock_id_int = b2i(tx_lock.TxHash())
|
||||
|
||||
tx = CTransaction()
|
||||
tx.version = self.txVersion()
|
||||
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n, 0)))
|
||||
tx.vout.append(self.txoType()(locked_coin, self.getPubkeyHashDest(pkh_dest)))
|
||||
|
||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
|
||||
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
||||
pay_fee = round(tx_fee_rate * size / 1000)
|
||||
tx.vout[0].value = locked_coin - pay_fee
|
||||
|
||||
fee_info['fee_paid'] = pay_fee
|
||||
fee_info['rate_used'] = tx_fee_rate
|
||||
fee_info['size'] = size
|
||||
|
||||
self._log.info('createSCLockSpendTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.',
|
||||
tx.TxHash().hex(), tx_fee_rate, size, pay_fee)
|
||||
|
||||
return tx.serialize(TxSerializeType.NoWitness)
|
||||
|
||||
def createSCLockRefundTx(self, tx_lock_bytes, script_lock, Kal, Kaf, lock1_value, csv_val, tx_fee_rate, vkbv=None):
|
||||
tx_lock = CTransaction()
|
||||
tx_lock = self.loadTx(tx_lock_bytes)
|
||||
|
||||
output_script = self.getScriptDest(script_lock)
|
||||
locked_n = findOutput(tx_lock, output_script)
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx_lock.vout[locked_n].value
|
||||
|
||||
tx_lock_id_int = b2i(tx_lock.TxHash())
|
||||
|
||||
refund_script = self.genScriptLockRefundTxScript(Kal, Kaf, csv_val)
|
||||
tx = CTransaction()
|
||||
tx.version = self.txVersion()
|
||||
tx.vin.append(CTxIn(COutPoint(tx_lock_id_int, locked_n, 0),
|
||||
sequence=lock1_value))
|
||||
tx.vout.append(self.txoType()(locked_coin, self.getScriptDest(refund_script)))
|
||||
|
||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(script_lock)
|
||||
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
||||
pay_fee = round(tx_fee_rate * size / 1000)
|
||||
tx.vout[0].value = locked_coin - pay_fee
|
||||
|
||||
self._log.info('createSCLockRefundTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.',
|
||||
tx.TxHash().hex(), tx_fee_rate, size, pay_fee)
|
||||
|
||||
return tx.serialize(TxSerializeType.NoWitness), refund_script, tx.vout[0].value
|
||||
|
||||
def createSCLockRefundSpendTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_refund_to, tx_fee_rate, vkbv=None):
|
||||
# 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
|
||||
# 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)
|
||||
|
||||
output_script = self.getScriptDest(script_lock_refund)
|
||||
locked_n = findOutput(tx_lock_refund, output_script)
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx_lock_refund.vout[locked_n].value
|
||||
|
||||
tx_lock_refund_hash_int = b2i(tx_lock_refund.TxHash())
|
||||
|
||||
tx = CTransaction()
|
||||
tx.version = self.txVersion()
|
||||
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n, 0),
|
||||
sequence=0))
|
||||
|
||||
tx.vout.append(self.txoType()(locked_coin, self.getPubkeyHashDest(pkh_refund_to)))
|
||||
|
||||
dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(script_lock_refund)
|
||||
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
||||
pay_fee = round(tx_fee_rate * size / 1000)
|
||||
tx.vout[0].value = locked_coin - pay_fee
|
||||
|
||||
self._log.info('createSCLockRefundSpendTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.',
|
||||
tx.TxHash().hex(), tx_fee_rate, size, pay_fee)
|
||||
|
||||
return tx.serialize(TxSerializeType.NoWitness)
|
||||
|
||||
def verifySCLockTx(self, tx_bytes, script_out,
|
||||
swap_value,
|
||||
Kal, Kaf,
|
||||
feerate,
|
||||
check_lock_tx_inputs, vkbv=None):
|
||||
# Verify:
|
||||
#
|
||||
|
||||
# Not necessary to check the lock txn is mineable, as protocol will wait for it to confirm
|
||||
# However by checking early we can avoid wasting time processing unmineable txns
|
||||
# Check fee is reasonable
|
||||
|
||||
tx = self.loadTx(tx_bytes)
|
||||
txid = self.getTxid(tx)
|
||||
self._log.info('Verifying lock tx: {}.'.format(b2h(txid)))
|
||||
|
||||
ensure(tx.version == self.txVersion(), 'Bad version')
|
||||
ensure(tx.locktime == 0, 'Bad locktime')
|
||||
ensure(tx.expiry == 0, 'Bad expiry')
|
||||
|
||||
script_pk = self.getScriptDest(script_out)
|
||||
locked_n = findOutput(tx, script_pk)
|
||||
ensure(locked_n is not None, 'Lock output not found in tx')
|
||||
locked_coin = tx.vout[locked_n].value
|
||||
|
||||
# Check value
|
||||
ensure(locked_coin == swap_value, 'Bad locked value')
|
||||
|
||||
# Check script
|
||||
A, B = extractScriptLockScriptValues(script_out)
|
||||
ensure(A == Kal, 'Bad script pubkey')
|
||||
ensure(B == Kaf, 'Bad script pubkey')
|
||||
|
||||
if check_lock_tx_inputs:
|
||||
# TODO: Check that inputs are unspent
|
||||
# Verify fee rate
|
||||
inputs_value = 0
|
||||
add_bytes = 0
|
||||
add_witness_bytes = 0
|
||||
for pi in tx.vin:
|
||||
ptx = self.rpc('getrawtransaction', [i2h(pi.prevout.hash), True])
|
||||
prevout = ptx['vout'][pi.prevout.n]
|
||||
inputs_value += self.make_int(prevout['value'])
|
||||
self._log.info('prevout: {}.'.format(prevout))
|
||||
prevout_type = prevout['scriptPubKey']['type']
|
||||
|
||||
'''
|
||||
if prevout_type == 'witness_v0_keyhash':
|
||||
#add_witness_bytes += 107 # sig 72, pk 33 and 2 size bytes
|
||||
#add_witness_bytes += getCompactSizeLen(107)
|
||||
else:
|
||||
# Assume P2PKH, TODO more types
|
||||
add_bytes += 107 # OP_PUSH72 <ecdsa_signature> OP_PUSH33 <public_key>
|
||||
'''
|
||||
|
||||
outputs_value = 0
|
||||
for txo in tx.vout:
|
||||
outputs_value += txo.nValue
|
||||
fee_paid = inputs_value - outputs_value
|
||||
assert (fee_paid > 0)
|
||||
|
||||
size = len(tx.serialize()) + add_witness_bytes
|
||||
fee_rate_paid = fee_paid * 1000 // size
|
||||
|
||||
self._log.info('tx amount, size, feerate: %ld, %ld, %ld', locked_coin, size, fee_rate_paid)
|
||||
|
||||
if not self.compareFeeRates(fee_rate_paid, feerate):
|
||||
self._log.warning('feerate paid doesn\'t match expected: %ld, %ld', fee_rate_paid, feerate)
|
||||
# TODO: Display warning to user
|
||||
|
||||
return txid, locked_n
|
||||
|
||||
def verifySCLockSpendTx(self, tx_bytes,
|
||||
lock_tx_bytes, lock_tx_script,
|
||||
a_pkhash_f, feerate, vkbv=None):
|
||||
# Verify:
|
||||
# Must have only one input with correct prevout (n is always 0) and sequence
|
||||
# Must have only one output with destination and amount
|
||||
|
||||
tx = self.loadTx(tx_bytes)
|
||||
txid = self.getTxid(tx)
|
||||
self._log.info('Verifying lock spend tx: {}.'.format(b2h(txid)))
|
||||
|
||||
ensure(tx.version == self.txVersion(), 'Bad version')
|
||||
ensure(tx.locktime == 0, 'Bad locktime')
|
||||
ensure(tx.expiry == 0, 'Bad expiry')
|
||||
ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
|
||||
|
||||
lock_tx = self.loadTx(lock_tx_bytes)
|
||||
lock_tx_id = self.getTxid(lock_tx)
|
||||
|
||||
output_script = self.getScriptDest(lock_tx_script)
|
||||
locked_n = findOutput(lock_tx, output_script)
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = lock_tx.vout[locked_n].value
|
||||
|
||||
ensure(tx.vin[0].sequence == 0, 'Bad input nSequence')
|
||||
ensure(len(tx.vin[0].signature_script) == 0, 'Input sig not empty')
|
||||
ensure(i2b(tx.vin[0].prevout.hash) == lock_tx_id and tx.vin[0].prevout.n == locked_n, 'Input prevout mismatch')
|
||||
|
||||
ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
|
||||
p2wpkh = self.getPubkeyHashDest(a_pkhash_f)
|
||||
ensure(tx.vout[0].script_pubkey == p2wpkh, 'Bad output destination')
|
||||
|
||||
# The value of the lock tx output should already be verified, if the fee is as expected the difference will be the correct amount
|
||||
fee_paid = locked_coin - tx.vout[0].value
|
||||
assert (fee_paid > 0)
|
||||
|
||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(lock_tx_script)
|
||||
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
||||
fee_rate_paid = fee_paid * 1000 // size
|
||||
|
||||
self._log.info('tx amount, size, feerate: %ld, %ld, %ld', tx.vout[0].value, size, fee_rate_paid)
|
||||
|
||||
if not self.compareFeeRates(fee_rate_paid, feerate):
|
||||
raise ValueError('Bad fee rate, expected: {}'.format(feerate))
|
||||
|
||||
return True
|
||||
|
||||
def verifySCLockRefundTx(self, tx_bytes, lock_tx_bytes, script_out,
|
||||
prevout_id, prevout_n, prevout_seq, prevout_script,
|
||||
Kal, Kaf, csv_val_expect, swap_value, feerate, vkbv=None):
|
||||
# Verify:
|
||||
# Must have only one input with correct prevout and sequence
|
||||
# Must have only one output to the p2wsh of the lock refund script
|
||||
# Output value must be locked_coin - lock tx fee
|
||||
|
||||
tx = self.loadTx(tx_bytes)
|
||||
txid = self.getTxid(tx)
|
||||
self._log.info('Verifying lock refund tx: {}.'.format(b2h(txid)))
|
||||
|
||||
ensure(tx.version == self.txVersion(), 'Bad version')
|
||||
ensure(tx.locktime == 0, 'locktime not 0')
|
||||
ensure(tx.expiry == 0, 'Bad expiry')
|
||||
ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
|
||||
|
||||
ensure(tx.vin[0].sequence == prevout_seq, 'Bad input sequence')
|
||||
ensure(i2b(tx.vin[0].prevout.hash) == prevout_id and tx.vin[0].prevout.n == prevout_n and tx.vin[0].prevout.tree == 0, 'Input prevout mismatch')
|
||||
ensure(len(tx.vin[0].signature_script) == 0, 'Input sig not empty')
|
||||
|
||||
ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
|
||||
|
||||
script_pk = self.getScriptDest(script_out)
|
||||
locked_n = findOutput(tx, script_pk)
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx.vout[locked_n].value
|
||||
|
||||
# Check script and values
|
||||
A, B, csv_val, C = extractScriptLockRefundScriptValues(script_out)
|
||||
ensure(A == Kal, 'Bad script pubkey')
|
||||
ensure(B == Kaf, 'Bad script pubkey')
|
||||
ensure(csv_val == csv_val_expect, 'Bad script csv value')
|
||||
ensure(C == Kaf, 'Bad script pubkey')
|
||||
|
||||
fee_paid = swap_value - locked_coin
|
||||
assert (fee_paid > 0)
|
||||
|
||||
dummy_witness_stack = self.getScriptLockTxDummyWitness(prevout_script)
|
||||
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
||||
fee_rate_paid = fee_paid * 1000 // size
|
||||
|
||||
self._log.info('tx amount, size, feerate: %ld, %ld, %ld', locked_coin, size, fee_rate_paid)
|
||||
|
||||
if not self.compareFeeRates(fee_rate_paid, feerate):
|
||||
raise ValueError('Bad fee rate, expected: {}'.format(feerate))
|
||||
|
||||
return txid, locked_coin, locked_n
|
||||
|
||||
def verifySCLockRefundSpendTx(self, tx_bytes, lock_refund_tx_bytes,
|
||||
lock_refund_tx_id, prevout_script,
|
||||
Kal,
|
||||
prevout_n, prevout_value, feerate, vkbv=None):
|
||||
# Verify:
|
||||
# Must have only one input with correct prevout (n is always 0) and sequence
|
||||
# Must have only one output sending lock refund tx value - fee to leader's address, TODO: follower shouldn't need to verify destination addr
|
||||
tx = self.loadTx(tx_bytes)
|
||||
txid = self.getTxid(tx)
|
||||
self._log.info('Verifying lock refund spend tx: {}.'.format(b2h(txid)))
|
||||
|
||||
ensure(tx.version == self.txVersion(), 'Bad version')
|
||||
ensure(tx.locktime == 0, 'locktime not 0')
|
||||
ensure(tx.expiry == 0, 'Bad expiry')
|
||||
ensure(len(tx.vin) == 1, 'tx doesn\'t have one input')
|
||||
|
||||
ensure(tx.vin[0].sequence == 0, 'Bad input sequence')
|
||||
ensure(len(tx.vin[0].signature_script) == 0, 'Input sig not empty')
|
||||
ensure(i2b(tx.vin[0].prevout.hash) == lock_refund_tx_id and tx.vin[0].prevout.n == 0 and tx.vin[0].prevout.tree == 0, 'Input prevout mismatch')
|
||||
|
||||
ensure(len(tx.vout) == 1, 'tx doesn\'t have one output')
|
||||
|
||||
# Destination doesn't matter to the follower
|
||||
'''
|
||||
p2wpkh = CScript([OP_0, hash160(Kal)])
|
||||
locked_n = findOutput(tx, p2wpkh)
|
||||
ensure(locked_n is not None, 'Output not found in lock refund spend tx')
|
||||
'''
|
||||
tx_value = tx.vout[0].value
|
||||
|
||||
fee_paid = prevout_value - tx_value
|
||||
assert (fee_paid > 0)
|
||||
|
||||
dummy_witness_stack = self.getScriptLockRefundSpendTxDummyWitness(prevout_script)
|
||||
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
||||
fee_rate_paid = fee_paid * 1000 // size
|
||||
|
||||
self._log.info('tx amount, size, feerate: %ld, %ld, %ld', tx_value, size, fee_rate_paid)
|
||||
|
||||
if not self.compareFeeRates(fee_rate_paid, feerate):
|
||||
raise ValueError('Bad fee rate, expected: {}'.format(feerate))
|
||||
|
||||
return True
|
||||
|
||||
def createSCLockRefundSpendToFTx(self, tx_lock_refund_bytes, script_lock_refund, pkh_dest, tx_fee_rate, vkbv=None):
|
||||
# lock refund swipe tx
|
||||
# Sends the coinA locked coin to the follower
|
||||
|
||||
tx_lock_refund = self.loadTx(tx_lock_refund_bytes)
|
||||
|
||||
output_script = self.getScriptDest(script_lock_refund)
|
||||
locked_n = findOutput(tx_lock_refund, output_script)
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_amount = tx_lock_refund.vout[locked_n].value
|
||||
|
||||
A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund)
|
||||
|
||||
tx_lock_refund_hash_int = b2i(tx_lock_refund.TxHash())
|
||||
|
||||
tx = CTransaction()
|
||||
tx.version = self.txVersion()
|
||||
tx.vin.append(CTxIn(COutPoint(tx_lock_refund_hash_int, locked_n, 0),
|
||||
sequence=lock2_value,))
|
||||
|
||||
tx.vout.append(self.txoType()(locked_amount, self.getPubkeyHashDest(pkh_dest)))
|
||||
|
||||
dummy_witness_stack = self.getScriptLockRefundSwipeTxDummyWitness(script_lock_refund)
|
||||
size = len(self.setTxSignature(tx.serialize(), dummy_witness_stack))
|
||||
pay_fee = round(tx_fee_rate * size / 1000)
|
||||
tx.vout[0].value = locked_amount - pay_fee
|
||||
|
||||
self._log.info('createSCLockRefundSpendToFTx %s:\n fee_rate, size, fee: %ld, %ld, %ld.',
|
||||
tx.TxHash().hex(), tx_fee_rate, size, pay_fee)
|
||||
|
||||
return tx.serialize(TxSerializeType.NoWitness)
|
||||
|
||||
def signTxOtVES(self, key_sign: bytes, pubkey_encrypt: bytes, tx_bytes: bytes, input_n: int, prevout_script: bytes, prevout_value: int) -> bytes:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
sig_hash = DCRSignatureHash(prevout_script, SigHashType.SigHashAll, tx, input_n)
|
||||
|
||||
return ecdsaotves_enc_sign(key_sign, pubkey_encrypt, sig_hash)
|
||||
|
||||
def verifyTxOtVES(self, tx_bytes: bytes, ct: bytes, Ks: bytes, Ke: bytes, input_n: int, prevout_script: bytes, prevout_value):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
sig_hash = DCRSignatureHash(prevout_script, SigHashType.SigHashAll, tx, input_n)
|
||||
return ecdsaotves_enc_verify(Ks, Ke, sig_hash, ct)
|
||||
|
||||
def decryptOtVES(self, k: bytes, esig: bytes) -> bytes:
|
||||
return ecdsaotves_dec_sig(k, esig) + bytes((SigHashType.SigHashAll,))
|
||||
|
||||
def recoverEncKey(self, esig, sig, K):
|
||||
return ecdsaotves_rec_enc_key(K, esig, sig[:-1]) # Strip sighash type
|
||||
|
||||
def getTxOutputPos(self, tx, script):
|
||||
if isinstance(tx, bytes):
|
||||
tx = self.loadTx(tx)
|
||||
script_pk = self.getScriptDest(script)
|
||||
return findOutput(tx, script_pk)
|
||||
|
||||
def getScriptLockTxDummyWitness(self, script: bytes):
|
||||
return [
|
||||
bytes(72),
|
||||
bytes(72),
|
||||
bytes(len(script))
|
||||
]
|
||||
|
||||
def getScriptLockRefundSpendTxDummyWitness(self, script: bytes):
|
||||
return [
|
||||
bytes(72),
|
||||
bytes(72),
|
||||
bytes((1,)),
|
||||
bytes(len(script))
|
||||
]
|
||||
|
||||
def extractLeaderSig(self, tx_bytes: bytes) -> bytes:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
|
||||
sig_len = tx.vin[0].signature_script[0]
|
||||
return tx.vin[0].signature_script[1: 1 + sig_len]
|
||||
|
||||
def extractFollowerSig(self, tx_bytes: bytes) -> bytes:
|
||||
tx = self.loadTx(tx_bytes)
|
||||
|
||||
sig_len = tx.vin[0].signature_script[0]
|
||||
ofs = 1 + sig_len
|
||||
sig_len = tx.vin[0].signature_script[ofs]
|
||||
ofs += 1
|
||||
return tx.vin[0].signature_script[ofs: ofs + sig_len]
|
||||
|
||||
def unlockInputs(self, tx_bytes):
|
||||
tx = self.loadTx(tx_bytes)
|
||||
|
||||
inputs = []
|
||||
for txi in tx.vin:
|
||||
inputs.append({'amount': float(self.format_amount(txi.value_in)), 'txid': i2h(txi.prevout.hash), 'vout': txi.prevout.n, 'tree': txi.prevout.tree})
|
||||
self.rpc_wallet('lockunspent', [True, inputs])
|
||||
|
||||
def getWalletRestoreHeight(self) -> int:
|
||||
start_time = self.rpc_wallet('getinfo')['keypoololdest']
|
||||
|
||||
blockchaininfo = self.getBlockchainInfo()
|
||||
best_block = blockchaininfo['bestblockhash']
|
||||
|
||||
chain_synced = round(blockchaininfo['verificationprogress'], 3)
|
||||
if chain_synced < 1.0:
|
||||
raise ValueError('{} chain isn\'t synced.'.format(self.coin_name()))
|
||||
|
||||
self._log.debug('Finding block at time: {}'.format(start_time))
|
||||
|
||||
rpc_conn = self.open_rpc()
|
||||
try:
|
||||
block_hash = best_block
|
||||
while True:
|
||||
block_header = self.json_request(rpc_conn, 'getblockheader', [block_hash])
|
||||
if block_header['time'] < start_time:
|
||||
return block_header['height']
|
||||
# genesis block
|
||||
if block_header['previousblockhash'] == '0000000000000000000000000000000000000000000000000000000000000000':
|
||||
return block_header['height']
|
||||
|
||||
block_hash = block_header['previousblockhash']
|
||||
finally:
|
||||
self.close_rpc(rpc_conn)
|
||||
raise ValueError('{} wallet restore height not found.'.format(self.coin_name()))
|
||||
|
||||
def createBLockTx(self, Kbs, output_amount, vkbv=None) -> bytes:
|
||||
tx = CTransaction()
|
||||
tx.version = self.txVersion()
|
||||
script_pk = self.getPkDest(Kbs)
|
||||
tx.vout.append(self.txoType()(output_amount, script_pk))
|
||||
return tx.serialize()
|
||||
|
||||
def publishBLockTx(self, kbv, Kbs, output_amount, feerate, unlock_time: int = 0) -> bytes:
|
||||
b_lock_tx = self.createBLockTx(Kbs, output_amount)
|
||||
|
||||
b_lock_tx = self.fundTx(b_lock_tx, feerate)
|
||||
b_lock_tx_id = self.getTxid(b_lock_tx)
|
||||
b_lock_tx = self.signTxWithWallet(b_lock_tx)
|
||||
|
||||
return bytes.fromhex(self.publishTx(b_lock_tx))
|
||||
|
||||
def getBLockSpendTxFee(self, tx, fee_rate: int) -> int:
|
||||
witness_bytes = 120 # TODO
|
||||
size = len(tx.serialize()) + witness_bytes
|
||||
pay_fee = round(fee_rate * size / 1000)
|
||||
self._log.info(f'BLockSpendTx fee_rate, vsize, fee: {fee_rate}, {size}, {pay_fee}.')
|
||||
return pay_fee
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, lock_tx_vout=None) -> bytes:
|
||||
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
|
||||
locked_n = lock_tx_vout
|
||||
|
||||
Kbs = self.getPubkey(kbs)
|
||||
script_pk = self.getPkDest(Kbs)
|
||||
|
||||
if locked_n is None:
|
||||
self._log.debug(f'Unknown lock vout, searching tx: {chain_b_lock_txid.hex()}')
|
||||
# When refunding a lock tx, it should be in the wallet as a sent tx
|
||||
wtx = self.rpc_wallet('gettransaction', [chain_b_lock_txid.hex(), ])
|
||||
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
|
||||
locked_n = findOutput(lock_tx, script_pk)
|
||||
|
||||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
pkh_to = self.decodeAddress(address_to)
|
||||
|
||||
tx = CTransaction()
|
||||
tx.version = self.txVersion()
|
||||
|
||||
chain_b_lock_txid_int = b2i(chain_b_lock_txid)
|
||||
|
||||
tx.vin.append(CTxIn(COutPoint(chain_b_lock_txid_int, locked_n, 0),
|
||||
sequence=0))
|
||||
tx.vout.append(self.txoType()(cb_swap_value, self.getPubkeyHashDest(pkh_to)))
|
||||
|
||||
pay_fee = self.getBLockSpendTxFee(tx, b_fee)
|
||||
tx.vout[0].value = cb_swap_value - pay_fee
|
||||
|
||||
b_lock_spend_tx = tx.serialize()
|
||||
b_lock_spend_tx = self.signTxWithKey(b_lock_spend_tx, kbs)
|
||||
|
||||
return bytes.fromhex(self.publishTx(b_lock_spend_tx))
|
||||
|
||||
def findTxnByHash(self, txid_hex: str):
|
||||
try:
|
||||
txout = self.rpc('gettxout', [txid_hex, 0, 0, True])
|
||||
except Exception as e:
|
||||
# self._log.warning('gettxout {}'.format(e))
|
||||
return None
|
||||
|
||||
confirmations: int = 0 if 'confirmations' not in txout else txout['confirmations']
|
||||
if confirmations >= self.blocks_confirmed:
|
||||
block_height = self.getChainHeight() - confirmations # TODO: Better way?
|
||||
return {'txid': txid_hex, 'amount': 0, 'height': block_height}
|
||||
return None
|
||||
|
||||
def isTxExistsError(self, err_str: str) -> bool:
|
||||
return 'transaction already exists' in err_str or 'already have transaction' in err_str
|
||||
|
||||
def isTxNonFinalError(self, err_str: str) -> bool:
|
||||
return 'locks on inputs not met' in err_str
|
||||
|
|
|
@ -153,7 +153,7 @@ class CTransaction:
|
|||
o += script_bytes
|
||||
|
||||
def serialize(self, ser_type=TxSerializeType.Full) -> bytes:
|
||||
data = bytearray()
|
||||
data = bytes()
|
||||
version = (self.version & 0xffff) | (ser_type << 16)
|
||||
data += version.to_bytes(4, 'little')
|
||||
|
||||
|
@ -195,3 +195,10 @@ class CTransaction:
|
|||
|
||||
def TxHashFull(self) -> bytes:
|
||||
raise ValueError('todo')
|
||||
|
||||
|
||||
def findOutput(tx, script_pk: bytes):
|
||||
for i in range(len(tx.vout)):
|
||||
if tx.vout[i].script_pubkey == script_pk:
|
||||
return i
|
||||
return None
|
||||
|
|
|
@ -27,6 +27,15 @@ def callrpc(rpc_port, auth, method, params=[], host='127.0.0.1'):
|
|||
return r['result']
|
||||
|
||||
|
||||
def openrpc(rpc_port, auth, host='127.0.0.1'):
|
||||
try:
|
||||
url = 'http://{}@{}:{}/'.format(auth, host, rpc_port)
|
||||
return Jsonrpc(url)
|
||||
except Exception as ex:
|
||||
traceback.print_exc()
|
||||
raise ValueError('RPC error ' + str(ex))
|
||||
|
||||
|
||||
def make_rpc_func(port, auth, host='127.0.0.1'):
|
||||
port = port
|
||||
auth = auth
|
||||
|
|
|
@ -9,14 +9,19 @@ OP_0 = 0x00
|
|||
OP_DATA_1 = 0x01
|
||||
OP_1NEGATE = 0x4f
|
||||
OP_1 = 0x51
|
||||
OP_IF = 0x63
|
||||
OP_ELSE = 0x67
|
||||
OP_ENDIF = 0x68
|
||||
OP_DROP = 0x75
|
||||
OP_DUP = 0x76
|
||||
OP_EQUAL = 0x87
|
||||
OP_EQUALVERIFY = 0x88
|
||||
OP_PUSHDATA1 = 0x4c
|
||||
OP_PUSHDATA2 = 0x4d
|
||||
OP_PUSHDATA4 = 0x4e
|
||||
OP_DUP = 0x76
|
||||
OP_EQUALVERIFY = 0x88
|
||||
OP_HASH160 = 0xa9
|
||||
OP_CHECKSIG = 0xac
|
||||
OP_CHECKMULTISIG = 0xae
|
||||
OP_CHECKSEQUENCEVERIFY = 0xb2
|
||||
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ class FIROInterface(BTCInterface):
|
|||
return addr_info['ismine']
|
||||
return addr_info['ismine'] or addr_info['iswatchonly']
|
||||
|
||||
def getSCLockScriptAddress(self, lock_script):
|
||||
def getSCLockScriptAddress(self, lock_script: bytes) -> str:
|
||||
lock_tx_dest = self.getScriptDest(lock_script)
|
||||
address = self.encodeScriptDest(lock_tx_dest)
|
||||
|
||||
|
@ -201,7 +201,7 @@ class FIROInterface(BTCInterface):
|
|||
add_bytes = 107
|
||||
size = len(tx.serialize_with_witness()) + add_bytes
|
||||
pay_fee = round(fee_rate * size / 1000)
|
||||
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
|
||||
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
|
||||
return pay_fee
|
||||
|
||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||
|
|
|
@ -13,7 +13,12 @@ from coincurve.keys import (
|
|||
PublicKey,
|
||||
PrivateKey,
|
||||
)
|
||||
from .btc import BTCInterface, find_vout_for_address_from_txobj, findOutput
|
||||
from basicswap.interface.btc import (
|
||||
BTCInterface,
|
||||
extractScriptLockRefundScriptValues,
|
||||
findOutput,
|
||||
find_vout_for_address_from_txobj,
|
||||
)
|
||||
from basicswap.rpc import make_rpc_func
|
||||
from basicswap.chainparams import Coins
|
||||
from basicswap.interface.contrib.nav_test_framework.mininode import (
|
||||
|
@ -24,7 +29,6 @@ from basicswap.interface.contrib.nav_test_framework.mininode import (
|
|||
CTransaction,
|
||||
CTxInWitness,
|
||||
FromHex,
|
||||
uint256_from_str,
|
||||
)
|
||||
from basicswap.util.crypto import hash160
|
||||
from basicswap.util.address import (
|
||||
|
@ -33,7 +37,7 @@ from basicswap.util.address import (
|
|||
encodeAddress,
|
||||
)
|
||||
from basicswap.util import (
|
||||
i2b, i2h,
|
||||
b2i, i2b, i2h,
|
||||
ensure,
|
||||
)
|
||||
from basicswap.basicswap_util import (
|
||||
|
@ -305,7 +309,7 @@ class NAVInterface(BTCInterface):
|
|||
def createRedeemTxn(self, prevout, output_addr: str, output_value: int, txn_script: bytes) -> str:
|
||||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
|
||||
prev_txid = b2i(bytes.fromhex(prevout['txid']))
|
||||
|
||||
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']),
|
||||
scriptSig=self.getScriptScriptSig(txn_script)))
|
||||
|
@ -319,7 +323,7 @@ class NAVInterface(BTCInterface):
|
|||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
tx.nLockTime = locktime
|
||||
prev_txid = uint256_from_str(bytes.fromhex(prevout['txid'])[::-1])
|
||||
prev_txid = b2i(bytes.fromhex(prevout['txid']))
|
||||
tx.vin.append(CTxIn(COutPoint(prev_txid, prevout['vout']),
|
||||
nSequence=sequence,
|
||||
scriptSig=self.getScriptScriptSig(txn_script)))
|
||||
|
@ -512,7 +516,7 @@ class NAVInterface(BTCInterface):
|
|||
tx.vout.append(self.txoType()(output_amount, script_pk))
|
||||
return tx.serialize()
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int) -> bytes:
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, lock_tx_vout=None) -> bytes:
|
||||
self._log.info('spendBLockTx %s:\n', chain_b_lock_txid.hex())
|
||||
wtx = self.rpc('gettransaction', [chain_b_lock_txid.hex(), ])
|
||||
lock_tx = self.loadTx(bytes.fromhex(wtx['hex']))
|
||||
|
@ -526,7 +530,7 @@ class NAVInterface(BTCInterface):
|
|||
tx = CTransaction()
|
||||
tx.nVersion = self.txVersion()
|
||||
|
||||
chain_b_lock_txid_int = uint256_from_str(chain_b_lock_txid[::-1])
|
||||
chain_b_lock_txid_int = b2i(chain_b_lock_txid)
|
||||
|
||||
script_sig = self.getInputScriptForPubkeyHash(self.getPubkeyHash(Kbs))
|
||||
|
||||
|
@ -678,7 +682,7 @@ class NAVInterface(BTCInterface):
|
|||
ensure(locked_n is not None, 'Output not found in tx')
|
||||
locked_coin = tx_lock_refund.vout[locked_n].nValue
|
||||
|
||||
A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
|
||||
A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund)
|
||||
|
||||
tx_lock_refund.rehash()
|
||||
tx_lock_refund_hash_int = tx_lock_refund.sha256
|
||||
|
|
|
@ -28,8 +28,13 @@ from basicswap.util.script import (
|
|||
from basicswap.util.address import (
|
||||
encodeStealthAddress,
|
||||
)
|
||||
from basicswap.interface.btc import (
|
||||
BTCInterface,
|
||||
extractScriptLockScriptValues,
|
||||
extractScriptLockRefundScriptValues,
|
||||
)
|
||||
|
||||
from basicswap.chainparams import Coins, chainparams
|
||||
from .btc import BTCInterface
|
||||
|
||||
|
||||
class BalanceTypes(IntEnum):
|
||||
|
@ -354,7 +359,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||
lock_txo_scriptpk = bytes.fromhex(lock_tx_obj['vout'][lock_output_n]['scriptPubKey']['hex'])
|
||||
script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
|
||||
ensure(lock_txo_scriptpk == script_pk, 'Bad output script')
|
||||
A, B = self.extractScriptLockScriptValues(script_out)
|
||||
A, B = extractScriptLockScriptValues(script_out)
|
||||
ensure(A == Kal, 'Bad script leader pubkey')
|
||||
ensure(B == Kaf, 'Bad script follower pubkey')
|
||||
|
||||
|
@ -402,7 +407,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||
lock_refund_txo_scriptpk = bytes.fromhex(lock_refund_tx_obj['vout'][lock_refund_output_n]['scriptPubKey']['hex'])
|
||||
script_pk = CScript([OP_0, hashlib.sha256(script_out).digest()])
|
||||
ensure(lock_refund_txo_scriptpk == script_pk, 'Bad output script')
|
||||
A, B, csv_val, C = self.extractScriptLockRefundScriptValues(script_out)
|
||||
A, B, csv_val, C = extractScriptLockRefundScriptValues(script_out)
|
||||
ensure(A == Kal, 'Bad script pubkey')
|
||||
ensure(B == Kaf, 'Bad script pubkey')
|
||||
ensure(csv_val == csv_val_expect, 'Bad script csv value')
|
||||
|
@ -632,7 +637,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||
addr_info = self.rpc_wallet('getaddressinfo', [addr_out])
|
||||
output_pubkey_hex = addr_info['pubkey']
|
||||
|
||||
A, B, lock2_value, C = self.extractScriptLockRefundScriptValues(script_lock_refund)
|
||||
A, B, lock2_value, C = extractScriptLockRefundScriptValues(script_lock_refund)
|
||||
|
||||
# Follower won't be able to decode output to check amount, shouldn't matter as fee is public and output is to leader, sum has to balance
|
||||
|
||||
|
@ -715,7 +720,7 @@ class PARTInterfaceBlind(PARTInterface):
|
|||
return -1
|
||||
return None
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False) -> bytes:
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes:
|
||||
Kbv = self.getPubkey(kbv)
|
||||
Kbs = self.getPubkey(kbs)
|
||||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||
|
@ -851,7 +856,7 @@ class PARTInterfaceAnon(PARTInterface):
|
|||
return -1
|
||||
return None
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False) -> bytes:
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes:
|
||||
Kbv = self.getPubkey(kbv)
|
||||
Kbs = self.getPubkey(kbs)
|
||||
sx_addr = self.formatStealthAddress(Kbv, Kbs)
|
||||
|
|
|
@ -107,7 +107,7 @@ class PIVXInterface(BTCInterface):
|
|||
add_bytes = 107
|
||||
size = len(tx.serialize_with_witness()) + add_bytes
|
||||
pay_fee = round(fee_rate * size / 1000)
|
||||
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
|
||||
self._log.info(f'BLockSpendTx fee_rate, size, fee: {fee_rate}, {size}, {pay_fee}.')
|
||||
return pay_fee
|
||||
|
||||
def signTxWithKey(self, tx: bytes, key: bytes) -> bytes:
|
||||
|
|
|
@ -409,7 +409,7 @@ class XMRInterface(CoinInterface):
|
|||
|
||||
return None
|
||||
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee_rate: int, restore_height: int, spend_actual_balance: bool = False) -> bytes:
|
||||
def spendBLockTx(self, chain_b_lock_txid: bytes, address_to: str, kbv: bytes, kbs: bytes, cb_swap_value: int, b_fee_rate: int, restore_height: int, spend_actual_balance: bool = False, lock_tx_vout=None) -> bytes:
|
||||
'''
|
||||
Notes:
|
||||
"Error: No unlocked balance in the specified subaddress(es)" can mean not enough funds after tx fee.
|
||||
|
|
|
@ -21,13 +21,17 @@ from basicswap.basicswap_util import (
|
|||
from . import ProtocolInterface
|
||||
from basicswap.contrib.test_framework.script import (
|
||||
CScript, CScriptOp,
|
||||
OP_CHECKMULTISIG)
|
||||
OP_CHECKMULTISIG
|
||||
)
|
||||
|
||||
|
||||
def addLockRefundSigs(self, xmr_swap, ci):
|
||||
self.log.debug('Setting lock refund tx sigs')
|
||||
witness_stack = [
|
||||
b'',
|
||||
|
||||
witness_stack = []
|
||||
if ci.coin_type() not in (Coins.DCR, ):
|
||||
witness_stack += [b'', ]
|
||||
witness_stack += [
|
||||
xmr_swap.al_lock_refund_tx_sig,
|
||||
xmr_swap.af_lock_refund_tx_sig,
|
||||
xmr_swap.a_lock_tx_script,
|
||||
|
@ -74,7 +78,8 @@ def recoverNoScriptTxnWithKey(self, bid_id: bytes, encoded_key):
|
|||
address_to = self.getCachedStealthAddressForCoin(offer.coin_to)
|
||||
|
||||
amount = bid.amount_to
|
||||
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, bid.chain_b_height_start, spend_actual_balance=True)
|
||||
lock_tx_vout = bid.getLockTXBVout()
|
||||
txid = ci_to.spendBLockTx(xmr_swap.b_lock_tx_id, address_to, xmr_swap.vkbv, vkbs, amount, xmr_offer.b_fee_rate, bid.chain_b_height_start, spend_actual_balance=True, lock_tx_vout=lock_tx_vout)
|
||||
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, txid.hex(), session)
|
||||
session.commit()
|
||||
|
|
|
@ -419,9 +419,10 @@ def compare_bid_states(states, expect_states, exact_match: bool = True) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
def compare_bid_states_unordered(states, expect_states) -> bool:
|
||||
def compare_bid_states_unordered(states, expect_states, ignore_states=[]) -> bool:
|
||||
ignore_states.append('Bid Delaying')
|
||||
for i in range(len(states) - 1, -1, -1):
|
||||
if states[i][1] == 'Bid Delaying':
|
||||
if states[i][1] in ignore_states:
|
||||
del states[i]
|
||||
|
||||
try:
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
# TODO
|
||||
# - Occasionally DCR simnet chain stalls.
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
|
@ -27,6 +24,7 @@ from basicswap.basicswap import (
|
|||
)
|
||||
from basicswap.basicswap_util import (
|
||||
TxLockTypes,
|
||||
TxTypes
|
||||
)
|
||||
from basicswap.util.crypto import (
|
||||
hash160
|
||||
|
@ -80,7 +78,25 @@ def make_rpc_func(node_id, base_rpc_port):
|
|||
return rpc_func
|
||||
|
||||
|
||||
def test_success_path(self, coin_from: Coins, coin_to: Coins):
|
||||
def wait_for_dcr_height(http_port, num_blocks=3):
|
||||
logging.info('Waiting for DCR chain height %d', num_blocks)
|
||||
for i in range(60):
|
||||
if test_delay_event.is_set():
|
||||
raise ValueError('Test stopped.')
|
||||
try:
|
||||
wallet = read_json_api(http_port, 'wallets/dcr')
|
||||
decred_blocks = wallet['blocks']
|
||||
print('decred_blocks', decred_blocks)
|
||||
if decred_blocks >= num_blocks:
|
||||
return
|
||||
except Exception as e:
|
||||
print('Error reading wallets', str(e))
|
||||
|
||||
test_delay_event.wait(1)
|
||||
raise ValueError(f'wait_for_decred_blocks failed http_port: {http_port}')
|
||||
|
||||
|
||||
def run_test_success_path(self, coin_from: Coins, coin_to: Coins):
|
||||
logging.info(f'---------- Test {coin_from.name} to {coin_to.name}')
|
||||
|
||||
node_from = 0
|
||||
|
@ -106,7 +122,7 @@ def test_success_path(self, coin_from: Coins, coin_to: Coins):
|
|||
swap_clients[node_from].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[node_from], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
|
||||
wait_for_bid(test_delay_event, swap_clients[node_to], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120)
|
||||
wait_for_bid(test_delay_event, swap_clients[node_to], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
|
||||
|
||||
# Verify lock tx spends are found in the expected wallets
|
||||
bid, offer = swap_clients[node_from].getBidAndOffer(bid_id)
|
||||
|
@ -139,7 +155,7 @@ def test_success_path(self, coin_from: Coins, coin_to: Coins):
|
|||
assert (compare_bid_states(bidder_states, self.states_bidder_sh[0]) is True)
|
||||
|
||||
|
||||
def test_bad_ptx(self, coin_from: Coins, coin_to: Coins):
|
||||
def run_test_bad_ptx(self, coin_from: Coins, coin_to: Coins):
|
||||
# Invalid PTX sent, swap should stall and ITx and PTx should be reclaimed by senders
|
||||
logging.info(f'---------- Test bad ptx {coin_from.name} to {coin_to.name}')
|
||||
|
||||
|
@ -164,7 +180,7 @@ def test_bad_ptx(self, coin_from: Coins, coin_to: Coins):
|
|||
swap_clients[node_to].setBidDebugInd(bid_id, DebugTypes.MAKE_INVALID_PTX)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[node_from], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
|
||||
wait_for_bid(test_delay_event, swap_clients[node_to], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120)
|
||||
wait_for_bid(test_delay_event, swap_clients[node_to], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
|
||||
|
||||
js_0_bid = read_json_api(1800 + node_from, 'bids/{}'.format(bid_id.hex()))
|
||||
js_1_bid = read_json_api(1800 + node_to, 'bids/{}'.format(bid_id.hex()))
|
||||
|
@ -190,6 +206,8 @@ def test_bad_ptx(self, coin_from: Coins, coin_to: Coins):
|
|||
offerer_states = read_json_api(1800 + node_from, path)
|
||||
bidder_states = read_json_api(1800 + node_to, path)
|
||||
|
||||
if coin_to not in (Coins.XMR,):
|
||||
return
|
||||
# Hard to get the timing right
|
||||
assert (compare_bid_states_unordered(offerer_states, self.states_offerer_sh[1]) is True)
|
||||
assert (compare_bid_states_unordered(bidder_states, self.states_bidder_sh[1]) is True)
|
||||
|
@ -200,7 +218,7 @@ def test_bad_ptx(self, coin_from: Coins, coin_to: Coins):
|
|||
assert (js_1['num_swapping'] == 0 and js_1['num_watched_outputs'] == 0)
|
||||
|
||||
|
||||
def test_itx_refund(self, coin_from: Coins, coin_to: Coins):
|
||||
def run_test_itx_refund(self, coin_from: Coins, coin_to: Coins):
|
||||
# Offerer claims PTX and refunds ITX after lock expires
|
||||
# Bidder loses PTX value without gaining ITX value
|
||||
logging.info(f'---------- Test itx refund {coin_from.name} to {coin_to.name}')
|
||||
|
@ -250,6 +268,189 @@ def test_itx_refund(self, coin_from: Coins, coin_to: Coins):
|
|||
assert (bid.amount_to - node_from_ci_to.make_int(wtx['details'][0]['amount']) < max_fee)
|
||||
|
||||
|
||||
def run_test_ads_success_path(self, coin_from: Coins, coin_to: Coins):
|
||||
logging.info(f'---------- Test ADS swap {coin_from.name} to {coin_to.name}')
|
||||
|
||||
# Offerer sends the offer
|
||||
# Bidder sends the bid
|
||||
id_offerer: int = 0
|
||||
id_bidder: int = 1
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
reverse_bid: bool = coin_from in swap_clients[id_offerer].scriptless_coins
|
||||
ci_from = swap_clients[id_offerer].ci(coin_from)
|
||||
ci_to = swap_clients[id_bidder].ci(coin_to)
|
||||
|
||||
self.prepare_balance(coin_to, 100.0, 1801, 1800)
|
||||
self.prepare_balance(coin_from, 100.0, 1800, 1801)
|
||||
|
||||
# Leader sends the initial (chain a) lock tx.
|
||||
# Follower sends the participate (chain b) lock tx.
|
||||
id_leader: int = id_bidder if reverse_bid else id_offerer
|
||||
id_follower: int = id_offerer if reverse_bid else id_bidder
|
||||
logging.info(f'Offerer, bidder, leader, follower: {id_offerer}, {id_bidder}, {id_leader}, {id_follower}')
|
||||
|
||||
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
offer_id = swap_clients[id_offerer].postOffer(coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP)
|
||||
|
||||
wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
|
||||
bid_id = swap_clients[id_bidder].postXmrBid(offer_id, amt_swap)
|
||||
wait_for_bid(test_delay_event, swap_clients[id_offerer], bid_id, BidStates.BID_RECEIVED)
|
||||
|
||||
swap_clients[id_offerer].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[id_offerer], bid_id, BidStates.SWAP_COMPLETED, wait_for=(180))
|
||||
wait_for_bid(test_delay_event, swap_clients[id_bidder], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=(30))
|
||||
|
||||
if reverse_bid:
|
||||
return # TODO
|
||||
|
||||
# Verify lock tx spends are found in the expected wallets
|
||||
bid, xmr_swap = swap_clients[id_offerer].getXmrBid(bid_id)
|
||||
|
||||
node_from_ci_to = swap_clients[0].ci(coin_to)
|
||||
max_fee: int = 10000
|
||||
if node_from_ci_to.coin_type() in (Coins.XMR, ):
|
||||
pass
|
||||
else:
|
||||
wtx = node_from_ci_to.rpc_wallet('gettransaction', [bid.xmr_b_lock_tx.spend_txid.hex(),])
|
||||
assert (bid.amount_to - node_from_ci_to.make_int(wtx['details'][0]['amount']) < max_fee)
|
||||
|
||||
node_to_ci_from = swap_clients[1].ci(coin_from)
|
||||
if node_to_ci_from.coin_type() in (Coins.XMR, ):
|
||||
pass
|
||||
else:
|
||||
wtx = node_to_ci_from.rpc_wallet('gettransaction', [xmr_swap.a_lock_spend_tx_id.hex(),])
|
||||
assert (bid.amount - node_to_ci_from.make_int(wtx['details'][0]['amount']) < max_fee)
|
||||
|
||||
bid_id_hex = bid_id.hex()
|
||||
path = f'bids/{bid_id_hex}/states'
|
||||
offerer_states = read_json_api(1800 + id_offerer, path)
|
||||
bidder_states = read_json_api(1800 + id_bidder, path)
|
||||
|
||||
assert (compare_bid_states(offerer_states, self.states_offerer[0]) is True)
|
||||
assert (compare_bid_states(bidder_states, self.states_bidder[0]) is True)
|
||||
|
||||
|
||||
def run_test_ads_both_refund(self, coin_from: Coins, coin_to: Coins, lock_value: int = 32) -> None:
|
||||
logging.info('---------- Test {} to {} both lock txns refunded'.format(coin_from.name, coin_to.name))
|
||||
|
||||
id_offerer: int = 0
|
||||
id_bidder: int = 1
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
reverse_bid: bool = coin_from in swap_clients[id_offerer].scriptless_coins
|
||||
ci_from = swap_clients[id_offerer].ci(coin_from)
|
||||
ci_to = swap_clients[id_offerer].ci(coin_to)
|
||||
|
||||
if reverse_bid:
|
||||
self.prepare_balance(coin_to, 100.0, 1801, 1800)
|
||||
self.prepare_balance(coin_from, 100.0, 1800, 1801)
|
||||
|
||||
id_leader: int = id_bidder if reverse_bid else id_offerer
|
||||
id_follower: int = id_offerer if reverse_bid else id_bidder
|
||||
logging.info(f'Offerer, bidder, leader, follower: {id_offerer}, {id_bidder}, {id_leader}, {id_follower}')
|
||||
|
||||
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
|
||||
debug_case = (None, DebugTypes.OFFER_LOCK_2_VALUE_INC)
|
||||
try:
|
||||
swap_clients[0]._debug_cases.append(debug_case)
|
||||
swap_clients[1]._debug_cases.append(debug_case)
|
||||
offer_id = swap_clients[id_offerer].postOffer(
|
||||
coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=lock_value)
|
||||
wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
|
||||
offer = swap_clients[id_bidder].getOffer(offer_id)
|
||||
finally:
|
||||
swap_clients[0]._debug_cases.remove(debug_case)
|
||||
swap_clients[1]._debug_cases.remove(debug_case)
|
||||
|
||||
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)
|
||||
|
||||
swap_clients[id_follower].setBidDebugInd(bid_id, DebugTypes.CREATE_INVALID_COIN_B_LOCK)
|
||||
swap_clients[id_offerer].acceptBid(bid_id)
|
||||
|
||||
leader_sent_bid: bool = True if reverse_bid else False
|
||||
wait_for_bid(test_delay_event, swap_clients[id_leader], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=leader_sent_bid, wait_for=(self.extra_wait_time + 180))
|
||||
wait_for_bid(test_delay_event, swap_clients[id_follower], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, sent=(not leader_sent_bid), wait_for=(self.extra_wait_time + 40))
|
||||
|
||||
if reverse_bid:
|
||||
return # TODO
|
||||
|
||||
# Verify lock tx spends are found in the expected wallets
|
||||
bid, xmr_swap = swap_clients[id_offerer].getXmrBid(bid_id)
|
||||
lock_refund_spend_txid = bid.txns[TxTypes.XMR_SWAP_A_LOCK_REFUND_SPEND].txid
|
||||
|
||||
bid, xmr_swap = swap_clients[id_bidder].getXmrBid(bid_id)
|
||||
|
||||
node_from_ci_from = swap_clients[0].ci(coin_from)
|
||||
max_fee: int = 10000
|
||||
if node_from_ci_from.coin_type() in (Coins.XMR, ):
|
||||
pass
|
||||
else:
|
||||
wtx = node_from_ci_from.rpc_wallet('gettransaction', [lock_refund_spend_txid.hex(),])
|
||||
assert (bid.amount - node_from_ci_from.make_int(wtx['details'][0]['amount']) < max_fee)
|
||||
|
||||
node_to_ci_to = swap_clients[1].ci(coin_to)
|
||||
if node_to_ci_to.coin_type() in (Coins.XMR, ):
|
||||
pass
|
||||
else:
|
||||
wtx = node_to_ci_to.rpc_wallet('gettransaction', [bid.xmr_b_lock_tx.spend_txid.hex(),])
|
||||
assert (bid.amount_to - node_to_ci_to.make_int(wtx['details'][0]['amount']) < max_fee)
|
||||
|
||||
bid_id_hex = bid_id.hex()
|
||||
path = f'bids/{bid_id_hex}/states'
|
||||
offerer_states = read_json_api(1800 + id_offerer, path)
|
||||
bidder_states = read_json_api(1800 + id_bidder, path)
|
||||
|
||||
assert (compare_bid_states(offerer_states, self.states_offerer[1]) is True)
|
||||
assert (compare_bid_states(bidder_states, self.states_bidder[1]) is True)
|
||||
|
||||
|
||||
def run_test_ads_swipe_refund(self, coin_from: Coins, coin_to: Coins, lock_value: int = 32) -> None:
|
||||
logging.info('---------- Test {} to {} coin a lock refund tx swiped'.format(coin_from.name, coin_to.name))
|
||||
|
||||
id_offerer: int = 0
|
||||
id_bidder: int = 1
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
reverse_bid: bool = coin_from in swap_clients[id_offerer].scriptless_coins
|
||||
ci_from = swap_clients[id_offerer].ci(coin_from)
|
||||
ci_to = swap_clients[id_offerer].ci(coin_to)
|
||||
|
||||
id_leader: int = id_bidder if reverse_bid else id_offerer
|
||||
id_follower: int = id_offerer if reverse_bid else id_bidder
|
||||
logging.info(f'Offerer, bidder, leader, follower: {id_offerer}, {id_bidder}, {id_leader}, {id_follower}')
|
||||
|
||||
if reverse_bid:
|
||||
self.prepare_balance(coin_to, 100.0, 1801, 1800)
|
||||
self.prepare_balance(coin_from, 100.0, 1800, 1801)
|
||||
|
||||
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
offer_id = swap_clients[id_offerer].postOffer(
|
||||
coin_from, coin_to, amt_swap, rate_swap, amt_swap, SwapTypes.XMR_SWAP,
|
||||
lock_type=TxLockTypes.SEQUENCE_LOCK_BLOCKS, lock_value=lock_value)
|
||||
wait_for_offer(test_delay_event, swap_clients[id_bidder], offer_id)
|
||||
offer = swap_clients[id_bidder].getOffer(offer_id)
|
||||
|
||||
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)
|
||||
|
||||
swap_clients[id_follower].setBidDebugInd(bid_id, 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_offerer].acceptBid(bid_id)
|
||||
|
||||
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)
|
||||
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))
|
||||
|
||||
|
||||
def prepareDCDDataDir(datadir, node_id, conf_file, dir_prefix, num_nodes=3):
|
||||
node_dir = os.path.join(datadir, dir_prefix + str(node_id))
|
||||
if not os.path.exists(node_dir):
|
||||
|
@ -298,8 +499,9 @@ class Test(BaseTest):
|
|||
test_coin = Coins.DCR
|
||||
dcr_daemons = []
|
||||
start_ltc_nodes = False
|
||||
start_xmr_nodes = False
|
||||
start_xmr_nodes = True
|
||||
dcr_mining_addr = 'SsYbXyjkKAEXXcGdFgr4u4bo4L8RkCxwQpH'
|
||||
extra_wait_time = 0
|
||||
|
||||
hex_seeds = [
|
||||
'e8574b2a94404ee62d8acc0258cab4c0defcfab8a5dfc2f4954c1f9d7e09d72a',
|
||||
|
@ -331,15 +533,19 @@ class Test(BaseTest):
|
|||
ci0 = cls.swap_clients[0].ci(cls.test_coin)
|
||||
|
||||
num_passed: int = 0
|
||||
for i in range(5):
|
||||
for i in range(30):
|
||||
try:
|
||||
ci0.rpc_wallet('purchaseticket', [cls.dcr_ticket_account, 0.1, 0])
|
||||
num_passed += 1
|
||||
if num_passed >= 5:
|
||||
break
|
||||
test_delay_event.wait(0.1)
|
||||
except Exception as e:
|
||||
if 'double spend' in str(e):
|
||||
pass
|
||||
else:
|
||||
logging.warning('coins_loop purchaseticket {}'.format(e))
|
||||
test_delay_event.wait(0.5)
|
||||
|
||||
try:
|
||||
if num_passed >= 5:
|
||||
|
@ -436,6 +642,8 @@ class Test(BaseTest):
|
|||
'address': js_w[coin_ticker][address_type],
|
||||
'subfee': False,
|
||||
}
|
||||
if coin in (Coins.XMR, ):
|
||||
post_json['sweepall'] = False
|
||||
json_rv = read_json_api(port_take_from_node, 'wallets/{}/withdraw'.format(coin_ticker.lower()), post_json)
|
||||
assert (len(json_rv['txid']) == 64)
|
||||
wait_for_amount: float = amount
|
||||
|
@ -763,22 +971,49 @@ class Test(BaseTest):
|
|||
assert (amount_proved >= require_amount)
|
||||
|
||||
def test_02_part_coin(self):
|
||||
test_success_path(self, Coins.PART, self.test_coin)
|
||||
run_test_success_path(self, Coins.PART, self.test_coin)
|
||||
|
||||
def test_03_coin_part(self):
|
||||
test_success_path(self, self.test_coin, Coins.PART)
|
||||
run_test_success_path(self, self.test_coin, Coins.PART)
|
||||
|
||||
def test_04_part_coin_bad_ptx(self):
|
||||
test_bad_ptx(self, Coins.PART, self.test_coin)
|
||||
run_test_bad_ptx(self, Coins.PART, self.test_coin)
|
||||
|
||||
def test_05_coin_part_bad_ptx(self):
|
||||
test_bad_ptx(self, self.test_coin, Coins.PART)
|
||||
run_test_bad_ptx(self, self.test_coin, Coins.PART)
|
||||
|
||||
def test_06_part_coin_itx_refund(self):
|
||||
test_itx_refund(self, Coins.PART, self.test_coin)
|
||||
run_test_itx_refund(self, Coins.PART, self.test_coin)
|
||||
|
||||
def test_07_coin_part_itx_refund(self):
|
||||
test_itx_refund(self, self.test_coin, Coins.PART)
|
||||
run_test_itx_refund(self, self.test_coin, Coins.PART)
|
||||
|
||||
def test_08_ads_coin_xmr(self):
|
||||
run_test_ads_success_path(self, self.test_coin, Coins.XMR)
|
||||
|
||||
def test_09_ads_xmr_coin(self):
|
||||
# Reverse bid
|
||||
run_test_ads_success_path(self, Coins.XMR, self.test_coin)
|
||||
|
||||
def test_10_ads_part_coin(self):
|
||||
run_test_ads_success_path(self, Coins.PART, self.test_coin)
|
||||
|
||||
def test_11_ads_coin_xmr_both_refund(self):
|
||||
run_test_ads_both_refund(self, self.test_coin, Coins.XMR, lock_value=20)
|
||||
|
||||
def test_12_ads_xmr_coin_both_refund(self):
|
||||
# Reverse bid
|
||||
run_test_ads_both_refund(self, Coins.XMR, self.test_coin, lock_value=20)
|
||||
|
||||
def test_13_ads_part_coin_both_refund(self):
|
||||
run_test_ads_both_refund(self, Coins.PART, self.test_coin, lock_value=20)
|
||||
|
||||
def test_14_ads_coin_xmr_swipe_refund(self):
|
||||
run_test_ads_swipe_refund(self, self.test_coin, Coins.XMR, lock_value=20)
|
||||
|
||||
def test_15_ads_xmr_coin_swipe_refund(self):
|
||||
# Reverse bid
|
||||
run_test_ads_swipe_refund(self, Coins.XMR, self.test_coin, lock_value=20)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -26,7 +26,7 @@ from basicswap.util import (
|
|||
make_int,
|
||||
format_amount,
|
||||
)
|
||||
from basicswap.interface import Curves
|
||||
from basicswap.interface.base import Curves
|
||||
from tests.basicswap.util import (
|
||||
read_json_api,
|
||||
)
|
||||
|
|
|
@ -337,7 +337,7 @@ class Test(BaseTest):
|
|||
wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
|
||||
|
||||
# Verify lock tx spends are found in the expected wallets
|
||||
bid, offer = swap_clients[0].getBidAndOffer(bid_id)
|
||||
|
@ -381,7 +381,7 @@ class Test(BaseTest):
|
|||
wait_for_in_progress(test_delay_event, swap_clients[0], bid_id, sent=True)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=30)
|
||||
|
||||
js_0 = read_json_api(1800)
|
||||
js_1 = read_json_api(1801)
|
||||
|
@ -404,7 +404,7 @@ class Test(BaseTest):
|
|||
wait_for_in_progress(test_delay_event, swap_clients[1], bid_id, sent=True)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
|
||||
|
||||
js_0 = read_json_api(1800)
|
||||
js_1 = read_json_api(1801)
|
||||
|
@ -428,7 +428,7 @@ class Test(BaseTest):
|
|||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=30)
|
||||
|
||||
js_0_bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex()))
|
||||
js_1_bid = read_json_api(1801, 'bids/{}'.format(bid_id.hex()))
|
||||
|
@ -464,7 +464,7 @@ class Test(BaseTest):
|
|||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid_tx_state(test_delay_event, swap_clients[0], bid_id, TxStates.TX_REDEEMED, TxStates.TX_REDEEMED, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=30)
|
||||
|
||||
js_0 = read_json_api(1800)
|
||||
assert (js_0['num_swapping'] == 0 and js_0['num_watched_outputs'] == 0)
|
||||
|
@ -513,7 +513,7 @@ class Test(BaseTest):
|
|||
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
|
||||
|
||||
def test_10_bad_ptx(self):
|
||||
# Invalid PTX sent, swap should stall and ITx and PTx should be reclaimed by senders
|
||||
|
@ -534,7 +534,7 @@ class Test(BaseTest):
|
|||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=120)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
|
||||
|
||||
js_0_bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex()))
|
||||
js_1_bid = read_json_api(1801, 'bids/{}'.format(bid_id.hex()))
|
||||
|
@ -574,7 +574,7 @@ class Test(BaseTest):
|
|||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, wait_for=120)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=120)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_ABANDONED, sent=True, wait_for=30)
|
||||
|
||||
js_0_bid = read_json_api(1800, 'bids/{}'.format(bid_id.hex()))
|
||||
js_1_bid = read_json_api(1801, 'bids/{}'.format(bid_id.hex()))
|
||||
|
@ -727,7 +727,7 @@ class Test(BaseTest):
|
|||
swap_clients[2].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[2], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=30)
|
||||
|
||||
# Verify expected inputs were used
|
||||
bid, offer = swap_clients[2].getBidAndOffer(bid_id)
|
||||
|
@ -762,7 +762,7 @@ class Test(BaseTest):
|
|||
wait_for_in_progress(test_delay_event, swap_clients[0], bid_id, sent=True)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.SWAP_COMPLETED, sent=True, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=80)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.SWAP_COMPLETED, wait_for=30)
|
||||
|
||||
def pass_99_delay(self):
|
||||
logging.info('Delay')
|
||||
|
|
|
@ -1528,7 +1528,7 @@ class Test(BaseTest):
|
|||
swap_clients[0].acceptXmrBid(bid_id)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[0], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=1800)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=1800, sent=True)
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.XMR_SWAP_FAILED_REFUNDED, wait_for=30, sent=True)
|
||||
|
||||
def test_16_new_subaddress(self):
|
||||
logging.info('---------- Test that new subaddresses are created')
|
||||
|
|
Loading…
Reference in a new issue